An HTML List Builder: A Study in Applying jQuery
Moving Down
More complicated is the functionality to move list entries down.
In particular, moving multiple entries down is complex. What, in
fact, does it mean?
The
question becomes interesting when you consider moving blocks of
entries. For example, consider a list of five entries (we'll call them
1, 2, 3, 4, and 5). Suppose you want to move all the entries but the
last down by one. There are at least two configurations with which you
might expect to wind up: 5, 1, 2 3, 4; and 1, 2, 3, 5, 4. The first
configuration takes the block of entries, maintains its integrity, and
allows the surrounding entries to flow around it. The second actually
reverses the problem: it considers the problem as taking the fifth
entry (the only one not originally selected to be moved) and moving
that entry up.
As posed so far, both definitions of what
constitutes moving multiple entries are reasonable. The problems with
the second definition are clarified, however, when the selections do
not constitute a block.
Suppose you select 1, 2, and 4 to move down.
According to the second definition, the result of the movement is 1, 3,
2, 5, 4. The first selection hasn't moved at all! If you try moving the
block up again using the logic above, nothing happens, since the first
element of the block has the index 0. This seems counter-intuitive.
Using the first definition results in the configuration 3, 1, 2, 5, 4,
which both seems more intuitive as a result and is easily reversed by
moving the block up according to the logic we have already seen.
Reversibility is an intuitively useful trait, so we will use the first
definition here.
To implement it, we will use the same methodology
described above to create a closure. First, we must ensure there is
something to be moved:
var selected = $('#' + selectId + ' option:selected');
if (selected.length > 0)
{
// logic here
}Then we must determine whether or not the last selected option is also last in the original list. If so, we will do nothing: var options = $('#' + selectId + ' option');
if (selected[selected.length - 1].index == options.length - 1)
{
return;
}Then we must determine what option each selection to be moved will eventually appear before (remember, this is how the add
function works). We'll simplify the problem to consider moving only one
selection at a time, and call the option before which this selection is
to be moved x. Our problem, as so often in high-school algebra, is to solve for x.
To
do this, we need a helper function that will allow us to distinguish
between options selected to be moved and those not selected. This is
not complicated:
var contains = function(list, value)
{
if (! value)
{
return false;
}
for (var i = 0; i < list.length; i++)
{
if (list[i] == value)
{
return true;
}
}
return false;
}
With this in place, we can write the actual logic. We need to hold on to two variables. The first is a candidate option x (remember, this is a candidate option for the second parameter to the add method). To initialize it, we simply count forward two options from the index of the selection to be moved, skipping the first.
The
second is the list of options having a higher index than the selection
to be moved. We loop through this list one option at a time: if the
option encountered is a member of the options selected to be moved,
skip the candidate x one further forward (this maintains the
relationships between the selected options) and go on to consider the
next option in the list. If we wind up past the end of the list, stop; x is null and we will append to the end of the list. Otherwise, stop when the index of the candidate x is greater than the index of the current option in the list.
var befores = new Array();Then, of course, the problem is a simple one:
for (var i = 0; i < selected.length; i++)
{
var currentIndex = selected[i].index;
var beforeIndex = currentIndex + 2;
var before = options[beforeIndex];
for (var j = 1; before; j++)
{
if (contains(selected, options[currentIndex + j]))
{
beforeIndex++;
before = options[beforeIndex];
}
else if (before.index > options[currentIndex + j].index)
{
break;
}
}
befores.push(before ? before : null);
}
selected.each(function(index) {
list = $('#' + selectId)[0];
list.remove(this.index);
list.add(this, befores[index]);
});Finally, we can see the whole thing together:var moveDownHandler = function(selectId)Again, we update the hidden input with the current state of the list builder and blur the button after moving. Assigning the handler to events fired by the left button is simple:
{
return function(event) {
var selected = $('#' + selectId + ' option:selected');
if (selected.length > 0)
{
var options = $('#' + selectId + ' option');
if (selected[selected.length - 1].index == options.length - 1)
{
return;
}
var befores = new Array();
for (var i = 0; i < selected.length; i++)
{
var currentIndex = selected[i].index;
var beforeIndex = currentIndex + 2;
var before = options[beforeIndex];
for (var j = 1; before; j++)
{
if (contains(selected, options[currentIndex + j]))
{
beforeIndex++;
before = options[beforeIndex];
}
else if (before.index > options[currentIndex + j].index)
{
break;
}
}
befores.push(before);
}
selected.each(function(index) {
list = $('#' + selectId)[0];
list.remove(this.index);
list.add(this, befores[index] ? befores[index] : null);
});
}
updateListBuilderState();
this.blur();
};
};
handler = moveDownHandler('leftSelect');
leftDownButton.click(handler).keyup(handler);Again, assigning the handler to the right button is similar.This meets requirements 8.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)
Tags:




