読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

リスナ登録ユーティリティ for ContainerJS 0.3.0

昔書いたリスナ登録ユーティリティContainerJS version 0.3.0にあわせて修正。Typeを使ってちょっと短く書けるようになってます。

サンプル

簡単に解説。

  • モデル、ビュー、コントローラからなる簡単なプログラムです。
  • ビュー→モデルの依存でオブザーバーパターンを採用。リスナ登録ユーティリティを使ってリスナ(オブザーバー)をモデルに関連付けています。
// モデル / 状態を保持するだけのオブジェクト
function Kitten( ) {
  this.state = "stop";
}

// コントローラー
// API呼び出しを受けてモデルを更新する。
function KittenService( kitten ) {
  this.kitten = kitten;
}
KittenService.prototype = {
  run:  function() {
    this.kitten.set("state", "running"); // set関数で更新するルール
  },
  stop: function() {
    this.kitten.set("state", "stop");
  },
  jump: function() {
    this.kitten.set("state", "jump!");
  }
}

// ビュー / モデルの更新を受けてUIを更新する。
function KittenStateView( elm ) {
  this.elm = elm;
}
KittenStateView.prototype = {
  // 状態変更ハンドラ
  onStateChanged: function( e ) {
      document.getElementById( this.elm ).innerHTML = e.value;
  }
}

// コンテナ
var c = new container.Container( function ( binder ) {
    // モデル
    binder.bind( Kitten ).to( "model" );
    // コントローラ
    binder.bind( KittenService ).to( "ctrl" ).inject( {
        "kitten":container.component("model")
    } );
    // ビュー
    binder.bind( KittenStateView ).inject( {"elm":"stdout1"} );
    binder.bind( KittenStateView ).inject( {"elm":"stdout2"} );
    binder.bind( KittenStateView ).inject( {"elm":"stdout3"} );

    // リスナ登録ユーティリティ / EagerSingletonなので登録するだけでOK
    binder.bind( container.utils.ListenerBinder ).inject({
        // 監査対象とリスナを指定
        // "model"コンポーネントの"state"の変更を"onStateChanged"を持つコンポーネントに通知する。
        recipe : { "model#state" : "onStateChanged" }
    });
});

// コントローラを作成。
var service = c.get("ctrl");
service.run(); // モデルの更新を受けてビューが更新される。

動作サンプルはこちら

実装

ユーティリティの実装は次の通りです。

if(typeof container=="undefined"){
    container={}
}
if(typeof container.utils=="undefined"){
    container.utils={}
}


/**
 * コンポーネントのフィールドの変更を捕捉するリスナを設定する。
 */
container.utils.ListenerBinder = function() {
    this.recipe = {}; // @Inject
}
container.utils.ListenerBinder.prototype = {
    meta: {
      "@Container": {
          "@Scope": container.Scope.EagerSingleton,
          "@Initialize": function( binder, container ) { binder.bind( container ); }
      }
    },

    /**
     * コンポーネント定義を元にモデルのリスナを設定する。
     * @param {Object} c コンテナ
     */
    bind: function( c ) {
        for ( var i in this.recipe ) {
            if ( typeof i == "function" ) { continue; }
            var result = new RegExp( "(.*)#(.*)$").exec( i );
            if ( result && result.length >= 3 ) {
                var fn = this.recipe[i];
                var component = c.get( result[1] );
                var listeners = c.gets( container.types.has( fn ) );
                if ( component && listeners && result[2]) {
                    this.addListeners( component, result[2], listeners, fn );
                }
            }
        }
    },

    /**
     * 更新リスナを追加する。
     * @param {Object} target リスナを追加するモデル
     * @param {String} key イベントキー
     * @param {Function} listener リスナ関数の配列
     * @param {String} functionName 関数名
     */
    addListeners: function ( target, key, listeners, functionName ) {
      if ( !target.__listeners ) {

        // ターゲットを拡張する。
        target.__listeners = {}; // リスナの記録先を確保。
        var org = target.set;

        // setを上書き。変更を受けてリスナをキックする関数にする。
        target.set = function( key, value, opt ) {
            opt = opt ? opt : {};
            opt.value = value;
            var result = null;
            if ( org ) {
                result = org();
            } else {
                this[key] = value;
            }
            if ( this.__listeners[key] ) {
                var fn = this.__listeners[key]["function"];
                for (var i=0; i < this.__listeners[key]["lis"].length; i++ ) {
                    this.__listeners[key]["lis"][i][fn]( opt );
                }
            }
            return result;
        }
      }
      // リスナをターゲットの属性として追加。
      target.__listeners[key] = {"lis":listeners, "function": functionName};
    }
}

Listenerの綴り間違いは、さりげなく直しておきました。

それにしても

しばらくさわってないと見事に忘れてる・・orz。container.typesとか忘却の彼方。やばし。