I recently implemented a change to my Track-Blaster software so that DJs can rearrange songs in a playlist by dragging and dropping them from one place to another. To do this, I incorporate the wonderful Scriptaculous javascript library.
I’m not going to give a tutorial of how this effect is achieved. If you want that, there’s a great one here. The reason for this post is to share a solution to a specific problem I was having using these “sortables”.
The problem is this: I drag an element within a sortable. Sometimes I drag it to a different position and sometimes I don’t. I want to run different functions on the dragged element depending on whether it has moved. There are functions included in the library that are useful — Sortable.serialize and Sortable.sequence — but they return a string or array containing the new order of all the elements. I am only interested in the element I’m dragging.
Sortable.create has a callback, OnUpdate, that’s triggered only when an element is moved to a different spot. However, it doesn’t know which element was dragged; the entire list is its parameter. Conversely, there is an option called reverteffect that knows which element was dragged, but it doesn’t know whether it’s gone to a different position or stayed put.
Conveniently, OnUpdate is called before reverteffect. My solution involves using OnUpdate to add a temporary class to the list container. Then, reverteffect is used to test whether that class exists. If it does, the class is removed and a function is called on the moved element. If it doesn’t, a different function can be called on the dragged-but-not-moved element. Here’s a sample page that shows you what I’m talking about. Put your mouse over the up/down arrows and drag that item to somewhere else within the list. Or put it back where you found it. After you’ve dropped it, a message will appear at the bottom identifying the item you just dragged and indicating whether it was moved.
You can examine the source code of this sample page, but here’s some annotations of the important bits. I’m assuming you’re familiar with the fabulous Prototype javascript library. Let’s start at the bottom:
Sortable.create('testlist',{
constraint: false,tag: 'div', handle: 'moveme',
reverteffect: moveRevertEffect,
onUpdate: function(element) {
element.addClassName('sortUpdate');
}
});
This script activates the sortable. The first parameter, “testlist” is the id of the object containing all the things you’ll be dragging. The next parameter is a hash of the various sortable options. The relevant ones to this post are the last two. “reverteffect: moveRevertEffect” replaces the standard Scriptaculous reverteffect with a custom one we’re arbitrarily calling moveRevertEffect. More on this below. “onUpdate: function(element)…” does what I said earlier. onUpdate is only called after you drop an element in a different spot from where it started. If it is called, the class “sortUpdate” (another arbitrary name) is added to the parent container (i.e. testlist).
After an element is dropped, and after onUpdate is called (if it is called), the reverteffect kicks in. Let’s look at that code (from near the top of the sample page):
var moveRevertEffect =
function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2) +
Math.abs(left_offset^2))*0.02;
new Effect.Move(element, {
x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
if (element.up().hasClassName('sortUpdate')) {
element.up().removeClassName('sortUpdate');
$('whahappened').innerHTML = element.id + ' has been moved';
} else {
$('whahappened').innerHTML = element.id + ' has not moved';
}
}
This custom script creates the new variable that will replace the standard reverteffect. We want to keep the standard effect but then add to it. So the first part of the custom script (the dur and Effect.Move statements) is simply a copy of the standard version found deep in the bowels of Scriptaculous (in the file dragdrop.js). The if statement is our addition. It checks to see if the parent of the dragged element (i.e. testlist) has the class “sortUpdate”. If it does, that means the element was moved. The class name is removed and we can run whatever function we like using the dragged element as an argument. If the parent doesn’t have the “sortUpdate” class, that means the element was dropped off where it started. The else clause kicks in and we can run some other function using the dragged element as an argument. In the example, I use a (very) simple function that changes the innerHTML property of the div at the bottom, but you can put anything here including ajax calls or whatever.
And that’s it. A rather esoteric problem to be sure, but hopefully someone, somewhere, sometime, will find this useful.