Handling Menu Click Events

このexampleでは(Exampleのページへのリンクはこちら)、MenuItemへクリックイベントとハンドラを仕掛ける方法が示されている。

これには、addItemsで設定するオブジェクト・リテラル

     onclick: { 
        fn: function,   // クリック時のハンドラ名
        obj: Object1   // ハンドラの引数となるオブジェクト名(option)
        scope: Object2
        // ハンドラのスコープ(option);defaultはイベントを発生させたインスタンス
         } 

を追加すればよい。
よく、「イベントハンドラのスコープ」という言葉がでてくるが、これは「イベントハンドラがどのオブジェクトスコープで動くか」という意味。
たとえば、上の場合、Object2のスコープ内でfunctionが実行されるということは、function内でthisは、Obj2を指すことになる。
このあたりの関数の実行空間scopeがJavascriptの場合分かりにくい。
Javascriptは、(Java屋さんからみると)全てpage scopeで動いているだけと思ってしまう。それにthisといえば、自インスタンスを指すと思ってしまう(Javascriptでも基本的にはそうなんだが)。

JavaScriptでは、page内に階層構造を作って空間を分割しているので、この空間がどういう序列になっているかを意識しないとうまくない。(その意味で、Java屋などには、module patternとその記事はよいと思う)
グローバルとか言われてしまうと、ServerSideのプログラマは、Java屋ならAppliaction scopeを思い出してしまうはずだし、PHP屋ならGlobal変数のことかと思ってしまうだろう。。
まったく、慣れというのは、1つの制約を頭に課してしまう。

基本的に、ハンドラ内では「thisはイベントの発生源」をさし、オブジェクト内であれば「オブジェクトの定義する空間」を指す。したがって、オブジェクト内で(関数定義などで)空間が分割されている場合、その関数内から、上位の空間内にある変数をthis付で呼ぶことはできない。

まえおきが長くなっが、この例の初期画面は以下である。

YUIのExampleでは、クリック時にlogを吐き出すようにコーディングされていたが、alertを上げるように変更した。

以下に、ソースコードの全文を示す。
この中に

   this.cfg.getProperty("text")

というセンテンスがでてくる。thisはMenuItemのインスタンスを表しているのだが、そのまま読むと、

  MenuItemのcfgオブジェクトのgetPropertyメソッドで"text"属性を取り出す

と読める。
このcfgに関する記述が見当たらなかったので、MenuItemのソースを追ってみると、

YAHOO widget.MenuItemにのソースmenuitem.jsのinitメソッドに、、

    this.cfg = new YAHOO.util.Config(this);

と書かれているのがわかるので、実体はYAHOO.util.Configのインスタンスである。YAHOO.util.ConfigのAPIドキュメント(APIドキュメントへのリンクはこちら)を見るとgetPropertyメソッドが書かれている。

継承関係は、

YAHOO.widget.MenuItem <-- YAHOO.widget.ContextMenuItem

となっている。

このcfgという変数は、以下の継承関係の中でも見つけることができる。この場合も最上位のオブジェクトのinitメソッドの中で定義されており、この実体もYAHOO.util.Configのインスタンスである。

YAHOO.widget.Module <-- YAHOO.widget.Overlay
<-- YAHOO.widget.Panel,YAHOO.widget.Menu,YAHOO.widget.Tooltip

分かりにくいコードであることに違いない。

サンプルコードは(いつもどおり)module patternで書き直した。

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<TITLE>Ajax_Sampling</TITLE>
<style type="text/css">
body {
	margin:0;
	padding:0;
}
</style>
<link rel="stylesheet" type="text/css" href="scripts/yui/fonts/fonts-min.css" />
<link rel="stylesheet" type="text/css" href="scripts/yui/menu/assets/skins/sam/menu.css" />
<script type="text/javascript" src="scripts/yui/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="scripts/yui/container/container_core-min.js"></script>
<script type="text/javascript" src="scripts/yui/menu/menu-min.js"></script>

