Wednesday, October 16, 2013

jQuery UI: Dragging Data Around

The draggable and droppable interaction widgets are great for taking any element and adding DnD behavior. Users can drag one element, and drop it into another. But, the draggable and droppable widgets aren't assigned to elements arbitrarily — they're based on some application logic. The data model that drives the application probably has something that represents an element visible to the user. For example, there might be a database table row corresponding to the product I'm looking at right now. That product might hold a reference to some parent object — a category or a cart for example. When the user re-arranges elements on the screen, they're doing so in order to change the state of the element, and not physically relocating it. So what's the best way to go about manipulating the application data using these widgets?

There are two dimensions to the problem of dragging data, and dropping data using the two respective jQuery UI widgets. First, you need a way to specify what application data the draggable element represents. A way to bind the view layer to the underlying model, in other words. Second, you need a way to pass this data to the droppable when dropped. Given that jQuery UI doesn't prescribe any specific approach to binding application data to widgets, we're free to implement this data-passing mechanism however we choose.

Let's start with the draggable component. Assume that we're building a product list where each product has a price. The price is part of the application data. We could use data attributes to bind this data to the element. That way, we don't have to worry about extraneous JavaScript code for validation and the like. All we care about is attaching application data to the widget element, for which, data attributes will do nicely. But what about the droppable element? In the drop event handler, we have access to the draggable element, and thus, our application data. So our cart element can easily grab the application data it needs through the data attributes we setup earlier, right?

Take a look at the following code. Specifically, look at the drop event handler setup on the droppable widget. Instead of looking up the price data attribute on the draggable element, we're accessing the price value from the ui.product.price attribute. The ui object is the generic object that the jQuery UI framework uses to pass additional data to the event callback function. If you look at the top of the code, you'll see that we're overriding the ui() method of the droppable widget. This method generates the default ui object used in callback functions — so all we're doing here is extending this object with our own application data.

The question is — why do that? Why bother adding this additional layer when all we have to do is use the data attributes directly in the drop callback? The answer is that we don't necessarily want to use data attribute access in the callbacks because there could be several of them. This way, if we ever had to change something about the data once it's dropped but before the callback has access to the data, we can change it in one place. The idea being, decoupling the application data binding approach, in this case, data attributes on the DOM element, from the widget interaction logic. The same principle can be applied to other widgets as well — provide application data in the ui object passed to callback functions.