Monday, August 18, 2008

Drag & Drop Sortable Lists

I'm working on the ROTA module of Taskerbot now, which I've started and stopped at least three times. Par for the course, I've mostly forgotten whatever the hell I was doing last time I was working on it, so I'm starting over. Again. On the bright side, three years of occasionally working on the idea finally seems to have gelled in the back of my little tiny brain, and now I actually think I can do it, and make it fairly simple in fact, unlike the complex coding behemoth it began.

Tonight the challenge was the basic creation of the sort. The manager takes a list of his members and using simple drag & drop javascript, puts them in the sequence that he wants for event-assignment (usually, tasking).

First of all, there are some precompiled javascripts that help do all kinds of things more easily and more cross-browser compatible, so I went and got those. They are Prototype and Scriptaculous. I got the latest .js (javascript) file from each of those and put it in my directory, and added a head call to each.

<script language="JavaScript" src="../tbotscripts/prototype.js"></script>
<script language="JavaScript" src="../tbotscripts/scriptaculous/scriptaculous.js"></script>

OK, so that is set. (Note that scriptaculous is actually a small library of files; you need to put them all in the same directory, then just call the one.)

Then after searching I found a script (and I can't recall the source or I'd link to it) for the "sortable" feature.

However I could not figure out how to make it work with DIVs as I allegedly can use. Nor could I figure out how to make it write to a hidden HTML form field. After screwing with both of them for quite some time, I finally thought, why am I making this so hard? Yes it would be nice if I had the neat canned javascript for that but I don't, and there is definitely a manual way around it. These items are going to be looped out from a cfquery after all so it's not rocket science.

So I formatted the LI list elements to look like DIVs. And then I put a hidden field inside each of the list elements that would carry the userid (UID) value. When I call the name of that form field, it will automatically give me a comma delimited list of all the values set with that field name, in the sequence they are shown on the HTML form. I tested it and it works just fine.

So I added this to my HEAD section:

<style>
li { list-style-type: none; border: 1px black solid; padding:3px; margin:4px;
margin-top:6px; background-color: FFFFCC; color: black; width: 300px; }
</style>

And then I added this to my HEAD section:

<script language="JavaScript">
function getOrder() {
var orderList = '';
orderedNodes = document.getElementById("sortable_list").getElementsByTagName("li");
for (var i=0;i < orderedNodes.length;i++) {
orderList += orderedNodes[i].getAttribute('recordid') + ', ';
}
alert(orderList);
}
</script>

And here is a simple static version of the code for the sortable list in the page. Note the little script in there -- it must come AFTER the sortable list.

<form name="myform" method="post" action="working1.cfm">
<ul class="sortlist" id="sortable_list" style="cursor: move">
<li id="1" class="sortlist"><input type="hidden" name="uid" value="1">name 1</li>
<li id="2" class="sortlist"><input type="hidden" name="uid" value="2">name 2</li>
<li id="3" class="sortlist"><input type="hidden" name="uid" value="3">name 3</li>
<li id="4" class="sortlist"><input type="hidden" name="uid" value="4">name 4</li>
<li id="5" class="sortlist"><input type="hidden" name="uid" value="5">name 5</li>
</ul>
<script language="JavaScript">
Sortable.create("sortable_list");
</script>
<input value="Submit" type="submit" name="sub">
</form>

Of course if it were dynamic, the output would be more like:

<cfoutput query="myqueryname">
<li id="#myqueryname.uid#" class="sortlist"><input type="hidden" name="uid"
value="#myqueryname.uid#">#trim(myqueryname.username)#</li>
</cfoutput>

(No wrap in the LI line above--the PRE code puts it outside the display is all.)

>> Here is a full simple demo <<


Anybody should be able to copy the view-source from that and make it work.

Of course, this is actually the EASY part. It's working out all the dates and the looping that is considerably more work. That comes next!

PJ

10 comments:

Anonymous said...

I am having difficulty figuring out how to update the database with the new order list. If you loop through the new list how do you associate the new value to the correct record in the db?

Any suggestions would be greatly appreciated.

PJ said...

"I am having difficulty figuring out how to update the database with the new order list. If you loop through the new list how do you associate the new value to the correct record in the db?"

I know exactly what you mean - 'how do you hold multiple values 'together' on the script the form is posting to? - I will post an example on this later tonight when I get off work.

Best,
PJ

Anonymous said...

Awesome! I am looking forward to seeing the example. Thanks for the help!

Anonymous said...

Any luck with the example?

PJ said...

Sorry for the delay. I put an example as a new post today. - PJ

Anonymous said...

Thanks for the cloop post! I got everything working. Is there a way to automatically submit the form on onChange or onUpdate so I don't have to include a submit button?

Maya said...

How would you automatically submit this form after each drag/drop so the database gets updated right away?

PJ said...

"How would you automatically submit this form after each drag/drop so the database gets updated right away?"

The enter key. So you can click on the text which then becomes editable, type in what you like, and hit enter, and it submits the change without refreshing the page, and the text goes back to being 'regular text' again except whatever you entered.

Anonymous said...

I'm having problems to get the order list. Please help.

Anonymous said...

Can you tell me how to disable some of the lists?
If there are 5 lists and I don't want to change first one and third one, how can I do that?