DD: Reordering a List

YUIのDrag and Dropリストを並べ替えるサンプル(サンプルページはこちら)。

ソースコードを見ると、いきなり(前のサンプルに比べて)えげつないコードが書かれていることがわかる。
Drag and Dropは、YAHOO.util.DDProxyを継承して作成されたオブジェクトで実装されている。

以下に、注釈つきのhtml全文を貼るが、一部、YUIソースコードを追った部分もあった。
DragOverでリストを移動するところなどは巧妙に思うが、なにかもう少しどうにかなんないのかな、というロジックを含んでいたりする。

<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"> 
div.workarea { padding:10px; float:left }

ul.draglist { 
    position: relative;
    width: 200px; 
    height:240px;
    background: #f7f7f7;
    border: 1px solid gray;
    list-style: none;
    margin:0;
    padding:0;
}

ul.draglist li {
    margin: 1px;
    cursor: move;
    zoom: 1;
}

ul.draglist_alt { 
    position: relative;
    width: 200px; 
    list-style: none;
    margin:0;
    padding:0;
    /*
		padding bottomは、targetableなリストが空だったときのため。
    */
    padding-bottom:20px;
}

ul.draglist_alt li {
    margin: 1px;
    cursor: move; 
}


li.list1 {
    background-color: #D1E6EC;
    border:1px solid #7EA6B2;
}

li.list2 {
    background-color: #D8D4E2;
    border:1px solid #6B4C86;
}

/*
#user_actions { float: right; }
*/

</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" src="scripts/yui/animation/animation-min.js" >
</script> 

<script type="text/javascript">
var Dom = YAHOO.util.Dom;
var Event = YAHOO.util.Event;
var DDM = YAHOO.util.DragDropMgr;

//////////////////////////////////////////////////////////////////////////////
//「現在の順番」表示、「バックグラウンドの消去」の処理。
//////////////////////////////////////////////////////////////////////////////

YAHOO.example.DDApp = {
    init: function() {

        var rows=3,cols=2,i,j;
        for (i=1;i<cols+1;i=i+1) {
            // ul要素はddTargetとしてインスタンス化
            new YAHOO.util.DDTarget("ul"+i);
        }

        for (i=1;i<cols+1;i=i+1) {
            for (j=1;j<rows+1;j=j+1) {
                // ul要素はddListとしてインスタンス化
                new YAHOO.example.DDList("li" + i + "_" + j);
            }
        }

        Event.on("showButton", "click", this.showOrder);
        Event.on("switchButton", "click", this.switchStyles);
    },
	//「現在の順番」を拾う処理
    showOrder: function() {
    	// この関数は下で使う。
    	var parseList = function(ul, title) {
            var items = ul.getElementsByTagName("li");
            var out = title + ": ";
            for (i=0;i<items.length;i=i+1) {
                out += items[i].id + " ";
            }
            return out;
        };

        var ul1=Dom.get("ul1"), ul2=Dom.get("ul2");
        alert(parseList(ul1, "List 1") + "\n" + parseList(ul2, "List 2"));

    },

	//「バックグラウンドの消去」の処理
    switchStyles: function() {
        Dom.get("ul1").className = "draglist_alt";
        Dom.get("ul2").className = "draglist_alt";
    }
};

//////////////////////////////////////////////////////////////////////////////
// custom drag and drop implementation
//////////////////////////////////////////////////////////////////////////////

// DDProxyを継承して、DDListを作成するための準備。
YAHOO.example.DDList = function(id, sGroup, config) {

	// スーパークラスのコンストラクタの呼び出し。
    YAHOO.example.DDList.superclass.constructor.call(this, id, sGroup, config);

    //this.logger = this.logger || YAHOO;
    var el = this.getDragEl();
    // Domを1/3の透過とする。
    Dom.setStyle(el, "opacity", 0.67); 

    this.goingUp = false;
    this.lastY = 0;
};

