Adding items to a jqui sortable via Knockout bindings

Get jQuery UI's sortable to recognize elements added through Knockout's foreach template binding. (Portions borrowed from http://jsfiddle.net/rniemeyer/Myue3/)

by medmunds

HTML

<script src="https://github.com/downloads/SteveSanderson/knockout/jquery.tmpl.js"></script>
<script src="https://github.com/downloads/SteveSanderson/knockout/knockout-1.1.2.debug.js"></script>
<div data-bind="customView: {}">
    <p>These are sortable:</p>
    <ul class="sortableList" data-bind="template: { name: 'itemsTmpl', foreach: items, afterRender: afterRender }"></ul>
</div>

<button data-bind="click: addOneItem">Add One Item</button>
<button data-bind="click: addThreeItems">Add Three Items</button>
<button data-bind="click: clearLog">Clear Log</button>

<script type="text/x-jquery-template" id="itemsTmpl">
    <li>
        <span data-bind="text: name"></span>
        <a data-bind="click: function() { viewModel.removeItem($data) }">Remove</a>
    </li>
</script>

<div id="console">
    <p>Console:</p>
</div>

CSS

ul {
    margin: 1em 0;
    border: 1px solid #cccccc;
    background: #eeeeee;
}

li {
    margin: 0.2em;
    padding: 0.2em 0.4em;
    border: 1px solid #dddd00;
    background: #ffffdd;
}

li > a {
    float: right;
    color: #0000ff;
    text-decoration: underline;
}

#console {
    font: 8pt monospace;
    margin-top: 2em;
    padding: 0.2em;
    background: #eeeeff;
}

JavaScript

var id = 1;
var renderCount = 0;

var viewModel = {
    items: ko.observableArray([
        { name: 'Item '+id, id: id++ },
        { name: 'Item '+id, id: id++ },
        { name: 'Item '+id, id: id++ }
    ]),
    afterRender: function(renderedNodes, data) {
        log("afterRender " + (++renderCount)
           + " rendering " + renderedNodes.length + " nodes");
    },
    addOneItem: function() {
        this.items.push(
            { name: 'Item '+id, id: id++ }
        );
    },
    addThreeItems: function() {
        this.items.splice(this.items().length, 0,
            { name: 'Item '+id, id: id++ },
            { name: 'Item '+id, id: id++ },
            { name: 'Item '+id, id: id++ }
        );
    },
    removeItem: function(itemToRemove) {
        this.items.remove(itemToRemove);
    }
};

///////////////////////////
// jQuery.ui.sortable simple ko binding

ko.bindingHandlers.customView = {
    init: function(element) {
        log("customView binding init");
        $(".sortableList", element).sortable()
            .disableSelection();
    },
    update: function(element) {
        log("sortable binding update");
    }
};


///////////////////////////

ko.applyBindings(viewModel);

function log(message) {
  $("#console").append(document.createTextNode(message)).append("<br/>");
}

function clearLog() {
    $("#console").empty();   
}