David Sills's muse is fueled by Bach, Beethoven, and Brahms (and Berlioz and Boulez). He has no life, which comports well with the über-geekitude to which he aspires. He reads and writes fitfully in English, Spanish, French, German, Italian (mostly medieval), C, VB, Perl.... Oh, and Java and XSLT, lots and lots of Java and XSLT. Trained somewhere in darkest Central America, he works today at DataSource, Inc., on the product team for the CASE tool Abri. David has posted 9 posts at DZone. View Full User Profile

An HTML List Builder: A Study in Applying jQuery

12.30.2008
| 36778 views |
  • submit to reddit
Movin' on Up
 
The algorithm for moving an entry up within a select is not complicated; there are two relevant methods in the HTML DOM Select object: remove and add. The way they work is that the remove method takes an index number (zero-based) and deletes the corresponding HTML DOM Option object from the select and renumbers the index numbers for the remaining Option objects. The add method takes the Option object to be added and an Option object already in the list before which the supplied option is to be inserted. If the second option is null, the first option is appended to the end of the list (that is to say, before the first empty entry).
 
For a single entry, when you remove an entry from the list, it is certain that either that option is first (at index 0) or it has a neighbor with an index lower by 1. The logic is simple: put a guardian to ensure that if the index of the option to be removed is 0, do nothing; if it is greater than 0, find this neighbor, and use it as the second parameter in the add method.
 
Again jQuery comes to the rescue, simplifying the process. First, we find the selected options to move (we'll return in a few paragraphs to exactly how to do this):
      var selected = ...;
If there are any, we check the first one for index 0; if the first has the index 0, we simply exit:
      if (selected.length > 0)
{
if (selected[0].index == 0)
{
return;
}
// main logic here
}
The comment above shows where the main logic goes. This loops through the selected options, calling a function that gets the index of the option and gets the Select object containing the whole list (again, we'll return in a few paragraphs as to how this is done), finds the previous option in the list, and calls the add and remove methods as described above:
        selected.each(function(index) {
var i = this.index;
var list = ...;
var previous = list.options[i - 1];
list.remove(i);
list.add(this, previous);
});
So far, so good, but how to attach this to a button? jQuery provides easy methods for binding functions as event handlers; the functions should take an event as a parameter, which won't be needed in this particular algorithm.
 
However, we don't want to cut and paste the logic twice, once for the left and once for the right button. Instead, we want to parameterize the function so as to be able to tell it which select we want at runtime. What to do?
 
The answer depends upon the nature of JavaScript, which treats functions differently from many familiar languages by not treating them differently from other objects. In other words, functions are simply objects you can access with a particular syntax. In fact, JavaScript functions are actually closures. A closure is a function that comes with an environment of "magically" assigned external values upon which it can operate. In the case of a JavaScript function, those magic external values consist of any variable that was in scope at the moment the function was created.
 
So we can use a function that returns the handler function we want. The logic is identical for both lists save for the id of the list; we will pass the id of the list into the outer function as a parameter:
  var moveUpHandler = function(selectId)
{
return function(event) {
var selected = $('#' + selectId + ' option:selected');
// logic as before
Now we can see how the list of selected options was retrieved: even though the moveUpHandler function is long finished and out of scope by the time the actual handler (the function it returns) fires, the value of the parameter selectId, since it was in scope when the handler was created, is still available for use in the jQuery selector.
 
Something similar is done inside the function applied to each of the selected options, since selectId is still in scope even there:
          var list = $('#' + selectId)[0];
Now the entire function returning the event handler can be seen together:
  var moveUpHandler = function(selectId)
{
return function(event) {
var selected = $('#' + selectId + ' option:selected');
if (selected.length > 0)
{
if (selected[0].index == 0)
{
return;
}
selected.each(function(idx) {
var i = this.index;
var list = $('#' + selectId)[0];
var previous = list.options[i - 1];
list.remove(i);
list.add(this, previous);
});
}
updateListBuilderState();
this.blur();
};
}
Note that at the end of the logic for moving values, we update the hidden input with the current state of the list builder and blur (that is, unfocus) the button. This last is a convenience, but the code reminds us of the many meanings of this in JavaScript: in the function applied to each of the selected option, this is the option; in the handler function, this is the button pressed.
 
Now assigning the handler to events fired by the left button is simple:
  var handler = moveUpHandler('leftSelect');
leftUpButton.click(handler).keyup(handler);
Assigning the handler to the right button is similar.

Legacy
Article Resources: 
Published at DZone with permission of its author, David Sills.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)