knockout.jsknockout-sortable

Knockout sortable makes duplicates (making a tree structure)


I am making a editable tree structure with Knockout sortable and found this great example: http://jsfiddle.net/rniemeyer/Lqttf

That works fine, but I have a list of root nodes so I changed the root node binding to be a sortable list of tree items too. Fiddle: http://jsfiddle.net/yyqnhngm

The new template markup looks like this (notice that the root ul is a sortable binding and not a template as in the original):

<script id="nodeTmpl" type="text/html">
    <li>
        <a href="#" data-bind="text: name"></a>
        <div>
           <ul data-bind="sortable: { template: 'nodeTmpl', data: $data.children }"></ul>            
        </div>
    </li>
</script>

<ul data-bind="sortable: { template: 'nodeTmpl', data: root }"></ul>

If you drag B into A, then B will be copied instead of moved into A. That's the issue I'm looking for a cause of and solution to. My immediate thought is that sortable thinks the item is dragged into both the lists at the same time, maybe because of a markup/html issue, but I can't see how.

Note: I know I could just wrap all the items into a root note, but that doesn't make much sense for my purposes.


Solution

  • It seems to me that there's no support for "ordinary" arrays. Changing the array in your viewmodel to an observableArray gives you the required bahavior. I.e.:

    ko.applyBindings({
      root: ko.observableArray([
        new TreeItem("A", []),
        new TreeItem("B", [])
      ])
    });
    

    For reference:

    According to the knockout-sortable readme (emphasis mine):

    knockout-sortable is a binding for Knockout.js designed to connect observableArrays with jQuery UI's sortable functionality.

    I also tried to go through the source to be able to explain what exactly was going wrong, but couldn't find the exact cause. I did find a snippet which seems to at least explain the author's thoughts.

    The code shows that the plugin's default behavior uses the splice method to move an element between arrays. This could explain why your example didn't trigger an exception: both Array.prototype and a ko.observableArray have a splice method; both take similar arguments.

    The last comment in the code block explains that targetParent and sourceParent are expected to be observable.

    if (!sortable.hasOwnProperty("strategyMove") || sortable.strategyMove === false) {
      //do the actual move
      if (targetIndex >= 0) {
        if (sourceParent) {
          sourceParent.splice(sourceIndex, 1);
    
          //if using deferred updates plugin, force updates
          if (ko.processAllDeferredBindingUpdates) {
            ko.processAllDeferredBindingUpdates();
          }
        }
    
        targetParent.splice(targetIndex, 0, item);
      }
    
      //rendering is handled by manipulating the observableArray; ignore dropped element
      dataSet(el, ITEMKEY, null);
    }
    

    Source: https://github.com/rniemeyer/knockout-sortable/blob/master/src/knockout-sortable.js#L250