搭建Javascript框架(四): 实现简单UI组件-使用观察者事件模式构建Grid表格

    组件是一个可复用的类,UI组件应该把开发人员从组织HTML+CSS中解放出来,让他们只关注驱动UI显示的数据,以及各种业务逻辑,事件驱动等,之前的自建JS类库已经有了基本对象扩展、类扩展、以及最核心的功能:事件监听管理,今天我就来使用这些自建的JS方法来构建一个简单并且可扩展的Grid组件!

    先说说Ext Grid吧,其功能极其强大,有着让人惊艳的UI显示和强大的交互功能,这里要制作的Grid组件当然不会要和Ext比较,况且涉及的知识点也会非常多难度也很大。我只是采用Ext GridView源码中的一种事件代理机制来设计自己的组件,再详细阐述事件代理机制前,我们先搭建Grid组件的基本类结构吧:

 
  
HL.ui.Grid = Class({
body : {},
// 组件初始化方法
init :
function (config){
var id = config.id, // HL.serialID;
container = config.renderer,
cm
= config.cm,
tpl;
this .ds = config.dataSource; // Grid需要展示的数据
HL.Observable(
this ); // 实现观察者事件监听接口
this .renderUI(id, container, cm);
this .initEvent();
},
// 根据数据生成Grid的DOM结构,渲染到页面中
renderUI :
function (id, container, cm){
var i, len, record, c,
body
= [ ' <table class= ' + this .getTableCls() + ' ><thead><tr> ' ];
for (i = 0 , len = cm.length; i < len; i ++ ){
body.push(cm[i]);
}

for (i = 0 , len = this .ds.data.length; i < len; i ++ ){
for (c = 0 ; c < record.length; c ++ ){
body.push(record[c]);
}

}
body.push(
' </tbody></table> ' );
this .body = document.createElement( ' div ' );
this .body.id = id;
this .body.innerHTML = body.join('');
document.getElementById(container).appendChild(
this .body);
},
// 注册Grid的DOM事件
initEvent :
function (){
HL.on(
this .body, ' mousedown ' , this .onMouseDown, this );
HL.on(
this .body, ' click ' , this .onClick, this );
HL.on(
this .body, ' keydown ' , this .onKeyDown, this );
HL.on(
this .body, ' mousemove ' , this .onMouseMove, this );
HL.on(
this .body, ' mouseout ' , this .onMouseOut, this );
},
// 事件代理方法
processHandler :
function (eventName, e){
},
onMouseDown :
function (e){
this .processHandler( ' mousedown ' , e);
if (e.button == 2 ){ // 是否右键,是则触发右键点击事件
this .processHandler( ' contextmenu ' , e);
}
},
onClick :
function (e){
this .processHandler( ' click ' , e);
},
onKeyDown :
function (e){
this .processHandler( ' keydown ' , e);
},
onMouseMove :
function (e){
if ( this .currentEl !== e.getTarget()){
this .processHandler( ' mousemove ' , e);
}
},
onMouseOut :
function (e){
this .clearHoverCls();
}
});

OK,这是Grid的基本类结构,根据之前Class方法,这个自定义类构造器中会调用init方法,所以Grid初始化时依次加载配置参数,实现之前实现的软件驱动事件接口,然后是渲染DOM结构,最后是给Grid注册各种事件监听。renderUI方法这里只写了点象征性的代码,因为构造Grid的DOM结构代码量确实比较大,姑且跳过这块让我先关注Grid的事件机制。

可以看到每个事件监听函数都调用了processHandler原形方法,这里我采用了Ext的设计理念,所有DOM事件都进行委托代理,processHandler是一个代理方法,它负责接收其他事件的类型和Event对象,然后根据触发事件的目标对象来实现更细粒度的操作,这么做的原因是:构造Grid DOM结构时,不可能逐个给每个 td tr 注册事件,效率底下代码不优雅是一方面,注册过多DOM事件影响性能也是一很大问题,下面是processHandler的代码:

 
  
processHandler : function (eventName, e){
var row, cell,
t
= e.getTarget(),
head
= this .getHeadIndex(t);
this .currentEl = t;
if (head !== false ){
this .fire( ' head ' + eventName, head, e);
}
else {
row
= this .getRowIndex(t);
if (row !== false ){
this .fire( ' row ' + eventName, row, e);
cell
= this .getCellIndex(t, row);
if (cell !== false ){
this .fire( ' cell ' + eventName, row, cell, e);
}
}
}
}

target是实际触发事件的DOM对象,我们之前只是给Grid的容器对象注册事件,这里如果能知道Target是什么类型的对象,处于Grid的什么位置,我们就能实现更加复杂的功能。先判断是否是标题head,是则触发head的响应事件,传入列序号和Event对象,这是之前所说的软件驱动事件,关于观察者事件模型再做一个更详细的阐述:观察者模式是一个解耦性极强的设计模式,目标对象只需发布事件通知,其他对象只需监听自己注册的事件,两者之间毫无任何关联,这在功能组合上能实现很强的灵活性;

根据target所处的位置,依次触发标题、行、单元格的各种事件,比如双击某一单元格,则触发cellDbclick事件,执行相关业务逻辑。说完事件代理让我们最后看看如何实现Target的查找定位:

 
  
getHeadIndex : function (t){
var i, len,
h
= HL.dom.findParent(t, ' th ' ),
th
= this .body.querySelectorAll( ' tr th ' );

for (i = 0 , len = th.length; i < len; i ++ ){
if (th[i] === h) {
this .currentHead = h;
return i;
}
}
this .currentHead = {};
return false ;
},
getRow :
function (){
return this .body.querySelectorAll( ' tbody tr. ' + this .getRowCls());
},

getRowIndex :
function (t){
var i, len,
row
= HL.dom.findParent(t, ' tr. ' + this .getRowCls()),
tr
= this .getRow();

for (i = 0 , len = tr.length; i < len; i ++ ){
if (tr[i] === row){
this .currentRow = row;
return i;
}
}
this .currentRow = {};
return false ;
},
getCellIndex :
function (t, r){
var i, len,
cell
= HL.dom.findParent(t, ' td. ' + this .getCellCls()),
tr
= this .getRow(),
td
= tr[r].querySelectorAll( ' td. ' + this .getCellCls());

for (i = 0 , len = td.length; i < len; i ++ ){
if (td[i] === cell){
this .currentCell = cell;
return i;
}
}
this .currentCell = {};
return false ;
},

DOM的遍历又可以单独作为一个话题来讨论,这里就说下HL.dom.findParent 方法,这是根据相应的CSS选择器来查找目标DOM上层父节点,因为grid dom结构可能会更复杂,嵌套关系复杂以后就并不一定是单纯的Tr td元素了,Ext也是采用这种方式定位源节点。

HL.dom.findParent使用了最新支持的JS原生API: querySelector,IE6不支持,关于dom遍历和CSS选择器我打算某天也开一个专门的帖子讨论,先附上源代码

 
  
findParent : function (el, selector, d){
var parent = el.parentNode,
mparent,
depth
= d || 5 ,
nodes, node, i, len;
if ( ! parent){
return el;
}
mparent
= parent.parentNode;
if (document.querySelector){
nodes
= parent.querySelectorAll(selector);
for (i = 0 , len = nodes.length; i < len; i ++ ){
if (nodes[i] === el)
return el;
}
while (depth > 0 && mparent){
nodes
= mparent.querySelectorAll(selector);
for (i = 0 , len = nodes.length; i < len; i ++ ){
if (nodes[i] === parent)
return parent;
}
parent
= mparent;
mparent
= parent.parentNode;
depth
-- ;
}
return false ;
}
else {
return parent;
}
}

逻辑还有待优化,但功能基本没问题,下面来看看测试代码和Grid效果吧

 
  
< script type ="text/javascript" >
HL.load(
' js/base/EventMgr.js ' );
HL.load(
' js/ui/Grid.js ' , function (){
var cm = [ ' id ' , ' name ' , ' age ' , ' sex ' , ' bouns ' ],
ds
= { data: [
[
' 1 ' , ' jill ' , ' 12 ' , ' female ' , ' 20K ' ],
[
' 2 ' , ' snake ' , ' 23 ' , ' male ' , ' 20K ' ],
[
' 3 ' , ' cash ' , ' 15 ' , ' male ' , ' 20K ' ],
[
' 4 ' , ' rose ' , ' 28 ' , ' female ' , ' 25K ' ],
[
' 5 ' , ' mike ' , ' 24 ' , ' male ' , ' 20K ' ],
[
' 6 ' , ' clare ' , ' 23 ' , ' female ' , ' 25K ' ],
[
' 7 ' , ' eve ' , ' 31 ' , ' female ' , ' 14K ' ]
]};
var grid = new HL.ui.Grid(
{id :
' mygrid ' ,
renderer :
' grid ' ,
cm : cm,
dataSource : ds});
});
</ script >
< div id ="grid" ></ div >

哈哈,是不是很想Ext的代码风格??我这里还加入了延迟加载的功能,需要某一组件时再加载它的js文件,下面看看Grid更复杂的功能如何实现的,在initEvent原型方法中加入以下代码:

 
  
this .on( ' cellclick ' , function (r, e){
if ( this .selected !== this .currentCell){
if ( this .selected){
HL.dom.removeClass(
this .selected, this .getSelectCls());
}
this .selected = this .currentCell;
HL.dom.addClass(
this .currentCell, this .getSelectCls());
}
});
this .on( ' rowmousemove ' , function (r, e){
this .clearHoverCls();
HL.dom.addClass(
this .currentRow, this .getHoverCls());
});
this .on( ' headmousemove ' , function (r, e){
this .clearHoverCls();
HL.dom.addClass(
this .currentHead, this .getHoverCls());
});

分别注册cellclick、rowmousemove、headmousemove 这些事件,来改变具体DOM元素的样式,实现鼠标滑过变色以及点击单元格变灰的效果。看基于观察者事件机制,增加复杂功能是不是很轻松啊!!

相关联的代码确实非常多,Grid源代码也有部分没有上传,也是怕大家看多了看烦了呵呵,有需要源代码的可以给我留言,我单独发,好了今天的UI组件之旅就到这里,最后附上Grid实际效果图:

2011051517102370.jpg

转载于:https://www.cnblogs.com/hlissnake/archive/2011/05/15/2046664.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值