knockout-sortable - nested
https://github.com/rniemeyer/knockout-sortable
by Asle Gundersby
HTML
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<script src="https://rawgithub.com/rniemeyer/knockout-sortable/master/build/knockout-sortable.js"></script>
<ul data-bind="sortable: children">
<li>
<div data-bind="text: name"></div>
<ul data-bind="sortable: subs">
<li data-bind="text: name"></li>
</ul>
</li>
</ul>
CSS
li { padding: 5px; margin: 5px; border: solid 1px black; margin-left: 25px; }
JavaScript
(function(factory) {
if (typeof define === "function" && define.amd) {
// AMD anonymous module
define(["knockout", "jquery", "jquery.ui.sortable"], factory);
} else {
// No module loader (plain <script> tag) - put directly in global namespace
factory(window.ko, jQuery);
}
})(function(ko, $, undefined) {
var ITEMKEY = "ko_sortItem",
LISTKEY = "ko_sortList",
PARENTKEY = "ko_parentList",
DRAGKEY = "ko_dragItem";
//internal afterRender that adds meta-data to children
var addMetaDataAfterRender = function(elements, data) {
ko.utils.arrayForEach(elements, function(element) {
if (element.nodeType === 1) {
ko.utils.domData.set(element, ITEMKEY, data);
ko.utils.domData.set(element, PARENTKEY, ko.utils.domData.get(element.parentNode, LISTKEY));
}
});
};
//prepare the proper options for the template binding
var prepareTemplateOptions = function(valueAccessor, dataName) {
var result = {},
options = ko.utils.unwrapObservable(valueAccessor()),
actualAfterRender;
//build our options to pass to the template engine
if (options.data) {
result[dataName] = options.data;
result.name = options.template;
} else {
result[dataName] = valueAccessor();
}
ko.utils.arrayForEach(["afterAdd", "afterRender", "beforeRemove", "includeDestroyed", "templateEngine", "templateOptions"], function(option) {
result[option] = options[option] || ko.bindingHandlers.sortable[option];
});
//use an afterRender function to add meta-data
if (dataName === "foreach") {
if (result.afterRender) {
//wrap the existing function, if it was passed
actualAfterRender = result.afterRender;
result.afterRender = function(element, data) {
addMetaDataAfterRender.call(data, element, data);
actualAfterRender.call(data, element, data);
};
} else {
...