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>Tasks</h2>
    <div class="container" data-bind="sortableList: tasks">
        <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>
    </div>
    <a href="#" data-bind="click: addTask">Add Task</a>
</div>    

<div id="results">
    <h2>Tasks</h2> 
    <ul data-bind="foreach: tasks">
        <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; }
li { padding: 2px; }
.container {  width: 125px; min-height: 50px; background-color: #AAA;}
.item { background-color: #DDD; cursor: move; }
.item input { width: 100px; }
#main { float: left; }
#results { margin-left: 175px; width: 150px; }

JavaScript

var Task = function(name) {
    this.name = ko.observable(name);
}

var ViewModel = function() {
    var self = this;
    self.tasks = ko.observableArray([
        new Task("Get dog food"),
        new Task("Mow lawn"),
        new Task("Fix car"),
        new Task("Fix fence"),
        new Task("Walk dog"),
        new Task("Read book")
    ]);
    
    
    self.selectedTask = ko.observable();
    self.clearTask = function() {
        self.selectedTask(null);
    };
    self.addTask = function() {
        var task = new Task("new");
        self.selectedTask(task);
        self.tasks.push(task);
    };
};

//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...