AsyncLoader, AsyncProxy
Ext JSはツリーにしろグリッドテーブルにしろ、見た目に使えるコンポーネントがそろっていてすばらしいが、イケてないところも多々ある。特に、データ接続の部分。標準で用意されているクラスの実装がURLと直結することを前提としてしまっている。つまり、サーバサイドにこれこれこういうフォーマットでデータを吐くサービスを置いてくれ、と強いている。これは若干うざい。
まあ仕方ないが、そこはクラスを継承して上書き実装してしまうことにする。ツリーの場合は Ext.tree.TreeLoader、グリッド表などのExt.data.Storeを使うコンポーネントの場合は Ext.data.DataProxy を拡張する。
それぞれ、任意の同期/非同期的データソースに接続できるようにしておきたい。
まずはTreeLoaderを拡張して、任意の非同期呼び出しにデータ接続を任せられるようにしたもの(AsyncLoader)。
var AsyncLoader = Ext.extend(Ext.tree.TreeLoader, { /** @private */ dataUrl : '#', // Ext.tree.TreeLoaderの実装でdataUrlの存在をチェックしているため、ダミーで設定。使われない。 /** ここに非同期処理を実装する */ doAsyncLoad : function(node, callback) { alert('you must override doAsyncLoad function!'); callback.onSuccess([]); }, /** @private */ requestData : function(node, callback){ if (this.fireEvent("beforeload", this, node, callback) !== false){ var _this = this; this.doAsyncLoad(node, { onSuccess: function(results) { _this.handleResponse(results, node, callback) }, onFailure: function(error) { _this.handleFailure(error, node, callback) } }) } else { // if the load is cancelled, make sure we notify // the node that we are done if (typeof callback == "function"){ callback(); } } }, /** @private */ handleResponse : function(results, node, callback){ try { node.beginUpdate(); for(var i=0, len=results.length; i<len; i++){ var n = this.createNode(results[i]); if(n){ node.appendChild(n); } } node.endUpdate(); if (typeof callback == "function"){ callback(this, node); } this.fireEvent("load", this, node, results); } catch(e) { this.handleFailure(e); } }, /** @private */ handleFailure : function(error, node, callback){ this.fireEvent("loadexception", this, node, error); if (typeof callback == "function"){ callback(this, node); } } });
若干汚いことをやってしまっているが、これで任意のデータソースからロードができる。継承するクラスではdoAsyncLoad抽象メソッドを実装することになる。
var BinaryLoader = Ext.extend(AsyncLoader, { doAsyncLoad : function(node, callback) { setTimeout(function() { var v = node.attributes.value; // 対象となるツリーノードが含む子ノードのconfig情報を配列にしてコールバック関数に返す callback.onSuccess([{ text : 'node'+(2*v), value : 2*v, }, { text : 'node'+(2*v+1), value : 2*v+1 }]); }, 1000); } }); new Ext.tree.TreePanel({ renderTo : Ext.getBody(), width : 100, height : 200, autoScroll : true, root : new Ext.tree.AsyncTreeNode({ text : 'node1', value : 1 }), loader : new BinaryLoader() });
つづいてDataProxyのAsync版。
var AsyncProxy = function(config) { AsyncProxy.superclass.constructor.call(this); Ext.apply(this, config); } Ext.extend(AsyncProxy, Ext.data.DataProxy, { /** overrided */ load : function(params, reader, callback, scope, arg) { if (this.fireEvent("beforeload", this, params) !== false) { var _this = this; var context = { params : params, reader : reader, callback : callback, scope : scope, arg : arg }; this.doAsyncRequest(params, { onSuccess : function(res){ _this.handleResponse(res, context) }, onFailure : function(error){ _this.handleFailure(error, context) } }); } else { callback.call(scope||this, null, arg, false); } } , /* abstract */ doAsyncRequest : function(params, callback) { alert('you must override doAsyncRequest function!'); callback.onSuccess({}); } , /** @private */ handleResponse : function(res, context) { var result; try { result = context.reader.readRecords(res); } catch (e) { this.fireEvent("loadexception", this, res, context.arg, e); context.callback.call(context.scope, null, context.arg, false); return; } this.fireEvent("load", this, result, context.arg); context.callback.call(context.scope, result, context.arg, true); } , /** @private */ handleFailure : function(error, context) { this.fireEvent("loadexception", this, null, context.arg); context.callback.call(context.scope, null, context.arg, false); } })
今度はdoAsyncRequestという非同期呼び出しが抽象メソッドとして定義されているので、それを実装する。
var TestAsyncProxy = Ext.extend(AsyncProxy, { doAsyncRequest : function(params, callback) { setTimeout(function() { callback.onSuccess([ [1, 'John', 'Due'], [2, 'Jane', 'Due'], [3, 'Lisa', 'Mayer'] ]); }, 1000); } }); new Ext.grid.GridPanel({ renderTo : Ext.getBody(), width : 300, height : 100, store : new Ext.data.Store({ proxy : new TestAsyncProxy(), reader : new Ext.data.ArrayReader({ fields : ['Id','FirstName','LastName'] }) }), columns : [ { header : '#', dataIndex : 'Id'}, { header : 'First Name', dataIndex : 'FirstName'}, { header : 'Last Name', dataIndex : 'LastName'} ], autoScroll : true, loadMask : true, viewConfig : { forceFit : true }, listeners : { render : function(grid) { grid.getStore().load() } } });