Draggable, droppable, and sortable list (2.0)
http://www.knockmeout.net/2011/05/dragging-dropping-and-sorting-with.html
by rniemeyer
HTML
<script src="http://knockoutjs.com/downloads/knockout-2.0.0.debug.js"></script>
<div id="main">
<h2>High Priority Tasks</h2>
<div class="container high" data-bind="sortableList: { tmpl: 'taskTmpl', list: highPriorityTasks }"></div>
<h2>Tasks</h2>
<div class="container" data-bind="sortableList: { tmpl: 'taskTmpl', list: normalPriorityTasks }"></div>
<a href="#" data-bind="click: addTask">Add Task</a>
<hr />
<h2>Trash</h2>
<div class="container trash" data-bind="sortableList: trash"></div>
<script id="taskTmpl" type="text/html">
<div class="item">
<a href="#" data-bind="text: name, click: $root.selectedTask, visible: $data !== $root.selectedTask()"></a>
<input data-bind="value: name, visibleAndSelect: $data === $root.selectedTask(), event: { blur: $root.clearTask }" />
</div>
</script>
</div>
<div id="results">
<h2>High Priority Tasks</h2>
<ul data-bind="foreach: highPriorityTasks">
<li data-bind="text: name"></li>
</ul>
<h2>Tasks</h2>
<ul data-bind="foreach: normalPriorityTasks">
<li data-bind="text: name"></li>
</ul>
</div>
CSS
body { font-family: arial; }
h2 { font-weight: bold; }
div { padding: 5px; margin: 5px; border: black 1px solid; }
p, a { font-size: .8em; }
ul { padding-bottom: 10px; }
.container { width: 125px; min-height: 50px; background-color: #AAA;}
.high { background-color: red; }
.trash { background-color: #000; }
.item { background-color: #DDD; cursor: move; }
.item input { width: 100px; }
#main { float: left; }
#results { margin-left: 175px; width: 150px; }
JavaScript
//connect items with observableArrays
ko.bindingHandlers.sortableList = {
init: function(element, valueAccessor, allBindingsAccessor, data, context) {
var options = ko.utils.unwrapObservable(valueAccessor());
$(element).data("sortList", options.list || valueAccessor()); //attach meta-data
$(element).sortable({
update: function(event, ui) {
var item = ui.item.data("sortItem");
if (item) {
//identify parents
var originalParent = ui.item.data("parentList");
var newParent = ui.item.parent().data("sortList");
//figure out its new position
var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
if (position >= 0) {
originalParent.remove(item);
newParent.splice(position, 0, item);
}
ui.item.remove();
}
},
connectWith: '.container'
});
return ko.bindingHandlers.template.init.apply(this, arguments);
},
update: function(element, valueAccessor, allBindingsAccessor, data, context) {
var options = ko.utils.unwrapObservable(valueAccessor()),
newOptions = {};
//build our options to pass to the template engine
if (options.list) {
newOptions.foreach = options.list;
newOptions.name = options.tmpl;
newOptions.includeDestroyed = options.includeDestroyed;
newOptions.afterAdd = options.afterAdd;
newOptions.beforeRemove = options.beforeRemove;
} else {
newOptions.foreach = valueAccessor();
}
//use an afterRender function to add meta-data
if (options.afterRender) {
//wrap the existing function, if it was passed
newOptions.afterRender =...