<style type="text/css" id="defaultstyle">
#container {
	margin: 2px;
	padding: 3px;
	width: 400px;
	height: auto;
	border:1px dashed #999999;
}

</style>

<script type="text/javascript">
//モジュールパターンで実装する。
YAHOO.namespace("EGP");

YAHOO.EGP.ScriptMenu = function() {

	var oMenu;
	
	var onMenuClick = function (p_Type, p_Args, p_Value) {
    	oMenu.show();
	};

	// 第1引数は、イベントの種類、第3引数にobjで指定したものが入る。
	var onMenuItemClick = function (p_Type, p_Args, p_Value){
    	// thisはイベントの起こった、MenuItemを意味する。
		alert("index: " + this.index + 
				   //cfgは、cunfiguration parameterを表すオブジェクト。
				   //MenuItemインスタンスのコンフィグレーションから
				   //コンフィグパラメータのtextを取り出すの意味。
	               ", text: " + this.cfg.getProperty("text") + 
	               ", event: " + p_Type + 
	               ", value: " + p_Value);
	};
	
	return{
    	init: function() {
		   // Muneのインスタンス生成
		   // コンストラクタの第1引数は、menuを構成するdivのid(この場合ない)、
		   // 第2引数は、属性を表すオブジェクトリテラル
		   // 属性はMenuのAPIドキュメント
		   // http://developer.yahoo.com/yui/docs/YAHOO.widget.Menu.html
   		
	   		oMenu = new YAHOO.widget.Menu("basicmenu", 
   			   				{ xy: [300,65] }
			);

	        oMenu.addItems([
                  { text: "Item One", 
                    onclick: { 
                 fn: onMenuItemClick  // クリック時のハンドラ 
                    } 
                           },

                  { text: "Item Two", 
                    onclick: { 
                fn: onMenuItemClick, // クリック時のハンドラ 
                obj: "foo"  // ハンドラの引数となるオブジェクト
                    } 
                  },

                  { text: "Item Three", 
                    onclick: { 
                      	fn: onMenuItemClick, // クリック時のハンドラ
                      	obj: ["foo", "bar"]   // ハンドラの引数となるオブジェクト
                        // 第3引数(ハンドラのスコープ:this)はdefaultのMenuItemインスタンス
                    } 
                  }
            ]);

	   // メニュー選択時に,onMenuItemClick処理後にメニューが消えるのを防ぐ。
	   // Menuインスタンスのクリックイベントに、show()で表示させるハンドラを仕込む。
	   oMenu.subscribe("click", onMenuClick);
	   		
	   // 引数はrenderingしたメニューのターゲット
   	   oMenu.render("rendertarget");


	   // Bottunのクリックイベントにメニューの表示(show)を仕掛ける。
	   // EventのAPIはhttp://developer.yahoo.com/yui/docs/YAHOO.util.Event.html
	   // 第4引数は、ハンドラーへの引数、第5引数は実行scope.
   	   YAHOO.util.Event.addListener(
   				"menubutton", 
   				"click", 
   				oMenu.show, 
   				null, 
   				oMenu
   	  );
	}
    };
}();

//DOMが完全にloadされたら、サンプルを初期化する。
YAHOO.util.Event.onDOMReady(
		//DomReadyイベントで発火するハンドラ
		YAHOO.EGP.ScriptMenu.init,
		//ハンドラに渡すオブジェクト(関数)
		YAHOO.EGP.ScriptMenu,
		//ハンドラは、上記のオブジェクトのスコープをもつ。   
		true
);

</script>
</HEAD>

<!-- class=" yui-skin-sam"の指定が必要 -->
<BODY class="yui-skin-sam">
<div id="container">
<p>
ScriptでMenuを作成します。<br/>
メニュー外でのクリックにより、メニューが消えます。<br>
また、メニューの「メニュ−を閉じる」にはurlを記述していません。これを選択してもメニューが消えます。
</p>
<button id="menubutton" type="button">Menuを表示</button>
<div id="rendertarget"></div>
</div>
</BODY>
</HTML>