knockout + jQuery UI sortable

by mgrcic

HTML

<script src="https://github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.debug.js"></script>
<script src="https://github.com/downloads/rniemeyer/knockout-sortable/knockout-sortable.js"></script>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/themes/cupertino/jquery-ui.css">
<div class="demo">
    
    <div class="column-parent">
        <h1 class="column-header">High</h1>
        <div class="column" data-bind="sortable: highPriorityTasks">
            <div class="portlet">
                <div class="portlet-header" data-bind="text: name"></div>
                <div class="portlet-content"></div>
            </div>
        </div>
    </div>
    
    <div class="column-parent">
        <h1 class="column-header">Normal</h1>
        <div class="column" data-bind="sortable: normalPriorityTasks">
            <div class="portlet">
                <div class="portlet-header" data-bind="text: name"></div>
                <div class="portlet-content"></div>
            </div>
        </div>
    </div>
</div>

<hr/>

<ul data-bind="foreach: allTasks">
    <li>
        <span data-bind="text: name"></span> - 
        <span data-bind="text: priority"></span>
    </li>
</ul>

CSS

.demo { font-size:12px; font-family:sans-serif; width:80%; margin:auto; }
        .column-parent { width: 100px; float: left; padding-bottom: 100px; }
        .column { padding-bottom: 100px; }
        .column-header { text-align:center; font-size: 14px; }
        .portlet { margin: 0 1em 1em 0; }
        .portlet-header { margin: 0.3em; padding-bottom: 4px; padding-left: 0.2em; }
        .blocked { background: #F38585; border: red !important;}
        .portlet-header .ui-icon { float: right; }
        .portlet-content { padding: 0.4em; }
        .ui-sortable-placeholder { border: 1px dotted black; visibility: visible !important; height: 50px !important; }
        #status {position: fixed; top:0px, left:0px; background: gray; padding: 5px;}
        .ui-sortable-placeholder * { visibility: hidden; }

JavaScript

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

var initialData = [
    new Task("Get dog food", "high"),
    new Task("Mow lawn", "high"),
    new Task("Fix car", "high"),
    new Task("Fix fence", "normal"),
    new Task("Walk dog", "normal"),
    new Task("Read book", "normal")
];

var ViewModel = function () {
    var self = this;

    self.highPriorityTasks = ko.observableArray(ko.utils.arrayFilter(initialData, function(task) { 
        return task.priority() === "high"; 
    }));    
    self.highPriorityTasks.priority = "high";

    self.normalPriorityTasks = ko.observableArray(ko.utils.arrayFilter(initialData, function(task) { 
        return task.priority() === "normal"; 
    }));
    self.normalPriorityTasks.priority = "normal";
    
    self.allTasks = ko.computed(function() {
        var result = [];
        ko.utils.arrayPushAll(result, self.highPriorityTasks());
        ko.utils.arrayPushAll(result, self.normalPriorityTasks());
        return result;        
    });
};

$( ".portlet" ).addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" )
    .find( ".portlet-header" )
        .addClass( "ui-widget-header ui-corner-all" )
        .prepend( "<span class='ui-icon ui-icon-minusthick'></span>")
        .end()
    .find( ".portlet-content" );

$( ".portlet-header .ui-icon" ).click(function() {
    $( this ).toggleClass( "ui-icon-minusthick" ).toggleClass( "ui-icon-plusthick" );
    $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).toggle();
});

$( ".column" ).disableSelection();
    
ko.bindingHandlers.sortable.afterMove = function(arg) {
    arg.item.priority(arg.targetParent.priority);  
};

ko.applyBindings(new ViewModel());