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