Drag and Drop table rows with AJAX, Scriptaculous and Prototype

November 14th, 2007 posted by codders

* UPDATE: This’ll only work in Firefox (at time of writing. Other browsers may catch up)

Tables are like sooo last century, I know. Sometimes, though, you have data that is genuinely tabular and that you want the user to be able to reorder. I had this most recently with a tabular todo list that I was writing where I wanted the user to be able to priority order the actions by drag and drop.

Below, you’ll see four tables. You can drag items back and forth between tables 1 and 2, and between tables 3 and 4, but not 1 and 3 or 2 and 4 or 1 and 4 or 2 and 3. I’ve enabled the ‘ghosting’ effect on tables 1 and 2, but not on tables 3 and 4 - ghosting appears to screw up the layout a little once you’ve done some dragging.

Table 1

Col1 Col2
Value1Table1 Value2Table1
Value3Table1 Value4Table1

Table 3

Col1 Col2
Value1Table3 Value2Table3
Value3Table3 Value4Table3

Table 2

Col1 Col2
Value1Table2 Value2Table2
Value3Table2 Value4Table2
Value5Table2 Value6Table2

Table 4

Col1 Col2
Value1Table4 Value2Table4
Value3Table4 Value4Table4
Value5Table4 Value6Table4


Neat. The question is how, right? The answer is Sciptaculous, which is a handy Javascript library for Ajax and special effects that takes most of the misery and the having-to-know-any-javascript out of writing dynamic web pages.

<!-- Javascript Includes -->
<script type="text/javascript" src="/script/prototype.js"></script>
<script type="text/javascript" src="/script/scriptaculous.js"></script>
<!-- Standard HTML -->
<table>
<tr><td>
<p>Table 1</p>
<table padding="10" border="1" bgcolor="#aaaaff">
<tr><th>Col1</th><th>Col2</th></tr>
<tbody id="table1">
<tr id="row_1"><td>Value1Table1</td><td>Value2Table1</td></tr>
<tr id="row_2"><td>Value3Table1</td><td>Value4Table1</td></tr>
</tbody>
</table>
</td>
<td>
<p>Table 3</p>
<table border="1" bgcolor="#aaaaff">
<tr><th>Col1</th><th>Col2</th></tr>
<tbody id="table3">
<tr id="row_1"><td>Value1Table3</td><td>Value2Table3</td></tr>
<tr id="row_2"><td>Value3Table3</td><td>Value4Table3</td></tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<p>Table 2</p>
<table border="1" bgcolor="#aaaaff">
<tr><th>Col1</th><th>Col2</th></tr>
<tbody id="table2">
<tr id="row_1"><td>Value1Table2</td><td>Value2Table2</td></tr>
<tr id="row_2"><td>Value3Table2</td><td>Value4Table2</td></tr>
<tr id="row_3"><td>Value5Table2</td><td>Value6Table2</td></tr>
</tbody>
</table>
</td><td>
<p>Table 4</p>
<table border="1" bgcolor="#aaaaff">
<tr><th>Col1</th><th>Col2</th></tr>
<tbody id="table4">
<tr id="row_1"><td>Value1Table4</td><td>Value2Table4</td></tr>
<tr id="row_2"><td>Value3Table4</td><td>Value4Table4</td></tr>
<tr id="row_3"><td>Value5Table4</td><td>Value6Table4</td></tr>
</tbody>
</table>
</td></tr></table>
<!-- Scriptaculous Magic -->
<script>
Sortable.create("table1", { tag:"tr", containment:["table1", "table2"], ghosting:true })
Sortable.create(”table2″, { tag:”tr”, containment:["table1", "table2"], ghosting:true })
Sortable.create(”table3″, { tag:”tr”, containment:["table3", "table4"]})
Sortable.create(”table4″, { tag:”tr”, containment:["table3", "table4"]})
</script>

(sed -e ’s/</\&lt;/g;s/>/\&gt;/g’ is my new bff)

Simple, right? Only slight oddness is the use of the <tbody> tag, which is a kink of the Scriptaculous library. In the call to ‘Sortable.create’, we’ve passed the “id” of the <tbody> element in as the first parameter, identified <tr> tags as the things we want to be able to reorder with the ‘tag’ option, specified the Sortable elements between which rows can be dragged with the ‘containment’ option, and turned on the ghosting effect in the obvious way. Only gotchas are that the includes at the top have to be done in the order shown, and that the calls to Sortable.create have to appear after the element definitions (not only of the Sortable in question, but also of any elements identified in the ‘containment’ option).
But you’re thinking “this is worse than useless - when I refresh the page, the sorting resets”. And you’d be right. This is where Prototype comes in. A copy of Prototype is included when you download Scriptaculous, and it implements all the handy little Ajax bits. This post is getting a bit long now, so next time I’ll explain why:

new Ajax.Request('/some/action/url', {
method:'post', parameters:Sortable.serialize('table1')
})

is basically all you need to know to be able to persist the reordering.