Wilsonhut

Deal with it or don't

Knockout + jQueryUI Draggable/Droppable

[Edit: The follow-up to this is in, as well as the final edition in the Knockout + jQueryUI Draggable/Droppable series is in!]

I recently wanted to create a simple game with my son that was very drag-n-drop-centric. I knew I’d use JqueryUI for the drag/drop, so I coded it up, took a step back, and realized that my code was seriously ugly. There was way too much DOM interaction in my code. What it needed was some Knockoutjs!

So this blog post will be the first in a series building up a drag-n-drop knockout custom binding. I’ll start out simple in this post, and build it up to be more feature-rich in later posts. In this post, we’ll learn about:

  • data binding using knockout
  • Jquery draggables and droppables
…as well as some slightly more advanced Knockout features such as…

The contrived task… a pizza builder. Take a list of toppings for a pizza that are in json, and drag the toppings onto the pizza, resulting in toppings data in my pizza json.

Knockout is all about binding data to the UI, so my drag-n-drop implementation will drag and drop DATA, and the UI will just represent that… you’ll see what I mean – especially if you look at the jsfiddle of it.

Here’s the html:

    1 <ul id=“toppings” data-bind=“foreach: toppings”>

    2   <li data-bind=“text: $data, drag: {value: $data}”></li>

    3 </ul>

    4 <br>

    5 <!– ko with: pizza –>

    6 <div id=“pizza” data-bind=“drop: {value: latestTopping}”>

    7   <div data-bind=“visible:(!!latestTopping())”>

    8     <p>

    9       Thanks for adding <span data-bind=“text:latestTopping”></span>!</p>

   10     <span>So far, you’ve added:</span>

   11     <ul data-bind=“foreach: toppingStack”>

   12       <li data-bind=“text: $data”></li>

   13     </ul>

   14   </div>

   15 </div>

   16 <!– /ko –>

Notice the drag: {value: ___} and drop: {value: ___}. That just says that I’m dragging one of the values from the toppings (see the foreach on line 1) and when it’s dropped, it’ll update the the latestTopping property (line 6) on the pizza (line 5). Small note: I could have simply used drag:$data instead of drag: {value:$data}, but I know I’ll have more options to add later.

The viewModel that I’m calling ko.applyBindings on looks like this:

var pizzaria = {

  toppings: [“Pepperoni”, “Olives”, “Sausage”, “Bacon”, “Canadian Bacon”, “Chilean Bacon”],

  pizza: new Pizza()

};

…and the Pizza function just defines a latestTopping property that is a knockout observable.

I wanted to know all the toppings ever added to the pizza, so the latestTopping property makes use of ko.computed so that the write adds the value to an array and the read just reads the most recently added topping. So here’s the Pizza:

    1 function Pizza() {

    2   this.toppingStack = ko.observableArray();

    3   this.latestTopping = ko.computed({

    4     read: function () {

    5       return this.toppingStack().length ? this.toppingStack()[0] : “”;

    6     },

    7     write: function (value) {

    8       if (value) {

    9         this.toppingStack.unshift(value);

   10       } else {

   11         this.toppingStack.shift();

   12       }

   13     },

   14     owner: this

   15   });

   16 }

   17

Then of course, there’s this important magical line of javascript:

ko.applyBindings(pizzaria);

So here it is… my simplest-thing-that-could-work dragon drop knockout custom binding with jqueryui draggable and droppable:

    1 (function () {

    2   var _dragged, _noOp = function () { };

    3   ko.bindingHandlers.drag = {

    4     init: function (element, valueAccessor, allBindingsAccessor, viewModel) {

    5       var dragElement = $(element);

    6       var dragOptions = {

    7         helper: ‘clone’,

    8         revert: true,

    9         revertDuration: 0,

   10         start: function () {

   11           _dragged = ko.utils.unwrapObservable(valueAccessor().value);

   12         },

   13         cursor: ‘default’

   14       };

   15       dragElement.draggable(dragOptions).disableSelection();

   16     }

   17   };

   18

   19   ko.bindingHandlers.drop = {

   20     init: function (element, valueAccessor, allBindingsAccessor, viewModel) {

   21       var dropElement = $(element);

   22       var dropOptions = {

   23         drop: function (event, ui) {

   24           valueAccessor().value(_dragged);

   25         }

   26       };

   27       dropElement.droppable(dropOptions);

   28     }

   29   };

   30 })();

First thing to note is that it is inside a self-executing function (note the “()” on the last line). The reason that is done is so that the variables declared at the top are hidden from the global scope.

A couple of notes about the variable “_dragged”:

  1. I named it with an underscore just so it would stand out to me as a closure from a scope outside where it is written and read. No big deal.
  2. I didn’t have to use a variable for that. I could have stuck it in the data of the html element being dragged (that is: $(element).data(“key”, “value”)), but I made the assumption that only one thing could be dragged at a time and I figured with a single variable to hold it, I wouldn’t leave data lying around tied to DOM elements or have to worry about cleaning them up.

For a custom bindingHandler, you have to implement one or both of the methods init and update. For now, I only need the init. Inside the drag’s init method and the drop’s init method, everything looks pretty typical for the jqueryui draggable and droppable, except for just a few things.

Notable items from the drag’s init function:

  • I used the option “helper: ‘clone'” and “revert: true” because I wanted to only effectively drag around DATA, and not actually move DOM elements around. I let the bindings do their magic and leave the DOM elements where they originate. Don’t worry, in a later post, I’ll discuss how to make it appear that the elements are actually moving, but in this example, the pizza toppings stay in the toppings list forever. They do appear to be ‘added’ to the pizza, but that is just plain-old data binding at work.  Besides, you probably want to be able to add bacon to the pizza multiple times.
  • Line 11 is where the _dragged variable is set from the observable you declared to be draggable in the data-bind=”drag:{value: ___}”

Notable items from the drop’s init function:

  • line 24 is where the observable you declared to be droppable in the data-bind=”drop: {value: ___}” gets set from the _dragged variable.

That’s pretty much it for the simple example.

Please check out the jsfiddle for this example to see it in action. More to come on this later.

Advertisements

4 responses to “Knockout + jQueryUI Draggable/Droppable

  1. Pingback: Knockout + jQuery UI Draggable/Droppable Follow-up « Wilsonhut

  2. Pingback: Knockout + jQueryUI draggable/droppable – the end « Wilsonhut

  3. Alex April 3, 2013 at 10:01 am

    This is great… Is there a way to stop the item being dragged again? For example, currently you can add Olives twice. I like Olives, but I don’t want to add two of the same to my pizza 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: