DD; Using Interaction Groups

2009/6/7 tetsuya_odaka js中のコメントの修正

===================================================
このInteraction Groupを用いたExampleも、さきのReordering a Listと同じくタフなExample。
解析してソースに注釈をいれるのに1時間半もかかってしまった。

画面は以下のようになっていて、画面上部にSlotと呼ぶDDTarget、下部にはPlayerと呼ぶDDProxyを拡張したDDPlayerというオブジェクトを配置するようになっている。
DDTargetとDDPlayerは、Interaction Groupに分けられていて、同一のGroupに属するSlotにしか、Playerを配置できない。同一GroupのSlot上でPlayerをDropすれば、そこに配置されるが、間違うとスタート地点に戻ってしまう。
また、SlotにPlayerが収まっていても、あらたなPlayerで上書きできるようになっている。
画面上部には、interactionモードを選択できるList Boxがあって、Point(デフォルト)とIntersectが選べる。

ソースコードは比較的平易に書かれており、YAHOO.util.DragFropMgrが、ヘルパーとしての多くの機能をもっていることが理解できる(APIドキュメントはこちら)。
面倒なのは、上の青字で書いた部分回りの処理で、そのためのフラグ類が読みづらい。
(そのため、1文だけ、コードを追加した)

以下にhtmlの全文を載せる。

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<META http-equiv="Content-Style-Type" content="text/css">
<TITLE>Ajax_Sampling</TITLE>
<style type="text/css"> 
.slot { border:2px solid #aaaaaa; background-color:#dddddd;
 color:#666666; text-align:center; position: absolute; width:60px; height:60px; }
.player { border:2px solid #bbbbbb; color:#eeeeee; 
text-align:center; position: absolute; width:60px; height:60px; }
.target { border:2px solid #574188; background-color:#cccccc; 
text-align:center; position: absolute; width:60px; height:60px; }

#t1 { left: 10px; top: 0px; }
#t2 { left: 378px; top: 0px; }
#b1 { left: 84px; top: 50px; }
#b2 { left: 158px; top: 50px; }
#b3 { left: 232px; top: 50px; }
#b4 { left: 306px; top: 50px; }

#pt1 { background-color:#7E695E; left: 84px; top: 150px; }
#pt2 { background-color:#7E695E; left: 84px; top: 230px; }
#pb1 { background-color:#416153; left: 195px; top: 150px; }
#pb2 { background-color:#416153; left: 195px; top: 230px; }
#pboth1 { background-color:#552E37; left: 306px; top: 150px; }
#pboth2 { background-color:#552E37; left: 306px; top: 230px; }

#usercontrols {
    top: -36px;
}

#workarea {
    position: relative;
    height: 300px;
}

</style>

<!-- 読み込むjs --> 
<script type="text/javascript" src="scripts/yui/yahoo-dom-event/yahoo-dom-event.js" >
</script> 
<script type="text/javascript" src="scripts/yui/dragdrop/dragdrop-min.js" >
</script> 

<script type="text/javascript">

// YAHOO.examplr.DDPlayerのコンストラクタの定義
YAHOO.example.DDPlayer = function(id, sGroup, config) {
	// superclassのコンストラクタの呼び出し
	YAHOO.example.DDPlayer.superclass.constructor.apply(this, arguments);
	// initPlayerメソッドの呼び出し
    this.initPlayer(id, sGroup, config);
};

//YAHOO.util.DDProxyを継承してDDPlayerを作成する。
YAHOO.extend(YAHOO.example.DDPlayer, YAHOO.util.DDProxy, {

    TYPE: "DDPlayer",

    // Playerの初期化
    initPlayer: function(id, sGroup, config) {
		// idがnullならイニシャライズできない。
		if (!id) { 
            return; 
        }

	// proxyを取得(getDragEl()) 
        var el = this.getDragEl()
        YAHOO.util.Dom.setStyle(el, "borderColor", "transparent");
        YAHOO.util.Dom.setStyle(el, "opacity", 0.76);

        // Targetではない。
        this.isTarget = false;

        // styleの退避領域
        this.originalStyles = [];

        this.type = YAHOO.example.DDPlayer.TYPE;

    // DropしたTargetオブジェクトを入れる
        this.slot = null;

        // DropしたPlayerをいれる。
        this.player = null;

	// 移動する前のPlayer要素(getEl())の位置を保持しておく。 
        this.startPos = YAHOO.util.Dom.getXY( this.getEl() );
    },

    /**
    * startDragのoverride
    * 
    */
    startDrag: function(x, y) {
        var Dom = YAHOO.util.Dom;

        // proxyを取得
        var dragEl = this.getDragEl();

        // クリックされたPlayer要素そのもの
        var clickEl = this.getEl();

        // proxy要素に、Player要素のinnerHTMLとclass名(style)をコピー
        dragEl.innerHTML = clickEl.innerHTML;
        dragEl.className = clickEl.className;

        // proxy要素のcolor属性、backgroundColor属性を、Player要素からコピー
        Dom.setStyle(dragEl, "color",  Dom.getStyle(clickEl, "color"));
        Dom.setStyle(dragEl, "backgroundColor", 
                Dom.getStyle(clickEl, "backgroundColor"));

        // Player要素の透過度を0.1まで下げる。
        Dom.setStyle(clickEl, "opacity", 0.1);

	// Player要素と同一のDDグループのTargetオブジェクトをDDMより取得する。
	// 第2引数=trueでターゲットだけ取得できる。
        var targets = YAHOO.util.DDM.getRelated(this, true);
//        YAHOO.log(targets.length + " targets", "info", "example");
        for (var i=0; i<targets.length; i++) {
            
            var targetEl = this.getTargetDomRef(targets[i]);

            // このPlayer(this)にターゲットのスタイル(クラス名)を保存していなければ、保存する。
            if (!this.originalStyles[targetEl.id]) {
                this.originalStyles[targetEl.id] = targetEl.className;
            }

            // class名をtargetに変更(targetのstyleが変更される)
            targetEl.className = "target";
        }
    },

    /**
     * oDD(Target)の状況を調べて、適当なオブジェクトを返却する。
     *   要素の取得(itemが入っていればitem、入っていなければ、targetそのものがどもってくる。
     */
    getTargetDomRef: function(oDD) {
    	// oDDにitemプロパティーが存在する(itemが入った)場合、入っているitemのエレメント(HTMLDivElement)を返す
        if (oDD.player) {
            return oDD.player.getEl();
        } else {
   	// oDDにitemプロパティーが存在しない場合(cartの場合)、そのエレメント(HTMLDivElement)を返す
            return oDD.getEl();
        }
    },

    /**
     * Drag終了時の処理。
   *  Targetの位置にPlayer要素が入る
     */
    endDrag: function(e) {
        // dropした要素の透過度を1に戻す。
        YAHOO.util.Dom.setStyle(this.getEl(), "opacity", 1);
	// dropした要素のスタイルを元に戻す。
        this.resetTargets();
    },

    /** 
     * ターゲットのスタイルを、Drop時に置き換えたitemのスタイルにリセットする。
     */
    resetTargets: function() {

        // 同一のDDグループのTargetオブジェクトの取得
        var targets = YAHOO.util.DDM.getRelated(this, true);
        for (var i=0; i<targets.length; i++) {
            // 要素の取得(itemが入っていればitem、入っていなければ、targetそのものがどもってくる)
            var targetEl = this.getTargetDomRef(targets[i]);
	    // class名をもとに戻す。=> original なスタイルに戻す。	
            var oldStyle = this.originalStyles[targetEl.id];
            if (oldStyle) {
                targetEl.className = oldStyle;
            }
        }
    },

    /**
     * DragされたオブジェクトがDropした際に発火する。
     * idは、mode=POINTの際はDropされたTargetのidか、mode=INTERSECTの場合は、配列
     * (TargetにまたがってDropされた場合の考慮)のいずれか。
     */ 
    onDragDrop: function(e, id) {

      	// thisはプロキシオブジェクト

        // DropされるTarget(=cart)のオブジェクトを入れる。
        var oDD;

        if ("string" == typeof id) {
	    // mode=pointの場合は、oDDにTargetを入れる。
            oDD = YAHOO.util.DDM.getDDById(id);
        } else {
	    // mode=intersectの場合は、BestMatchするTargetをoDD入れる。
	    // TargetにまたがってDropされない場合には、pointと同じこと。
            oDD = YAHOO.util.DDM.getBestMatch(id);
        }

        // プロキシオブジェクトの要素の取得
        var el = this.getEl();

    	// Targetにplayerプロパティーが存在する場合;itemが入っている場合
        if (oDD.player) {
	    // 移動中のplayerが既にスロットに入っていた場合=>スロット間の移動の場合
            if (this.slot) {
            	// 移動中のitemのスロットが、Dropされたitemのターゲットとしてlegalな場合。
            	//		=> swapする。
                if ( YAHOO.util.DDM.isLegalTarget(oDD.player, this.slot) ) {
                    // elの(x,y)に、Targetに入っているPlayerの要素の(x,y)
		    // をコピー(thisの移動元に戻す)。
		    YAHOO.util.DDM.moveToEl(oDD.player.getEl(), el);
		    this.slot.player = oDD.player;
                    oDD.player.slot = this.slot;
                } else {
                    // illegalならば、移動を中止する。
                    YAHOO.util.Dom.setXY(oDD.player.getEl(), oDD.player.startPos);
                    this.slot.player = null;
                    oDD.player.slot = null
                }
            } else {
            // スロット間移動以外
                oDD.player.slot = null;
                YAHOO.util.DDM.moveToEl(oDD.player.getEl(), el);
            }
        } else {
    	// Targetにitemプロパティーが存在しない場合;itemが入っていない場合
            if (this.slot) {
                this.slot.player = null;
            }
        }

	// Targetに入っているPlayerの要素の(x,y)に、この移動元の(x,y)
	// をコピー。
        YAHOO.util.DDM.moveToEl(el, oDD.getEl());
        // Drop後の状態にスタイルをリセットする。
	this.resetTargets();

        // このオブジェクトのslotに、DropしたTargetオブジェクトを入れる。
        this.slot = oDD;
        // このオブジェクトのslot.itemに、this(Dropしたオブジェクト)をいれる。
        this.slot.player = this;
    },

    swap: function(el1, el2) {
        var Dom = YAHOO.util.Dom;
        var pos1 = Dom.getXY(el1);
        var pos2 = Dom.getXY(el2);
        Dom.setXY(el1, pos2);
        Dom.setXY(el2, pos1);
    },

    // 何もしないようにoverride; レスポンス向上のためと思われる
    onDragOver: function(e, id) {
    },

    // 何もしないようにoverride; レスポンス向上のためと思われる
    onDrag: function(e, id) {
    }

});

