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() }
  }
});