Menu Family: Context Menu

Yahoo.widget.ContextMenuは、Yahoo.widget.Menuを継承している。
このExample(Exampleへのリンクはこちら)は、2つのContextMenuを含んでいる。

まず、初期画面は以下。

クローン羊のドリーの写真が現れる。これをクリックするとWikipediaへ_selfで飛ぶようになっている。以下は、このドリーの写真にマウスオーバーして、右クリックをした時の画面。ここで現れるのが1つ目のContextMenuである。このContextメニューでは、写真のタイトルの変更、クローン(コピー)の作成、削除ができるようになっている。

また、ドリーの背景になっている緑の部分で右クリックしたときの画面が以下である。
ここで現れるのが2つめのContextメニューである。このメニューからは、背景色の選択、クローン)コピー)の作成、全ての写真の削除、といったアクションを行うことができる。

Markup上では、背景はulエレメント、写真はliエレメントとして、定義されている。2つのContextMenuもそれぞれのエレメントと紐付けがなされている。この紐付けは、ContextMenuのインスタンス生成時のtrigger属性によって定義されており、クリックされた領域(ulとli)により現れるメニューが選択される。

以下にJavaScriptを含むhtmlの全文を示す。
このサンプルのコードは、YUIのExampleのコードから殆ど変化させていない。
これは、Exampleのコードにthisオブジェクトが多用されており、module patternのようなscopeを変化させる改変を加えると、(スコープを明らかにして)動作させるために相当の改変が必要となってしまうためである。
以下のソースでは、見やすいようにコードの順番を整理し、注釈を入れるに止めた。
2つのContextMenuでトリガーのかけ方が違っている(写真に仕掛けられたメニューでは、ハンドラーで一度、Dispatchが行われている)のがわかる。
また、このメニューを実現するために、相当量のDOMプログラミングが必要であることがわかる。

<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/reset/reset.css" />
<link rel="stylesheet" type="text/css" href="scripts/yui/menu/assets/skins/sam/menu.css" />

<script type="text/javascript" src="scripts/yui/yahoo/yahoo-min.js"></script>
<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">

h1 { 
	font-weight: bold; 
	margin: 0 0 1em 0;
}

body {
	padding: 1em;
}

p, ul {
	margin: 1em 0;
}

p em,
#operainstructions li em {
	font-weight: bold;
}

#operainstructions {
	list-style-type: square;
	margin-left: 2em;
}

#clones {
	background: #99cc66 url(http://developer.yahoo.com/yui/examples/menu/assets/grass.png);
   /* Hide the alpha PNG from IE 6 */
   _background-image: none;
	width: 450px;
	height: 400px;
	overflow: auto;
}
            
#clones li {
	float: left;
	display: inline;
	border: solid 1px #000;
    background-color: #fff;
	margin: 10px;
    text-align: center;
    zoom: 1;
            }

#clones li img {
	border: solid 1px #000;
	margin: 5px;
            }
            
#clones li cite {
	display: block;
	text-align: center;
    margin: 0 0 5px 0;
    padding: 0 5px;
            }

</style>

<script type="text/javascript">