// page scopeで変数を定義&Alias
var slots = [], players = [];

var Event = YAHOO.util.Event, DDM = YAHOO.util.DDM;

Event.onDOMReady(function() { 
    // slots
    slots[0] = new YAHOO.util.DDTarget("t1", "topslots");
    slots[1] = new YAHOO.util.DDTarget("t2", "topslots");
    slots[2] = new YAHOO.util.DDTarget("b1", "bottomslots");
    slots[3] = new YAHOO.util.DDTarget("b2", "bottomslots");
    slots[4] = new YAHOO.util.DDTarget("b3", "bottomslots");
    slots[5] = new YAHOO.util.DDTarget("b4", "bottomslots");
    
    // players
    players[0] = new YAHOO.example.DDPlayer("pt1", "topslots");
    players[1] = new YAHOO.example.DDPlayer("pt2", "topslots");
    players[2] = new YAHOO.example.DDPlayer("pb1", "bottomslots");
    players[3] = new YAHOO.example.DDPlayer("pb2", "bottomslots");
    players[4] = new YAHOO.example.DDPlayer("pboth1", "topslots");
    players[4].addToGroup("bottomslots");
    players[5] = new YAHOO.example.DDPlayer("pboth2", "topslots");
    players[5].addToGroup("bottomslots");

    DDM.mode = document.getElementById("ddmode").selectedIndex;

    Event.on("ddmode", "change", function(e) {
            YAHOO.util.DDM.mode = this.selectedIndex;
        });
});

</script>
</HEAD>

<BODY>
<div id="usercontrols">
Interaction Mode:
<select id="ddmode" >
  <option value="0" selected>Point</option>
  <option value="1">Intersect</option>
</select>
</div>

<div id="workarea">
<!-- Top Slot -->
<div class="slot" id="t1" >1</div>
<div class="slot" id="t2" >2</div>

<!-- Bottom Slot -->
<div class="slot" id="b1" >3</div>
<div class="slot" id="b2" >4</div>
<div class="slot" id="b3" >5</div>
<div class="slot" id="b4" >6</div>

<!-- Top SlotだけをTargetにするPlayer -->
<div class="player" id="pt1" >1</div>
<div class="player" id="pt2" >2</div>

<!-- Bottom SlotだけをTargetにするPlayer -->
<div class="player" id="pb1" >3</div>
<div class="player" id="pb2" >4</div>

<!-- Top,Bottom Slotを共にTargetにするPlayer -->
<div class="player" id="pboth1" >5</div>
<div class="player" id="pboth2" >6</div>

</div>

</BODY>
</HTML>