ドラッグドロップで項目の移動と並びかえができるリストのサンプル
ドラッグドロップで項目の移動と並びかえができるリストの実装サンプルです。「Bookmarks」の「タグセット編集」機能で使ったもの。99%くらいはYUIのサンプルのコピーだったりしますが、まぁ、あのサンプルの実装メモということで。
戦略
- アイテム(リストの項目)をドラッグ対象として登録。
- アイテムとリストをドロップ対象として登録。
- ドラッグにはプロキシを使う。(アイテム要素を直接マウスに追従させず、アイテム要素の中身をコピーしたプロキシを移動させる。)
- アイテム要素はドラッグ中は非表示。
- アイテムにドラッグした場合、アイテムの前、または後に要素を挿入する。
- 挿入先がわかりやすいように、ドラッグ完了(マウスを放す)前のドラッグオーバー(ドラッグ中にマウスが乗った)段階で、アイテム要素を挿入する。
- ドラッグ完了時には、プロキシを非表示にし、アイテム要素を表示するだけ。
- リストにドラッグした場合、リストの末尾にアイテム要素を挿入する。
実装とサンプル
依存モジュール
<script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/yahoo/yahoo-min.js"></script> <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/event/event-min.js"></script> <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/dom/dom-min.js"></script> <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/dragdrop/dragdrop-min.js"></script> <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/yahoo-dom-event/yahoo-dom-event.js"></script> <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/animation/animation-min.js"></script>
リスト要素
<!-- ドラッグドロップで項目の移動と並べかえができるリスト --> <div id="a" class="list"> <div id="item1" class="item">item1</div> <div id="item2" class="item">item2</div> <div id="item3" class="item">item3</div> </div> <div id="b" class="list"> <div id="item4" class="item">item4</div> </div>
アイテム用ドラッグドロップクラスの実装
/** * リストにドラッグ可能なアイテム * @param {Object} id 要素ID * @param {Object} sGroup ドラッグのグループ * @param {Object} config コンフィグレーション */ ListDD = function(id, sGroup, config ) { ListDD.superclass.constructor.call(this, id, sGroup, config); var el = this.getDragEl(); this.goingUp = false; // 上方向にドラッグしていればtrue this.lastY = 0; // goingUpの更新で使用。 }; YAHOO.extend(ListDD, YAHOO.util.DDProxy, { /** * ドラッグ開始 * @param {Object} x クリックされたx座標 * @param {Object} y クリックされたy座標 */ startDrag: function(x, y) { var srcEl = this.getEl(); // クリックした要素 = ドラッグ対象要素 var proxy = this.getDragEl(); // 実際にドラッグされる要素(プロキシ) / これがマウスに追従する。 // ドラッグ対象要素は非表示にする。 YAHOO.util.Dom.setStyle(srcEl, "visibility", "hidden"); // プロキシに、ドラッグ対象要素の内容をコピー。 // 必要に応じてスタイルも設定する必要有り。 proxy.innerHTML = srcEl.innerHTML; proxy.className = "item"; }, /** * ドラッグ終了(ドロップされずに終わった場合) * @param {Object} e イベント */ endDrag: function(e) { var srcEl = this.getEl(); var proxy = this.getDragEl(); // プロキシを元の位置に戻すアニメーションを実行。 YAHOO.util.Dom.setStyle(proxy, "visibility", ""); var a = new YAHOO.util.Motion( proxy, { points: { to: YAHOO.util.Dom.getXY(srcEl) } }, 0.2, YAHOO.util.Easing.easeOut ) // アニメーション実行後に、プロキシを非表示、ドラッグ対象要素を表示状態にする。 var proxyid = proxy.id; var thisid = this.id; a.onComplete.subscribe(function() { YAHOO.util.Dom.setStyle(proxyid, "visibility", "hidden"); YAHOO.util.Dom.setStyle(thisid, "visibility", ""); }); a.animate(); }, /** * ドロップ対象にドロップされた * @param {Object} e イベント * @param {Object} id ドロップ対象要素のID */ onDragDrop: function(e, id) { // コンテナにドロップされている場合、コンテナの末尾に挿入する。 // ただし、アイテムにドラッグオーバーしていた場合は、何もしない。 // If there is one drop interaction, the li was dropped either on the list, // or it was dropped on the current location of the source element. if (YAHOO.util.DragDropMgr.interactionInfo.drop.length === 1) { // The position of the cursor at the time of the drop (YAHOO.util.Point) var pt = YAHOO.util.DragDropMgr.interactionInfo.point; // The region occupied by the source element at the time of the drop var region = YAHOO.util.DragDropMgr.interactionInfo.sourceRegion; // Check to see if we are over the source element's location. We will // append to the bottom of the list once we are sure it was a drop in // the negative space (the area of the list without any list items) if (!region.intersect(pt)) { var destEl = YAHOO.util.Dom.get(id); var destDD = YAHOO.util.DragDropMgr.getDDById(id); destEl.appendChild(this.getEl()); destDD.isEmpty = false; YAHOO.util.DragDropMgr.refreshCache(); } } }, /** * ドラッグされた * @param {Object} e イベント */ onDrag: function(e) { // どちらの方向にドラッグしているか記録する。 var y = YAHOO.util.Event.getPageY(e); if (y < this.lastY) { this.goingUp = true; } else if (y > this.lastY) { this.goingUp = false; } this.lastY = y; }, /** * ドラッグ中に、他のドラッグドロップ要素にマウスが乗った * @param {Object} e イベント * @param {Object} id マウスオーバーした要素のID */ onDragOver: function(e, id) { var destEl = YAHOO.util.Dom.get(id); // ドラッグ中にマウスが乗った要素 var srcEl = this.getEl(); // ドラッグ中にマウスが乗った要素がアイテムであれば、 // 非表示になっているドラッグ対象要素を挿入する。 if ( destEl.className == srcEl.className ) { var orig_p = srcEl.parentNode; var p = destEl.parentNode; // 上向きに移動中であれば上に挿入。 if (this.goingUp) { p.insertBefore(srcEl, destEl); } else { p.insertBefore(srcEl, destEl.nextSibling); } YAHOO.util.DragDropMgr.refreshCache(); } } });
UIとの関連づけ部分
// ドラッグドロップのグループ。ドラッグ対象、ドロップ対象で統一すること。 var group = "group-1"; // リスト2つをドロップ先として指定。 new YAHOO.util.DDTarget("a", group); new YAHOO.util.DDTarget("b", group); // アイテムをドラッグドロップする要素として指定。 for ( var i = 1; i <= 4; i++ ) { new ListDD("item" + i, group, {}); }