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()方法。

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

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

TabelRendererEditor.zip

扩展HT for Web之HTML5表格组件的Renderer和Editor的更多相关文章

  1. HT for Web的HTML5树组件延迟加载技术实现

    HT for Web的HTML5树组件有延迟加载的功能,这个功能对于那些需要从服务器读取具有层级依赖关系数据时非常有用,需要获取数据的时候再向服务器发起请求,这样可减轻服务器压力,同时也减少了浏览器的 ...

  2. HT for Web基于HTML5的图像操作(二)

    上篇介绍了HT for Web采用HTML5 Canvas的getImageData和setImageData函数,通过颜色乘积实现的染色效果,本文将再次介绍另一种更为高效的实现方式,当然要实现的功能 ...

  3. HT for Web基于HTML5的图像操作(三)

    上篇采用了HTML5的Canvas的globalCompositeOperation属性达到了染色效果,其实CSS也提供了一些常规图像变化的设置参数,关于CSS的过滤器Filter设置可参考 http ...

  4. HT for Web基于HTML5的图像操作(一)

    HT for Web独创的矢量图片设计架构,使其具有强大丰富的动态图形呈现能力,但从最近知乎热议的“Adobe Photoshop 是否已经过时?”的话题,大家能体会到很多情况下实际项目不可能完全采用 ...

  5. 基于HT for Web矢量实现HTML5文件上传进度条

    在HTML中,在文件上传的过程中,很多情况都是没有任何的提示,这在体验上很不好,用户都不知道到时有没有在上传.上传成功了没有,所以今天给大家介绍的内容是通过HT for Web矢量来实现HTML5文件 ...

  6. 基于HTML5树组件延迟加载技术实现

    HT for Web的HTML5树组件有延迟加载的功能,这个功能对于那些需要从服务器读取具有层级依赖关系数据时非常有用,需要获取数据的时候再向服务器发起请求,这样可减轻服务器压力,同时也减少了浏览器的 ...

  7. HT For Web 拓扑图背景设置

    HT For Web 的HTML5拓扑图组件graphView背景设置有多种途径可选择: divBackground:通过css设置graphView对应的div背景 Painter:通过graphV ...

  8. 百度地图、ECharts整合HT for Web网络拓扑图应用

    前一篇谈及到了ECharts整合HT for Web的网络拓扑图应用,后来在ECharts的Demo中看到了有关空气质量的相关报表应用,就想将百度地图.ECharts和HT for Web三者结合起来 ...

  9. ECharts+BaiduMap+HT for Web网络拓扑图应用

    前一篇谈及到了ECharts整合HT for Web的网络拓扑图应用,后来在ECharts的Demo中看到了有关空气质量的相关报表应用,就想将百度地图.ECharts和HT for Web三者结合起来 ...

随机推荐

  1. RCP:解决Navigator快捷键不生效的问题

    自己扩展CNF之后,导航栏的删除.复制.黏贴等快捷键失效了,在网上搜索了半天,结果最终不如自己看源码. 本篇文章的主要目的不止于解决快捷键失效,更在于如何处理类似的问题,如何利用debug快速定位.这 ...

  2. node(thrift)

    thrift是一种跨语言的RPC框架,据说uber采在node.js项目中采用thrfit后,比原有的http+json的方式提高近20倍的性能. 所谓的RPC本质上就是客户端将需要调用的方法名和参数 ...

  3. 分享一个简单程序(webApi+castle+Automapper+Ef+angular)

    前段时间在周末给朋友做了一个小程序,用来记录他们单位的一些调度信息(免费,无版权问题).把代码分享出来.整个程序没有做任何架构.但是麻雀虽小,用到的技术也没少.WebApi+Castle+AutoMa ...

  4. 单一职责原则(Single Responsibility Principle)

    单一职责原则(SRP:The Single Responsibility Principle) 一个类应该有且只有一个变化的原因. There should never be more than on ...

  5. Aoite 系列(04) - 强劲的 CommandModel 开发模式(上篇)

    Aoite 是一个适于任何 .Net Framework 4.0+ 项目的快速开发整体解决方案.Aoite.CommandModel 是一种开发模式,我把它成为"命令模型",这是一 ...

  6. JavaScript开发原生App模式能否突出重围?

    移动应用制作的第三方服务市场已经被瓜分得差不多了,对于刚起步的中小企业来说,这些公司的 IT 部门人员比较熟悉的是 Appcan ,但随着互联网公司对 App 开发的需求持续升温,也有不少后来的闯入者 ...

  7. [蓝牙] 2、蓝牙BLE协议及架构浅析&&基于广播超时待机说广播事件

    第一章 BLE基本概念了解 一.蓝牙4.0和BLE区别   蓝牙4.0是一种应用非常广泛.基于2.4G射频的低功耗无线通讯技术.蓝牙低功耗(Bluetooth Low Energy ),人们又常称之为 ...

  8. [Unity3D]做个小Demo学习Input.touches

    [Unity3D]做个小Demo学习Input.touches 学不如做,下面用一个简单的Demo展示的Input.touches各项字段,有图有真相. 本项目已发布到Github,地址在(https ...

  9. common-dbcp2数据库连接池参数说明

    参数 默认值 描述 建议值 DefaultAutoCommit  null 通过这个池创建连接的默认自动提交状态.如果不设置,则setAutoCommit 方法将不被调用.  true Default ...

  10. 初识jsonp

    jsonp 全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案.很多时候我们需要在客户端获取服务器数据进行操作,一般我们会使用ajax+webservice做此事,但是如 ...