Draggable, droppable, and sortable list (2.0)

http://stackoverflow.com/questions/8961707/how-to-detect-moving-of-an-item-between-knockout-observable-arrays

by mgrcic

HTML

<script src="https://github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.debug.js"></script>
<div id="main">
    <h2>High Priority Tasks</h2>
    <div class="high" data-bind="sortableList: { tmpl: 'taskTmpl', list: highPriorityTasks, afterDrop: myDropCallback }"></div>
    
    <h2>Tasks</h2>
    <div data-bind="sortableList: { tmpl: 'taskTmpl', list: normalPriorityTasks, afterDrop: myDropCallback }"></div>
    
    <a href="#" data-bind="click: addTask">Add Task</a>
    <hr />
    <h2>Trash</h2>
    <div class="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; width: 150px; margin: 0px; }
#results { margin-left: 175px; width: 150px; }

JavaScript

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
    prepareTemplateOptions: function(valueAccessor) {
        var result = {},
            options = ko.utils.unwrapObservable(valueAccessor());

        //build our options to pass to the template engine
        if (options.list) {
            ko.utils.extend(result, options);
            result.foreach = options.list;
            result.name = options.tmpl;
        } else {
            result.foreach = valueAccessor();
        }

        //use an afterRender function to add meta-data
        if (options.afterRender) {
            //wrap the existing function, if it was passed
            result.afterRender = function(element, data) {
               ko.bindingHandlers.sortableList.afterRender.call(data, element, data);
               options.afterRender.call(data, element, data);
            };
        } else {
            result.afterRender = ko.bindingHandlers.sortableList.afterRender;
        }

        //return options to pass to the template binding
        return result;
    },
    init: function(element, valueAccessor, allBindingsAccessor, data, context) {
        var options = ko.bindingHandlers.sortableList.prepareTemplateOptions(valueAccessor);

        //attach the appropriate class to our element
        if (ko.bindingHandlers.sortableList.autoAddClass) {
             ko.utils.toggleDomNodeCssClass(element, ko.bindingHandlers.sortableList.connectWithClass, true);
        }

        //attach meta-data
        $(element).data("sortList", options.foreach);
        $(element).sortable({
            update: function(event, ui) {
                var originalParent, newParent, position,
                    item = ui.item.data("sortItem");

                if (item) {
                    //identify parents
                    originalParent = ui.item.data("parentList");
                    newParent = ui.item.parent().data("sortList");
                    position =...