//DDProxyを継承して、DDListを作成する。
//thisは、DDListのインスタンスをあらわす。
YAHOO.extend(YAHOO.example.DDList, YAHOO.util.DDProxy, {

	// startDragのoverride
    startDrag: function(x, y) {
		// this.logger.log(this.id + " startDrag");

		// ドラッグされるProxyElementを取得
        var dragEl = this.getDragEl();
		// ドラッグされる元のElementを取得
        var clickEl = this.getEl();
        // 元のエレメントは、非表示にする。
        Dom.setStyle(clickEl, "visibility", "hidden");

		// ProxyElementに、元のエレメントの要素を追加。
        dragEl.innerHTML = clickEl.innerHTML;
		// ProxyElementに、元のエレメントのスタイルをコピー。
        Dom.setStyle(dragEl, "color", Dom.getStyle(clickEl, "color"));
        Dom.setStyle(dragEl, "backgroundColor", Dom.getStyle(clickEl, "backgroundColor"));
        Dom.setStyle(dragEl, "border", "2px solid gray");
    },

	// endDragのoverride
	// startDragされて、Dropされずに、Mouseupされた場合に元に戻す処理。
    endDrag: function(e) {
    	// thisは、DDListのインスタンスをあらわす。
		// DDListはli要素についてインスタンス化される。
		
    	// ドラッグさている元Elementを取得
        var srcEl = this.getEl();
		// ドラッグされているProxyElementを取得
        var proxy = this.getDragEl();

		// ProxyElementをみせて、元Elementの位置を取得して
		// Anim(Motion)で戻る。
        Dom.setStyle(proxy, "visibility", "");
        var a = new YAHOO.util.Motion( 
            proxy, { 
                points: { 
                    to: Dom.getXY(srcEl)
                }
            }, 
            0.2, 
            YAHOO.util.Easing.easeOut 
        )
        var proxyid = proxy.id;
        // 元エレメントのID
        var thisid = this.id;

		// 上のMotionが終わったら、proxyを消して、元のエレメントを見せる。
        a.onComplete.subscribe(function() {
                Dom.setStyle(proxyid, "visibility", "hidden");
                Dom.setStyle(thisid, "visibility", "");
            });
		//Motionの発火
		a.animate();
    },

    // onDragOverで挿入が行われていることに注意。
   	// (注)idは、Dragされた要素が、落っことされた要素のid	
    onDragDrop: function(e, id) {

    	// DDM.interactionInfo.dropはonDragDropでinteractions Pointを
    	// 意味する。(APIドキュメント)
		// 1回のInteractionが行われたら。
		// ただしい移動(挿入、移動)後はinteraction情報がクリアされる。
		// 以下の条件を満たすのは、liがUL(のドロップゾーン)にinteractする場合。
		// これ以外は、endDragで戻してしまう。
		if (DDM.interactionInfo.drop.length === 1) {

        	// Interact(Drop)したときのカーソルポジション
        	// ※PointはYAHOO.util.Point(PointはRegionのサブクラス)
            var pt = DDM.interactionInfo.point; 

        	// Interact(Drop)したときの元要素の領域
        	// ※RegionはYAHOO.util.Regionのサブクラスと思われる。
            var region = DDM.interactionInfo.sourceRegion; 

			// regionがpointを含まなければ、子要素として追加。
            if (!region.intersect(pt)) {
                //Dropされた場所の要素を取得。(ULを拾っている)
                var destEl = Dom.get(id);
                //Drag and Dropされたオブジェクト
                var destDD = DDM.getDDById(id);
                //this.getElで取れるのは、移動元のelement
                //移動元のエレメントをDropされたElementの最後の子要素にする。
                destEl.appendChild(this.getEl());
                destDD.isEmpty = false;
				//DDの座標のリセット(Interaction情報のクリア)
                DDM.refreshCache();
            }

        }
    },

    // Drag中の処理。
    onDrag: function(e) {

		// y軸方向の移動量
    	var y = Event.getPageY(e);
		
        if (y < this.lastY) {
            // goingUpはプロパティーとして定義されていることに注意
            this.goingUp = true;
        } else if (y > this.lastY) {
            this.goingUp = false;
        }

		// lastYもプロパティーとして定義されていることに注意
        this.lastY = y;
    },

   	// (注)idは、DragOverされた要素のid	
	// DragOverされたら、挿入してしまう。
   	onDragOver: function(e, id) {
    
		// ドラッグされた元要素のelementの取得
    	var srcEl = this.getEl();
		// ドラッグオーバーされた要素のelementの取得
        var destEl = Dom.get(id);

        // li要素にドラッグオーバーされたら、以下の処理。
        if (destEl.nodeName.toLowerCase() == "li") {
            var orig_p = srcEl.parentNode;
            var p = destEl.parentNode;

			// このDDListのインスタンスが上を向いて動いていれば、前に挿入
			// goingUpは、onDragで計ってある。
			if (this.goingUp) {
                p.insertBefore(srcEl, destEl); // insert above
   			// そうでなければ、後に挿入
            } else {
                p.insertBefore(srcEl, destEl.nextSibling); // insert below
            }
			//DDの座標のリセット(Interaction情報のクリア)
            DDM.refreshCache();
        }
    }
});

Event.onDOMReady(YAHOO.example.DDApp.init, YAHOO.example.DDApp, true);

</script>
</HEAD>

<BODY>
<div class="workarea">
  <h3>List 1</h3>
  <ul id="ul1" class="draglist">
    <li class="list1" id="li1_1">list 1, item 1</li>
    <li class="list1" id="li1_2">list 1, item 2</li>
    <li class="list1" id="li1_3">list 1, item 3</li>
  </ul>
</div>

<div class="workarea">
  <h3>List 2</h3>
  <ul id="ul2" class="draglist">
    <li class="list2" id="li2_1">list 2, item 1</li>
    <li class="list2" id="li2_2">list 2, item 2</li>
    <li class="list2" id="li2_3">list 2, item 3</li>
  </ul>
</div>

<div id="user_actions">
  <input type="button" id="showButton" value="現在の順番" />
  <input type="button" id="switchButton" value="バックグラウンドの消去" />
</div>
</BODY>
</HTML>

画面は下のイメージ。