`
xhload3d
  • 浏览: 210414 次
社区版块
存档分类
最新评论

自定义HT for Web之HTML5表格组件的Renderer和Editor

阅读更多

HT for Web提供了一下几种常用的Editor,分别是:

  • slider:拉条
  • color picker:颜色选择器
  • enum:枚举类型
  • boolean:真假编辑器
  • string:普通的文本编辑器

除了这几种常用编辑器之外,用户还可以通过继承ht.widget.BaseItemEditor类来实现自定义编辑器。

而渲染器,在HT for Web提供常用的Renderer有:

  • enum:枚举类型
  • color:颜色类型
  • boolean:真假渲染器
  • text:文本渲染器

和编辑器一样也可以自定义渲染器,但是方式不太一样,渲染器是通过定义column中drawCell()方法来自定义单元格展现效果。

今天我们就来实现一把自定义HTML5表格组件的Renderer和Editor,为了更直观地演示编辑效果,我们正好利用HT for Web强大的HTML5拓扑图组件

首先来瞧瞧效果:

 


 

效果图中,左边表格的第二列,是定义了一个编辑器,用一个圆盘来表示当前文本的旋转角度,可以通过拖拉来实现角度变换;表格的第三列,是通过drawCell()方法来绘制单元格内容,中间线标识旋转角度为零,向左表示文本逆时针旋转指定角度,向右表示文本顺时针旋转指定角度。

HT for Web的拓扑图网络节点的文字,简单修改label.rotation属性即可实现文字旋转功能,为了更直观我特意加上label.background使得网络拓扑图节点文字具有背景效果。

接下来我们就来看看具体的实现,先来了解下渲染器的实现:

 

 

{
    name : 'label.rotation',
    accessType : 'style',
    drawCell : function(g, data, selected, column, x, y, w, h, tableView) {
        var degree = Math.round(data.s('label.rotation') / Math.PI * 180),
                width = Math.abs(w / 360 * degree),
                begin = w / 2,
                rectColor = '#29BB9C',
                fontColor = '#000',
                background = '#F8F0E5';

        if (selected) {
            rectColor = '#F7F283';
            background = '#29BB9C';
        }
        g.beginPath();
        g.fillStyle = background;
        g.fillRect(x, y, w, h);
        g.beginPath();
        if (degree < 0) begin -= width;
        g.fillStyle = rectColor;
        g.fillRect(x + begin, y, width, h);
        g.beginPath();
        g.font = '12px arial, sans-serif';
        g.fillStyle = fontColor;
        g.textAlign = 'center';
        g.textBaseline = 'middle';
        g.fillText(degree, x + w / 2, y + h / 2);
    }
}

 

 

上面的代码就是定义表格第三列的代码,可以看到除了定义column自身属性外,还添加了drawCell()方法,通过drawCell()方法传递进来的参数,来绘制自己想要的效果。

 

渲染就是这么简单,那么编辑器就没那么容易了,在设计自定义编辑器之前,得先来了解下编辑器的基类ht.widget.BaseItemEditor,其代码如下:

 

ht.widget.BaseItemEditor = function (data, column, master, editInfo) {    
    this._data = data;
    this._column = column;
    this._master = master;
    this._editInfo = editInfo;
};
ht.Default.def(‘ht.widget.BaseItemEditor’, Object, {
    ms_ac:["data", "column", "master", "editInfo"],
    editBeginning: function() {},
    getView: function() {},
    getValue: function() {},
    setValue: function() {}
});

 

它处理构造函数中初始化类变量外,就定义了几个接口,让用户重载实现相关业务操作逻辑处理。那么接下来说说这些接口的具体用意:

editBeginning:在单元格开始编辑前调用

  • getView:获取编辑器view,值类型为DOM元素
  • getValue:获取编辑器值
  • setValue:设置编辑器值,并做编辑器的页面初始化操作

在创建一个自定义编辑器的时候,必须实现这些接口,并在不同的接口中,做不同的操作。

现在我们来看看旋转角度的自定义编辑是如何设计的:

    1. 按照HT for Web组件的设计惯例,我们需要创建一个Div作为view,在view中包含一个canvas元素,组件内容在canvas上绘制;

    2. editor需要与用户有交互,因此,需要在view上添加事件监听,监听用户有可能的操作,在这次的Demo中,我们希望用户通过拖拉角度控制盘来控制角度,所以,我们在view上添加了mousedown、mousemove及mouseup三个事件监听;

    3. 用户通过拖拉组件可以改变角度,这个改变是连续的,而且在拖拉的时候有可能鼠标会离开组件区域,要实现离开组件区域也能够正确的改变值,那么这时候就需要调用HT for Web的startDragging()方法;

 

以上讲述的操作都在构造函数中处理,接下来看看构造函数长什么样:

 

// 类ht.widget.RotationEditor构造函数
ht.widget.RotationEditor = function(data, column, master, editInfo) {
    // 调用父类构造函数初始化参数
    this.getSuperClass().call(this, data, column, master, editInfo);

    var self = this,
        view = self._view = createDiv(1),
        canvas = self._canvas = createCanvas(self._view);
    view.style.boxShadow = '2px 2px 10px #000';

    // 在view上添加mousemove监听
    view.addEventListener('mousemove', function(e) {
        if (self._state) {
            ht.Default.startDragging(self, e);
        }
    });

    // 在view上添加mousedown监听
    view.addEventListener('mousedown', function(e) {
        self._state = 1;
        self.handleWindowMouseMove(e);
    });

    // 在view上添加mouseup监听,做些清理操作
    view.addEventListener('mouseup', function(e) {
        self.clear();
    });
};

 

    4. 接下来就是通过def()方法来定义ht.widget.RotationEditor类继承于ht.widget.BaseItemEditor,并实现父类的方法,代码如下,在代码中,我没有贴出setValue()方法的实现,因为这块有些复杂,我们单独抽出来讲解;

 

ht.Default.def('ht.widget.RotationEditor', ht.widget.BaseItemEditor, {
    editBeginning : function() {
        var self = this,
            editInfo = self.getEditInfo(),
            rect = editInfo.rect;

        // 编辑前再对组件做一次布局,避免组件宽高计算不到位
        layout(self, rect.x, rect.y, rect.width, rect.width);
    },
    getView : function() {
        return this._view;
    },
    getValue : function() {
        return this._value;
    },
    setValue : function(val) {
       // 设置编辑器值,并做编辑器的页面初始化操作
    }
});

 

    5. 我们要在setValue()方法中绘制出文章开头的效果图上面展现的效果,大致分解了些,可以分成以下四步来绘制,当然在绘制之前需要线获得canvas的context对象:

        5.1. 绘制内外圆盘,通过arc()方法绘制两个间隔10px的同心圆;

        5.2. 绘制值区域,通过结合arc()方法及lineTo()方法绘制一个扇形区域,在通过fill方法填充颜色;

        5.3. 绘制指针,通过lineTo()方法绘制两个指针;

        5.4. 绘制文本,在绘制文本的时候,不能直接将文本绘制在圆心处,因为圆心处是指针的交汇处,如果直接绘制文本的话,将与指针重叠,这时,通过clearRect()方法来清除文本区域,在通过fillRect()方法将背景填充上去,不然文本区域块将是透明的,接下来就调用fillText()方法绘制文本。

 

这些就是组件绘制的所有逻辑,但是有一点必须注意,在绘制完组件后,必须调用下restore()方法,因为在initContext()方法中做了一次save()操作,接下来看看具体实现(代码有些长);

 

setValue : function(val) {
    var self = this;
    if (self._value === val) return;

    // 设置组件值
    self._value = val;

    var editInfo = self.getEditInfo(),
        rect = editInfo.rect,
        canvas = self._canvas,
        radius = self._radius = rect.width / 2,
        det = 10,
        border = 2,
        x = radius,
        y = radius;

    // 弧度到角度的转换
    val = Math.round(val / Math.PI * 180);
    // 设置canvas大小
    setCanvas(canvas, rect.width, rect.width);
    // 获取画笔
    var g = initContext(canvas);
    translateAndScale(g, 0, 0, 1);

    // 绘制背景
    g.fillStyle = '#FFF';
    g.fillRect(0, 0, radius * 2, radius * 2);

    // 设置线条颜色及线条宽度
    g.strokeStyle = '#969698';
    g.lineWidth = border;

    // 绘制外圈
    g.beginPath();
    g.arc(x, y, radius - border, 0, Math.PI * 2, true);
    g.stroke();

    // 绘制内圈
    g.beginPath();
    g.arc(x, y, radius - det - border, 0, Math.PI * 2, true);
    g.stroke();

    // 绘制值区域
    var start = -Math.PI / 2,
        end = Math.PI * val / 180 - Math.PI / 2;
    g.beginPath();
    g.fillStyle = 'rgba(255, 0, 0, 0.7)';
    g.arc(x, y, radius - border, end, start, !(val < 0));
    g.lineTo(x, border + det);
    g.arc(x, y, radius - det - border, start, end, val < 0);
    g.closePath();
    // 填充值区域
    g.fill();
    // 绘制值区域末端到圆心的线条
    g.lineTo(x, y);
    g.lineTo(x, det + border);
    g.stroke();

    // 绘制文本
    var font = '12px arial, sans-serif';
    // 计算文本大小
    var textSize = ht.Default.getTextSize(font, '-180');
    // 文本区域
    var textRect = {
        x : x - textSize.width / 2,
        y : y - textSize.height / 2,
        width : textSize.width,
        height : textSize.height
    };
    g.beginPath();
    // 清空文本区域
    g.clearRect(textRect.x, textRect.y, textRect.width, textRect.height);
    g.fillStyle = '#FFF';
    // 补上背景
    g.fillRect(textRect.x, textRect.y, textRect.width, textRect.height);
    // 设置文本样式
    g.textAlign = 'center';
    g.textBaseline = 'middle';
    g.font = font;
    g.fillStyle = 'black';
    // 绘制文本
    g.fillText(val, x, y);

    // restore()和save()是配对的,在initContext()方法中已经做了save()操作
    g.restore();
}

 

    6. 这时候编辑器的设计就大体完成,那么编辑器该如何用到表格上呢?很简单,在表格定义列的时候,加上下面两行代码就可以开始使用编辑器了;

 

editable : true, // 启动编辑
itemEditor : ‘ht.widget.RotationEditor' // 指点编辑器类

 

介绍到这里,编辑器可以正常的绘制出来,但是在操作的时候,你会发现,编辑器并不会根据拖拉的位置而改变角度,这是为什么呢?请看下一点说明:

 

    7. 在构造函数中,view的mousemove事件调用了startDragging()方法,其实这个方法是有依赖的,它需要组件重载handleWindowMouseMove()及handleWindowMouseUp()两个方法。原因很简单,就如第3点种提到的,用户在拖拉组件的时候,有可能拖离了组件区域,这时候只能通过window上的mousemove及mouseup两个事件监听令用户继续操作;

 

// 监听window的mousemove事件,在view的mousemove事件中,调用了startDragging()方法,
// 而startDragging()方法中的实质就是触发window的mousemove事件
// 该方法计算值的变化,并通过setValue()方法来改变值
handleWindowMouseMove : function(e) {
    var rect = this._view.getBoundingClientRect(),
        x = e.x - rect.left,
        y = e.y - rect.top,
        radius = this._radius,
        // 通过反三角函数计算弧度,再将弧度转换为角度
        value = Math.round(Math.atan2(y - radius, x - radius) / Math.PI * 180);

    if (value > 90) {
        value = -(180 - value + 90);
    }
    else {
        value = value + 90;
    }
    this.setValue(value / 180 * Math.PI);
},
handleWindowMouseUp : function(e) {
    this.clear();
},
clear : function() {
    // 清楚状态组件状态
    delete this._state;
}

 

加上上面的三个方法,运行代码可以发现编辑器可以正常编辑了。但是只有在结束编辑后,才可以在拓扑图上看到文本旋转角度变化,如果可以实时更新拓扑图上的文本旋转角度,将会更加直观些,那么现在该怎么办呢?

 

   8.自定义编辑器这块并像其他已经实现了的编辑器那样可以指定编辑器的属性,自定义编辑器能够指定的就只有一个类名,所以在编辑器上设置参数是没用的,用户无法设置到编辑器中。一个偷巧的方法是在column上做手脚,借鉴其他编辑器的设计思想,在column上添加一个名字为_instant的属性,在代码中通过该属性值来判断是否要立即更新对应的属性值,因此只需要在setValue()方法中添加如下代码,就能够实现实时更新属性值的效果;

 

// 判断列对象是否设置了_instant属性
if (column._instant) {
    var table = self.getMaster();
    table.setValue(self.getData(), column, val);
}

 

   9.至此,编辑器的设计已经完成,现在来看看具体的用法,下面的代码是Table中具体的列定义,在列定义中,指定itemEditor属性值,并设置_instant属性为true,就可以实现编辑器实时更新的效果

 

{
    accessType : 'style',
    name : 'label.rotation',
    editable : true,
    itemEditor : 'ht.widget.RotationEditor',
    _instant : true,
    formatValue : function(value) {
        return Math.round(value / Math.PI * 180);
    }
}

 

代码中你会发现定义了一个formatValue()方法,该方法是为了与编辑器中编辑的值类型一致,都将弧度转换为角度。

在表格的第三列中,通过渲染器自定义了单元格样式,同时我也为其定义了另外一个编辑器,通过左右拖拉单元格来实现角度的变化,这个编辑器的实现与上面谈及的编辑器略有不同,具体的不同之处在于,第三列的编辑器通过HT for Web中定义的ms_listener模块来添加监听,让构造函数与交互分离开,看起来更加清晰明了。

介绍下ms_listener模块,如果类添加了ms_listener模块,那么在类中将会多以下两个方法:

  • addListeners:将类中定义的handle_XXX()方法(XXX代表某个DOM事件名称,如:mousemove等)作为相应的事件监听函数添加到组件的view上;
  • removeListeners:将类中定义的handle_XXX()方法对应的事件从view上移除。

那么类中如何添加ms_listener模块呢,只需要在def()方法中类的方法定义上,添加ms_listener:true这行代码,并在方法定义上添加DOM事件对应的handle函数,再在构造函数中调用类的addListeners()方法。

具体的代码我就不在阐述了,思路与前面讲述的编辑器的思路差不多。

 

最后附上程序的所有代码,供大家参考,有什么问题欢迎留言咨询。

 

2
1
分享到:
评论

相关推荐

    创建JSF自定义组件

    创建JSF自定义组件是一项复杂但强大的任务,它允许开发者根据项目需求定制Web应用的界面和交互。通过理解组件和渲染器的工作原理,以及如何注册和使用它们,开发者可以极大地提升JSF应用的灵活性和可扩展性。

    《JSF自定义组件》

    通过以上内容,我们可以看到,自定义JSF组件不仅涉及到对JSF生命周期的理解,还需要掌握组件、标签和Renderer的概念与实现。自定义组件可以极大地扩展JSF的功能,并提供更加灵活的用户界面设计选项。

    JSF2.0实战 - 4、自定义组件

    自定义组件通常由两部分组成:UI组件(UIComponent)和渲染器(Renderer)。UIComponent定义了组件的行为和属性,而Renderer则负责将组件呈现为HTML或其他客户端格式。在JSF中,自定义组件类需要继承自`javax.faces...

    jsf自定义组件.pdf

    ### JSF自定义组件知识点详解 #### 一、JSF生命周期与组件概述 **1.1 JSF生命周期** JavaServer Faces (JSF) 是一种基于Java的Web应用框架,主要用于构建用户界面。为了更好地理解如何自定义JSF组件,首先需要...

    web下使用itext renderer将html生成pdf

    在Web环境下,使用iText Renderer库将HTML转换为PDF是一种常见的需求,特别是在生成报表、文档或需要离线阅读的场景。iText是一个强大的Java库,主要用于创建和编辑PDF文档,而iText Renderer则是其扩展,专门用于将...

    jsf2.0 自定义组件

    5. **属性和事件处理**:自定义组件可以定义自己的属性,这些属性可以是简单类型,如字符串、整数,也可以是复杂类型,如自定义对象。同时,组件可以监听和处理特定的事件,例如,按钮的点击事件。 6. **组件的生命...

    vxetablevuetable一个功能更加强大的Vue表格组件

    VxeTable是一个基于Vue.js的高性能表格组件,它提供了丰富的功能和高度的可定制性,使得在JavaScript开发中处理数据展示和交互变得更加便捷。VxeTable不仅具备基本的表格操作,如排序、筛选、分页,还支持复杂的表格...

    JTable Model Renderer Editor

    简要的JTable示例,包含了Model,Renderer,Editor。麻雀虽小,五脏俱全!

    LineRendererEditor:Unity 5 LineRenderer组件的自定义编辑器脚本

    总结来说,LineRendererEditor是利用Unity 5的C#和UnityEditor库对LineRenderer组件进行扩展的自定义编辑器脚本,旨在提供更强大、更灵活的线性图形编辑功能。通过自定义编辑器,开发者可以实现动态点管理、可视化...

    Line Renderer) 组件绘制直线,曲线,圆弧

    线渲染器 (Line Renderer) 组件采用 3D 空间中两个或多个点的数组,在每个点之间绘制一条直线。可以使用线渲染器 (Line Renderer) 来绘制从简单直线到复杂螺旋线的任何线条。 这条线始终是连续的;如果需要绘制两条...

    Unity LineRenderer 连线功能示例工程

    5. **材质应用**:LineRenderer可以使用自定义的Shader,通过设置Material属性,可以实现更复杂的渲染效果,如透明度、纹理贴图等。 6. **脚本控制**:通常,我们会使用C#脚本来控制LineRenderer的行为,例如在...

    自定义menuStrip的Renderer

    winform里面自带的menuStrip样式很难看,在网上搜集了很多资料,总算是勉勉强强写了一个,不敢独享,拿上来分享一下。菜鸟之作,还请大家见谅。

    html转pdf的core-renderer.jar支持中文自动换行

    5. **API接口与示例**: 使用`core-renderer.jar`,开发者需要了解其提供的API接口,例如创建转换器对象、设置输入HTML和输出PDF路径、调用转换方法等。通常,官方会提供详细的API文档和示例代码来帮助用户快速上手。...

    HtmlRenderer

    HtmlRenderer 可以下载

    Fast Line Renderer for Unity 1.3.4.zip

    为了解决这一问题,"Fast Line Renderer for Unity 1.3.4.zip" 提供了一个高效、优化的线渲染解决方案,旨在提升游戏和应用的性能。 Fast Line Renderer插件是专为Unity设计的一个GPU加速的线渲染和粒子系统,它...

    Arcgis server for javascript 实例学习 renderer 菜单 数组 select

    在ArcGIS Server for JavaScript API中,Renderer是一种关键的组件,用于定义地图图层上几何对象(如点、线、面)的视觉表示。Renderer决定了如何根据数据值将几何对象渲染成不同颜色、符号或图案,使得地图具有更好...

    基于PixiJS实现的HTML5自定义海报生成功能.zip

    在HTML5的canvas-poster-master项目中,我们可能会看到以下关键组件和流程: 1. **资源加载**:使用PixiJS的`Loader`类加载所需的图片、字体等资源。这一步是预处理,确保在绘制海报时所有素材都已准备就绪。 2. *...

    Fast Line Renderer for Unity 1.4.2

    Fast Line Renderer for Unity - GPU Line and Particle System 1.4.2

    在ht5页面端实现threejs模型环绕观看

    首先,我们要理解HTML5是现代网页开发的核心,它引入了Canvas元素,使得在浏览器中进行2D和3D图形渲染成为可能。Three.js是一个基于WebGL的JavaScript库,它简化了WebGL的复杂性,让开发者能够更容易地创建3D交互式...

    Trail_Renderer.zip

    Unity中的Trail Renderer组件是用于创建动态拖尾效果的工具,广泛应用于游戏开发中的粒子系统、移动物体的轨迹显示,如飞船飞行痕迹、武器射击轨迹等。这个"Trail_Renderer.zip"压缩包显然包含了与Unity Trail ...

Global site tag (gtag.js) - Google Analytics