Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Revisiting Dragging, Dropping, and Sorting observableArrays

| Comments

Earlier I explored a set of Knockout.js bindings that would allow dragging and dropping items in an observableArray. These bindings could either be used to sort items in a single array or to move items between arrays. With Knockout 2.0, I wanted to take a fresh look at how this can be accomplished. The resulting project is here.

Areas for improvement

The original syntax that I was using for binding to a single array looked like:

1
2
3
4
<div class="container"
  data-bind="template: {name: 'taskTmpl', foreach: tasks},
    sortableList: tasks">
</div>

The syntax for binding multiple arrays, required additional data to be passed to the chlidren. The parent looked like:

1
2
3
4
5
6
<div class="container"
  data-bind="template: {name: 'taskTmpl',
    foreach: highPriorityTasks,
    templateOptions: { parentList: highPriorityTasks} },
    sortableList: highPriorityTasks">
</div>

The child elements required some meta-data to be attached via a binding:

1
<div data-bind="sortableItem: { item: $data, parentList: $item.parentList }" >

This syntax is far too verbose and forces us to use too many bindings to make this work. My goal is to use a single binding that will work for both a single array and moving items between arrays. Ultimately, I wanted to simplify it down to something like this:

1
2
3
<ul data-bind="sortable: tasks">
  <li data-bind="text: name"></li>
</ul>

or if using named templates like:

1
<ul data-bind="sortable: { tmpl: 'taskTmpl', list: tasks }"></ul>

What does our single binding need to accomplish?

  1. We are not specifying the template or foreach binding, so our binding will have to wrap this functionality by calling the template binding with the appropriate options.
  2. We are not specifying any bindings on the children, so we need to find a way to attach meta-data to the children, so we can retrieve the data and original parent from a dropped element. Since we are calling the template binding, we can use its afterRender option to add the meta-data.
  3. The element require a certain class to be hooked up as a target. We should handle adding this class in the binding rather than requiring the developer to add it (or forget to add it).
  4. We still need to initialize the container by calling jQuery UI’s .sortable and specify how to handle an item being dropped.
  5. We should also call the sortable widgets destroy method when Knockout removes the element (perhaps by the template binding).

How about some extra features?

With these features in place, we can clean up the syntax required on the element and still accomplish the same functionality as before. However, there are a few additional enhancements that I felt would be useful in the binding:

  1. You can configure the class that is used on the element to enable it as a drop target (the connectWith class).
  2. You can specify a function, observable, or static value to determine whether the element is a target for dropping. This will toggle the class based on the supplied value and react if it changes.
  3. The binding accepts a beforeMove function that allows you to perform an action prior to the item being moved from its original location to its new destination. This function can also cancel the move.
  4. The binding also accepts an afterMove function to perform an action after the item has been moved to its new index it the destination array.

Each of these options can be specified in the binding like:

1
<ul data-bind="sortable: {data: items, afterMove: saveData, allowDrop: isBucketFull}"></ul>

Additionally, each feature can be configured with a global default. This keeps the markup/bindings clean when the options will apply to all or most of the bindings. This would look like:

1
2
3
ko.bindingHandlers.sortable.connectClass = "myContainer";
ko.bindingHandlers.sortable.allowDrop = viewModel.isBucketFull;
ko.bindingHandlers.sortable.afterMove = viewModel.saveData;

Samples

Single Todo list:

Link to sample on jsFiddle.net

Connected Todo lists:

Link to sample on jsFiddle.net

Seating chart:

Link to sample on jsFiddle.net

knockout-sortable Project

The binding is up on github here: https://github.com/rniemeyer/knockout-sortable. I am definitely looking for feedback and suggestions for additional functionality, as well as working to improve the docs, specs, examples.

Comments