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

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

アクションを使ったグレーアウト管理

JavaScript

コマンドのグレーアウト機能を作るときに、JavaActionみたいな仕組みがあれば便利そうだな、と思って作ってみました。

Actionってなに?

コマンドを実行した時の処理内容(関数)と、コマンド実行可/不可といったコマンドのプロパティをまとめてオブジェクト化したものです。
UIボタンのグレーアウト機能を作るときに必要な「コマンド実行可/不可」状態はコマンドごとの情報なので、コマンド自体に持たせれば簡潔に管理できるんじゃね?というアプローチ。

戦略

JavaScriptの関数はすでにオブジェクトなので、それに必要な属性や機能を追加する形で実装できます。

  • 具体的には、指定された関数に以下を追加してアクション化します。
    • 実行可/不可の状態
    • 実行可/不可を設定するAPI
    • 実行可/不可を評価するAPI
    • 実行可/不可の状態変更リスナとリスナ登録API
  • アクションが利用不可になった場合、以下の流れでグレーアウト処理が行われます。
    • 1.アクションを利用不可にする。
    • 2.リスナ経由でボタン等のUIに状態を通知
    • 3.UIをグレーアウトする
/**
 * 関数をアクション化する。
 * @param {Function} func アクション化する関数
 */
function createAction( func ) {
    func._listeners = [];
    func._enable = false;
    func.addListener = createAction.prototype.addListener;
    func.setEnable = createAction.prototype.setEnable;
    func.enable = createAction.prototype.enable;
    return func;
}
createAction.prototype = {
    addListener: function( listener ) {
        this._listeners.push(listener);
    },
    setEnable: function( enable ) {
        if( this._enable != enable ) {
            this._enable = enable;
            for(var i=0; i < this._listeners.length; i++ ) {
                this._listeners[i]( this._enable );
            }
        }
    },
    enable: function( ) { this._enable; }
}

使い方例

以下はアクションを使ったグレーアウトのサンプルです。
サンプル

  • メニューをクリックすると★が移動します。
  • 星が画面の端にくると、メニューがグレーアウトします。

実装は次のとおり。

function Service( ) {
  this.x = 2;
  this.y = 2;
  
  // 自身の関数をアクション化して再登録。
  this.left  = createAction( this.left );
  this.right = createAction( this.right );
  this.up    = createAction( this.up );
  this.down  = createAction( this.down );
  
  this.update();
}
Service.prototype = {
    
  left:  function() {
    this.x-=1;
    this.update();
  },
  right:  function() {
    this.x+=1;
    this.update();
  },
  up: function() {
    this.y-=1;
    this.update();
  },
  down: function() {
    this.y+=1;
    this.update();
  },
  
  update: function() {
      
    // ビューを更新
    var str = "<table border='1px'>"
    for ( var i=0;i<5;i++ ) {
        str += "<tr>";
        for ( var j=0;j<5;j++ ) {
            str += "<td width='20px' height='20px'>";
            if ( this.x==j && this.y==i ) {
                str += "★";
            } else {
                str += " ";
            }
            str += "</td>";
        }
        str += "</tr>";
    }
    str += "</table>";
    document.getElementById("table").innerHTML = str;
    
    // 状態の変更に合わせてアクションの実行可/不可を更新
    if ( this.x == 0 ) {
        this.left.setEnable(false);
        this.right.setEnable(true);
    } else if ( this.x == 4 ) {
        this.right.setEnable(false);
        this.left.setEnable(true);
    } else {
        this.right.setEnable(true);
        this.left.setEnable(true);
    }
    if ( this.y == 0 ) {
        this.up.setEnable(false);
        this.down.setEnable(true);
    } else if ( this.y == 4 ) {
        this.up.setEnable(true);
        this.down.setEnable(false);
    } else {
        this.down.setEnable(true);
        this.up.setEnable(true);
    }
  }
}

// メニューを生成
// アクションにリスナを設定して、利用不可になったらメニューをグレーアウトする。
function createMenu( id, service ){
  service[id].addListener( function( enable ) {
    if ( enable ) {
        document.getElementById(id).innerHTML = 
            '<a href="javascript:service.' + id + '();">' + id +'</a>';
    } else {
        document.getElementById(id).innerHTML = 
            '<span style="color:#999999">' + id +'</span>';
    }
  });
}

// サービスとメニューの生成
var service = new Service();
createMenu("left",  service );
createMenu("right", service );
createMenu("up",    service );
createMenu("down",  service );