Introduction
I happened to stumble upon a jQuery discussion forum, where a user, requesting for help, wanted to know how to swap two items in a jQuery object. The answers that tried to address the problem were not satisfactory in my opinion and with this article, I will present my solution to the issue.
First, let me present the original scenario. The user has a table with some rows (tr
) and each row has up and down buttons. When the client clicks on a button, the user uses jQuery ($.insertAfter()
, $.insertBefore()
) to move that row up or down inside the table.
He also has a jQuery object that holds all the rows ($("table").find("tr")
). What that user wanted was to swap places inside that jQuery object without selecting again. He argued that any re-selecting will result in the same jQuery object
as before with the exception of two rows with swapped places. So any re-selecting will be redundant.
The various solutions, presented in that discussion forum, had some intrinsic problems with them. First, there's an assumption that the jQuery object holds DOM elements. Obviously from the way the scenario was presented, that assumption is correct, but it also prevented for a more generic solution to emerge. Most of the solutions used jQuery functions such as $.replaceWith()
, $.detach()
, $.insertAfter()
, $.insertBefore()
. What happens when the jQuery object holds numbers $([0, 1, 2, 3, 4, 5])
? Second, some solutions used cloning ($.clone()
). What they did, in some form or other, was to clone an item, then removed it and inserted the clone to a new location. The problem with cloning is that you get a whole new object with different jQuery id and if you're not too careful, you might lose any events attached to the original item.
Swap Items in Array Object
Before we proceed to understand how to swap items in jQuery object, we must be reminded how to swap items in JavaScript Array
object. Array
object has a function called splice
. splice
removes certain number of items (could be 0 items) from a certain index and them inserts new items, into the array, in that index. splice
returns an array of removed items. You can read more about it in w3Schools JavaScript Array splice() Method. Here's an example of how to swap two items in an Array
object.
var arr = [0, 1, 2, 3, 4, 5];
var index1 = 4;
var index2 = 1;
var item2 = arr[index2];
var item1 = arr.splice(index1, 1, item2)[0];
arr.splice(index2, 1, item1);
alert(arr);
And with a single line of code.
var arr = [0, 1, 2, 3, 4, 5];
var index1 = 4;
var index2 = 1;
arr.splice(index2, 1, arr.splice(index1, 1, arr[index2])[0]);
alert(arr);
Swap Items in jQuery Object
We are going to leverage the function $.toArray()
to retrieve an array with the elements that are contained in the jQuery object. Then use Array splice
to swap items, and then wrap it again in jQuery. Here's the table
that we'll use (nothing fancy).
<table>
<tr><td>tr 0</td></tr>
<tr><td>tr 1</td></tr>
<tr><td>tr 2</td></tr>
<tr><td>tr 3</td></tr>
<tr><td>tr 4</td></tr>
<tr><td>tr 5</td></tr>
</table>
And this is the code that swaps two tr
in positions 1 and 4. I added a little alert
at the end for readability.
var $trItems = $("tr");
var index1 = 4;
var index2 = 1;
var trArr = $trItems.toArray();
trArr.splice(index2, 1, trArr.splice(index1, 1, trArr[index2])[0]);
$trItems = $(trArr);
var message = "";
$trItems.each(function(index, tr) {
if (index > 0) message += "\n";
message += $(tr).find("td").text();
});
alert(message);
swap Function
We want to generalize what we did before, add some validity checks and wrap it all in a function.
function swap($items, index1, index2) {
if (index1 < 0 || index1 >= $items.length)
return $items;
if (index2 < 0 || index2 >= $items.length)
return $items;
if (index1 == index2)
return $items;
var items = $items.toArray();
items.splice(index2, 1, items.splice(index1, 1, items[index2])[0]);
$items = $(items);
return $items;
}
The Fly in the Ointment
There is one cavity with what we did so far. The jQuery object that we return from the swap function is different from the jQuery object that we passed as parameter. They will have different jQuery id. When we swap items in Array
object, we swap them in-place, meaning inside the Array
itself. However, this is not possible in jQuery. Every time that we try to change a jQuery object - not even change, but try to change - we get a new jQuery object. Its content may be different from or same as the original jQuery object, but it is a different object altogether. To illustrate this point, let's look at this code:
var obj1 = $([0, 1, 2, 3, 4, 5]);
alert(obj1.length);
var obj2 = obj1.filter(function(index, element) {
return element <= 3;
});
alert(obj1.length);
alert(obj2.length);
alert(obj1 === obj2);
The filter changes the jQuery object (in this case, reduces the number of elements) and returns a new jQuery object. To illustrate this point further more, let's look at a filter that doesn't filter at all.
var obj1 = $([0, 1, 2, 3, 4, 5]);
alert(obj1.length);
var obj2 = obj1.filter(function(index, element) {
return true;
});
alert(obj1.length);
alert(obj2.length);
alert(obj1 === obj2);
This filter returns true
for all elements so no element is filtered out. The contents of obj1
and obj2
are the same but obj1
and obj2
are completely different jQuery objects.
Once we understand this, we can see where the swap
function could cause our code to be error-prone. The swapping is not performed in-place the jQuery object. We must remember to update all references to the result that returns from swapping two items.
var trItems1 = $("tr");
var trItems2 = trItems1;
var index1 = 4;
var index2 = 1;
trItems1 = swap(trItems1, index1, index2);
alert(trItems1 === trItems2);
trItems2 = trItems1;
alert(trItems1 === trItems2);