Knockout.js で Drag and Drop
Knockout.js で jQuery UI の Draggable and Droppable を利用する方法を説明します。
まず、新しい binding を定義します。
- draggable binding
- 初期化時に $(element).draggable() を実行し、要素をドラッグ可能にする。
- droppable binding
- 初期化時に $(element).droppable() を実行し、要素に対してドロップ可能にする。$(element).droppable() ではドロップ時のハンドラを指定する。
整理すると、 draggable な要素を droppable な要素にドロップできるようになります。ドロップ時の動作は droppable で設定します。ややこしいです。
[draggable な要素] → [droppable な要素]
ドロップ時のハンドラでは draggable な要素は取得できますが、draggable な要素にバインドされている ViewModel は直接取得できません*1。そこで、ドロップ時のハンドラで draggable な要素にイベントを投げることで、 draggable な要素にバインドされている ViewModel に振る舞いを記述できます。
具体的な流れを示します。
- ユーザが draggable な要素をドラッグする。
- ユーザが draggable な要素を droppable な要素にドロップする。
- droppable な要素で drop イベントが発火し、イベントハンドラが実行される。
- イベントハンドラは draggable な要素(引数から取得可能)に対して独自イベントを発火させる。イベントの引数として自身にバインドされている ViewModel を渡す。
- draggable な要素で独自イベントが発火し、イベントにバインドされているメソッドが実行される。メソッドは引数として droppable の ViewModel を受け取る。
このパターンは、ドロップ時の振る舞いを draggable に記述する方が自然な場合に適しています。例えば、カレンダーに予定がドロップされた時、予定のプロパティを変更するなど。
コード例を載せておきます。
<!-- ドラッグされる要素 --> <div data-bind="draggable: true, event: {dropped: dropped}" class="hoge"> ぬるぽ </div> <!-- ドロップ先 --> <div data-bind="droppable: true" data-drop-accept=".hoge" data-drop-hover-class="dropping"> ここにドロップしてね </div>
/** * Drag and drop binding. */ ko.bindingHandlers.draggable = { init: function (element, valueAccessor, allBindingsAccessor, viewModel) { $(element).draggable(); }, update: function (element, valueAccessor, allBindingsAccessor, viewModel) { if (valueAccessor()) { $(element).draggable('enable'); } else { $(element).draggable('disable'); } } }; ko.bindingHandlers.droppable = { init: function (element, valueAccessor, allBindingsAccessor, viewModel) { var $element = $(element); $element.droppable({ accept: $(element).data('drop-accept'), hoverClass: $(element).data('drop-hover-class'), tolerance: 'pointer', drop: function (e, ui) { // reset position ui.draggable.css({top: 0, left: 0}); // trigger event ui.draggable.trigger('dropped', [viewModel, e, ui]); } }); }, update: function (element, valueAccessor, allBindingsAccessor, viewModel) { if (valueAccessor()) { $(element).droppable('enable'); } else { $(element).droppable('disable'); } } };
Drag and Drop はいろんなケースを考慮すると一般化しづらいと思います。このエントリは一つの解と考えていただければと思います。
*1:$.data() を使って要素と ViewModel の対応を覚えておけば取得できそう。