// clone readyで発動
YAHOO.util.Event.onContentReady("clones", function () {

	var Dom = YAHOO.util.Dom;
	
	// Maintain a reference to the "clones" <ul>
   	//var oClones = this;
   	// スコープをはっきりさせる。
   	var oClones = Dom.get('clones');

	// 最初(1つめの)のeweの取得
   	var oLI = oClones.getElementsByTagName("li")[0];
   	// クローンの保管
   	var oEweTemplate = oLI.cloneNode(true);

	//=============== ewe コンテキストメニューの処理 ====================//

	// ewe コンテキストメニューのラベル
	var aMenuItems = ["Edit Name", "Clone", "Delete" ]; 

	//----- ewe コンテキストメニューのハンドラー  -----//
	
   	// 写真のrename
   	// p_oLIは右クリックされたli
   	function editEweName(p_oLI) {
   	   	var oCite = p_oLI.lastChild;
   	   		// ELEMENT_NODEでなければ、前の兄弟ノード
	       	if (oCite.nodeType != 1) {
	           	oCite = oCite.previousSibling;
           	}

   	   		// テキストの取得
	       	var oTextNode = oCite.firstChild;
           	var sName = window.prompt("新しい名前を入力 ", 
	                        oTextNode.nodeValue);
           	if (sName && sName.length > 0) {
                oTextNode.nodeValue = sName;
            }
   	}
	        

	// 写真のクローン
   	// p_oLIは右クリックされたli,p_oMenuはコンテキストメニュー
	function cloneEwe(p_oLI, p_oMenu) {
		// cloneの作成
		var oClone = p_oLI.cloneNode(true);
		p_oLI.parentNode.appendChild(oClone);
		// コンテキストメニューのtrggerにクローンの子要素(テキスト)を追加。
		p_oMenu.cfg.setProperty("trigger", oClones.childNodes);
    }

	// 写真の削除
   	// p_oLIは右クリックされたli
    function deleteEwe(p_oLI) {
		var oUL = p_oLI.parentNode;
	    oUL.removeChild(p_oLI);
	}
	
	// eweメニューの要素がクリックされたときのハンドラー        
	// "click" event handler for each item in the ewe context menu
	function onEweContextMenuClick(p_sType, p_aArgs) {
  		// p-aArgsの第2引数は、clickイベントが起こったMenuItemインスタンス。
		var oItem = p_aArgs[1], // The MenuItem that was clicked
		// thisはContextMenuインスタンス
		// ContextMenuを表示させるtargetとなるHTMLエレメント
		oTarget = this.contextEventTarget,
	    oLI;

        if (oItem) {
			oLI = oTarget.nodeName.toUpperCase() == "LI" ? 
					oTarget : YAHOO.util.Dom.getAncestorByTagName(oTarget, "LI");

        	switch (oItem.index) {
        		case 0:     // Edit name
					editEweName(oLI);
					break;
                case 1:     // Clone
                    cloneEwe(oLI, this);
        		    break;
                case 2:     // Delete
	                deleteEwe(oLI);
                    break;                    
           }
		}
	}

	// ContextMenuのインスタンス化
   	// triggerはエレメントで、そのcontextmenuイベントがCOntextMenuインスタンス
   	// を画面に出すもの。下の場合は、oClone(ul)の子ノード(li)
	var oEweContextMenu = new YAHOO.widget.ContextMenu(
	                   "ewecontextmenu", 
	                   {
	                    trigger: oClones.childNodes,
	                    itemdata: aMenuItems,
	                    lazyload: true                                    
	                    } 
	);

	// イベントハンドラー
    function onContextMenuRender(p_sType, p_aArgs) {
        // thisはContext Menuのインスタンス
        //  Add a "click" event handler to the ewe context menu
		this.subscribe("click", onEweContextMenuClick);
	}

    // eweメニューのrenderingイベントに、上のクリック時のハンドラーを仕掛ける。 
    oEweContextMenu.subscribe("render", onContextMenuRender);

    
	//=============== field コンテキストメニューの処理 ====================//

	// field コンテキストメニューのラベル
	var oFieldContextMenuItemData = [
		{
	    	text: "Field color", 
	        submenu: { 
	        	id: "fieldcolors", 
	       		itemdata: [
	            	{ text: "Light Green", onclick: { fn: setFieldColor, obj: "#99cc66" }, checked: true }, 
	            	{ text: "Medium Green", onclick: { fn: setFieldColor, obj: "#669933" } }, 
	            	{ text: "Dark Green", onclick: { fn: setFieldColor, obj: "#336600" } }
	            ] 
	       	} 
       	},
       	{ text: "Delete all", onclick: { fn: deleteEwes } },
       	{ text: "New Ewe", onclick: { fn: createNewEwe } }
	];

	//----- field コンテキストメニューのハンドラー  -----//
    
    // 全て削除
    function deleteEwes() {

    	oEweContextMenu.cfg.setProperty("target", null);
	    oClones.innerHTML = "";

	    	function onHide(p_sType, p_aArgs, p_oItem) {
	        	p_oItem.cfg.setProperty("disabled", true);
	       		p_oItem.parent.unsubscribe("hide", onHide, p_oItem);
            }

	    	this.parent.subscribe("hide", onHide, this);
    }

    // 写真の追加
	function createNewEwe() {

	    // 最初にとったクローンから複製を作る。
		var oLI = oEweTemplate.cloneNode(true);
	    oClones.appendChild(oLI);
		// thisはContextMenu.
	    this.parent.getItem(1).cfg.setProperty("disabled", false);

	    oEweContextMenu.cfg.setProperty("trigger", oClones.childNodes);
	        
	}

	// フィールドカラーの変更
	function setFieldColor(p_sType, p_aArgs, p_sColor) {
		// thisはクリックされたMenuItem。
		
		// チェック済みのMenuItemを拾う。
	    var oCheckedItem = this.parent.checkedItem;

	    // チェック済みのMenuItemとクリックされたMenuItemが違ったら、
	    // バックグラウンドの色を変える。 
	    if (oCheckedItem != this) {
	    	YAHOO.util.Dom.setStyle("clones", "backgroundColor", p_sColor);
            this.cfg.setProperty("checked", true);
            oCheckedItem.cfg.setProperty("checked", false);
            this.parent.checkedItem = this;
        }
	}

   	// fiels コンテキストメニュークリック時のハンドラー
	function onFieldMenuRender(p_sType, p_aArgs) {
		if (this.parent) {  // submenu
	    this.checkedItem = this.getItem(0);
		}
	}

   	// コンテキストメニューのインスタンス化
   	// triggerはエレメントで、そのcontextmenuイベントがCOntextMenuインスタンス
   	// を画面に出すもの。下の場合は、id=cloneのエレメント(UL)。
	var oFieldContextMenu = new YAHOO.widget.ContextMenu(
				"fieldcontextmenu",
				{
					trigger: "clones",
					itemdata: oFieldContextMenuItemData,
					lazyload: true
				}
	);

	// renderメソッドにハンドラを仕掛ける。
	oFieldContextMenu.subscribe("render", onFieldMenuRender);

});

</script>
</HEAD>

<!-- class=" yui-skin-sam"の指定が必要 -->
<BODY class="yui-skin-sam">
<ul id="clones">
	<li><a href="http://en.wikipedia.org/wiki/Dolly_%28\\clone%29">
	    <img src="http://developer.yahoo.com/yui/examples/menu/assets/dolly.jpg" width="100" height="100" 
	    alt="ドリー(雌ヒツジ)は大人の細胞から始めてクローンに成功した哺乳類です。">
	    </a>
	    <cite>ドリー</cite>
	</li>
</ul>
</BODY>
</HTML>