Queued: Drag and Drop in the Queue

By on April 16, 2009 9:52 am

During the interaction design phase, we determined that Netflix’s current drag and drop reordering feature was well thought out — so we set out to create the same reordering behavior in Queued. As Revin mentioned before, we also wanted to keep Queued as light as possible; because of this, we decided early on to not use any of the Dijit infrastructure to create the queue listings. Not only that, but the Dojo Grid would not work for the queues because it doesn’t support drag and drop reordering of rows. This meant we would have to come up with something which would be flexible (yet fast) to render the queue.

In the user interface design phase, we decided that each of the lists in the “Your Queue” section would be based on HTML tables. In Queued, only the DVD queue and the Instant list can be reordered, so we’ll focus on the DVD queue.

Here’s the HTML for the DVD queue:

Movie Instant Star Rating Genre Disc Format Remove

This table’s body corresponds to a queueList object:

qd.app.queueList = function(){
	this.list = [];
	this.domNode = null;
	this.constructor = function(options){
		dojo.mixin(this, options);
		this.domNode = dojo.byId("queueTemplateNode");
		this.dndSource = new dojo.dnd.Source(this.domNode, {
			accept: "movie",
			skipForm: true,
			withHandles: true,
			isSource: true,
			singular: true,
			creator: dojo.hitch(this, this.createItem)
			dojo.filter(this.result.items, function(i){
				// in English: for "queue" and "instant",
				// skip when position==null
				return i.position ||
					(this.type!="queue" && this.type!="instant");
			}, this)
		dojo.connect(this.dndSource, "onDropInternal", this, "onDrop");
		dojo.connect(this.dndSource, "onDndCancel", this, "onDragCancel");

When the queueList is instantiated, a dojo.dnd.Source object is created; this means the body of the table is now a source for items to be dragged out of. This particular source also functions as a container — items can be dropped on it — and will only accept items with a type of “movie” (meaning that all other items will be ignored). We then populate the queue when insertNodes is called with the filtered list of items we want to add. The insertNodes function calls the creator parameter to dojo.dnd.Source. Let’s take a look at what that function does:

this.createItem = function(item, hint){
	if(hint == "avatar"){
		var node = dojo.doc.createElement("div");
		node.className = "movieDragAvatar";
		node.innerHTML = item.item.title.title;
		this.draggingitem = item;
		return {
			node: node,
			data: item,
			type: this.type
	var listItem = new qd.app.queueItem({
			item: item,
			type: this.type,
			parent: this
		}, this.domNode);
	return {
		node: listItem.domNode,
		data: listItem,
		type: this.type

As you can see, createItem gets called in a couple of situations. We mentioned insertNodes, but it is also called when you drop an item onto a source and when you start a drag. The latter situation calls the creator with the hint argument set to "avatar". In that situation, we return a node that has a simple representation of what we are dragging that follows the mouse. When insertNodes calls createItem, it will instantiate a qd.app.queueItem which handles creating the correct markup for a row in the table. Then, insertNodes adds the node property of the returned object to the node passed to the dojo.dnd.Source constructor. You might be wondering what a qd.app.queueItem does in its constructor; it simply takes the information provided in the item and constructs a table row.

The dojo.dnd.Source object also provides some events to which we can connect. The first, onDropInternal, is triggered when the user drops an item dragged from the DVD Queue into the DVD Queue — in other words, a re-order of the queue. The event handler, onDrop, looks like this:

this.onDrop = function(nodes){
	var node = nodes[0];
	this.dndSource.getAllNodes().forEach(function(n, i){
		var listItem = this.dndSource.getItem(n.id).data;
		if(n.id == node.id){
			this.reorder(listItem, i+1, false);
	}, this);
	this.draggingitem = null;

The reorder method handles talking to Netflix’s service to move items around and it also renumbers the rows in the HTML table. The second interesting event (to us) is onDndCancel. It is triggered when a drag operation is canceled — for example, when the user hits ESC during a drag. It is also triggered at the end of any drop. Our event handler, onDragCancel, cleans up after our drag operations:

this.onDragCancel = function(){
		this.draggingitem = null;

In the end, that’s all it took to get drag and drop working in Queued. Dojo’s drag and drop API is a robust, flexible system which can be easily tailored to your application by passing a creator function to a dojo.dnd.Source. All the creator function has to do is know how to create a representation of the item to be appended to the source (in this case, a row appended to a table) and create a representation of the item while it’s being dragged. After you’ve set that up, you just have to listen to the source’s events and make adjustments on the server-side accordingly.