在gridx的列定义中,一个decorator方法可以被用来在cell中显示任何HTML/CSS. 但是有时候这稍显不够。当cell中需要放入复杂的控件时,从decorator方法中返回的纯string不再有效。所以gridx/modules/CellWidget模块在这是显示了作用。
CellWidget模块通过grid的body模块中的onAfterRow事件将widget附加到cell节点中。其中的关键是如何才能高效地完成这个工作。
Grid的body随着时间不断刷新,刷新的原因可以是排序,过滤,分页,虚拟滚动,改变值,展开树节点,等等。每次body被刷新,行被重新,并且onAfterRow事件将会被处罚。如果我们总是在onAfterRow事件中创建新的widget,那将会非常浪费。如果你点击header两次为了要是的一列降序排序,许多widget将会被立刻摧毁。所以一些缓存机制必须被建立。第一个方法是为在每个cell中使用一个widget,无论被刷新多少次。这解决了一些刷新问题,但是如果我们有100000行,这仍然不高效。试想如果一个用户慢慢地滚动这个巨大的grid来看每一行,最终将会有多少widget被创建!
所以CellWidget的目的是创建尽可能少的控件。方法是使用set('value',...)方法在row之间重用控件。
在控件上设置值通常比创建一个新控件快。这是一个好方法,但是需要用户意识到重用这件事情。让我们首先看一下如何使用CellWidget模块:
var grid = new Grid({ cacheClass: 'gridx/core/model/cache/Async', store: someStore, structure: [ { id: 'progress', field: 'progress', name: 'Install Progress', widgetsInCell: true, decorator: function(){ return "<div data-dojo-type='dijit.ProgressBar' data-dojo-props='maximum: 1' " + "class='gridxHasGridCellValue' style='width: 100%;'></div>"; } } ], modules: [ "gridx/modules/CellWidget" ] });
这里你将看到第一个小技巧:decorator并没有任何的参数。这并不是当widgetsInCell 为false的情况。通常当前的cell数据会被传入,和row Id和Index一起。如果某些特定的cell数据被传入,我们将如何重用他们?所以这个template string不能包含任何特定的行信息。在上面的示例中,一个dijit/ProgressBar被放进template中,他的参数可以在data-dojo-props中被加入。注意这个gridxhasGridCellValue class,这是关于widgets被重用的第二个技巧。CellWidget模块将会在渲染每一行时自动查找所有含有这个class的widget,并且调用set('value')在widget中设置合适的值。
{ id: 'progress', field: 'progress', name: 'Install Progress', widgetsInCell: true, decorator: function(){ return "<div data-dojo-type='dijit.ProgressBar' data-dojo-props='maximum: 1' " + "data-dojo-attach-point='progBar' style='width: 100%;'></div>"; }, setCellValue: function(gridData, storeData, cellWidget){ var data = doSomethingIntersting(gridData); cellWidget.progBar.set('value', data); // cellWidget.cell give you full access to everything you want. var rowIndex = cellWidget.cell.row.index(); } }
setCellValue方法会在每一行被render的时候被调用。当这个方法被调用时,widget已经被创建并且你能完全控制widget。你可以在widgets上设置值,改变css,或者操纵dom节点并且添加事件。这个方法中的第三个参数'cellWidget'引用了cell widget本身,这是拥有从'decorater'方法中返回的template string的widget,所以你可以访问任何的在控件中定义的“dojo attach point”。第一和第二个参数是当前cell的grid data与store data。他们只有当'formatter'方法被提供时才会有差别。你可以通过cellWidget.cell来获取当前的cell,从中你可以获取任何你需要的东西。
如何处理widget事件
但是请记住widgets("cell widget"作为一个整体)是在不同的行之间被重用。所以如果你在setCellValue方法中绑定了一些事件或者改变了一些dom节点的时候,这些改变将会在被其他行重用时被保留。如果你一直连接事件而不断开事件,将会有巨大的内存泄漏风险。所以合适的方法是在setCellValue中连接并且断开事件。例如
{ id: 'progress', field: 'progress', name: 'Install Progress', widgetsInCell: true, decorator: function(){ return "<button data-dojo-type='dijit.form.Button' data-dojo-attach-point='btn'></button>"; }, setCellValue: function(gridData, storeData, cellWidget){ cellWidget.btn.set('label', gridData); if(cellWidget.btn._cnnt){ // Remove previously connected events to avoid memory leak. cellWidget.btn._cnnt.remove(); } cellWidget.btn._cnnt = dojo.connect(cellWidget.btn, 'onClick', function(e){ alert(gridData); // do your job here...... }); } }
我承认,这看起来不怎么直观。所以在gridx 1.2中一个新的方法(回调)在会被引入以使得这个工作更加简便:
{ id: 'progress', field: 'progress', name: 'Install Progress', widgetsInCell: true, decorator: function(){ return "<button data-dojo-type='dijit.form.Button' data-dojo-attach-point='btn'></button>"; }, setCellValue: function(gridData, storeData, cellWidget){ cellWidget.btn.set('label', gridData); }, getCellWidgetConnects: function(cellWidget, cell){ // return an array of connection arguments return [ [cellWidget.btn, 'onClick', function(e){ alert(cell.data()); // do your job here..... }] ]; } }
有了这个getCellWidgetConnects,gridx可以为你管理事件链接(句柄)。连接和断开都会被自动执行。
其他高级的回调函数
初次之外,gridx 1.2添加了另外2个方法来为你的工作提供更加有意义的命名:
{ id: 'progress', field: 'progress', name: 'Install Progress', widgetsInCell: true, decorator: function(){ return "<button data-dojo-type='dijit.form.Button' data-dojo-attach-point='btn'></button>"; }, setCellValue: function(gridData, storeData, cellWidget){ cellWidget.btn.set('label', gridData); }, getCellWidgetConnects: function(cellWidget, cell){ // return an array of connection arguments return [ [cellWidget.btn, 'onClick', function(e){ alert(cell.data()); // do your job here..... }] ]; }, initializeCellWidget: function(cellWidget, cell){ // create extra widgets or manipulate dom nodes that depends on current cell context. cellWidget.anotherButton = new Button({...}); cellWidget.domNode.append(cellWidget.anotherButton.domNode); }, uninitializeCellWidget: function(cellWidget, cell){ // don't forget to undo the changes you made in initializeCellWidget, so that it can be reused among different rows. cellWidget.anotherButton.destroy(); } }
如你所见,事实上你可以在setCellValue方法中做这些事情。这个新方法只是提供了更多的语义,使你的代码更加易于阅读并且省去了你添加额外的注释的功夫。
如果你曾经用过DataGrid,你可能会对于在formatter方法中返回widget的方式感到熟悉,并且对于写template string的方式感到变扭。因此你可以使用onCellWidgetCreated事件,甚至省略“decorator”方法(gridx 1.2之后):
{ id: 'progress', field: 'progress', name: 'Install Progress', widgetsInCell: true, onCellWidgetCreated: function(cellWidget, column){ var btn = new Button({...}); btn.placeAt(cellWidget.domNode); } }
以下是如何在gridx的cell中显示widget的结论: