Appending An Array Of jQuery Objects To The DOM
Often times, when I am creating HTML in a thick-client application, I am merging the JSON (JavaScript Object Notation) data returned from an AJAX call with a client-side template. The resultant DOM (Document Object Model) nodes are then initialized and appended to the visible document tree. As an intermediary step in this process, I am typically creating a collection of jQuery objects, each of which represents a newly formed DOM node. The problem with this approach, however, is that the jQuery append() method does not play nicely with an array of jQuery objects.
To see what I'm talking about, let's take a look at some code. In the following demo, I'm creating a number of new LI nodes which will get appended to a UL element within the document. For the sake of simplicity, the LI nodes that we create are rather simple; but, imagine that in a more robust application, these nodes might require much more initialization.
<!DOCTYPE html>
<html>
<head>
<title>Appending An Array Of jQuery Objects To The DOM</title>
</head>
<body>
<h1>
Appending An Array Of jQuery Objects To The DOM
</h1>
<ul class="friends">
<!-- This will be populated dynamically. -->
</ul>
<script type="text/javascript" src="./jquery-1.6.3.js"></script>
<script type="text/javascript">
// I am a convenience method for creating a Friend node
// with the given name (returned as a jQuery object).
function createFriendNode( name ){
// Create the friend node.
return(
$( "<li>" + name + "</li>" )
);
}
// Create an array of friends.
var buffer = [];
buffer.push( createFriendNode( "Joanna" ) );
buffer.push( createFriendNode( "Lisa" ) );
buffer.push( createFriendNode( "Tricia" ) );
buffer.push( createFriendNode( "Kim" ) );
// Append the friends to the DOM.
$( "ul.friends" ).append( buffer );
</script>
</body>
</html>
As you can see, my "buffer" object is simply a native array of jQuery objects. And, when I attempt to append this array to the visible DOM tree, I get the following JavaScript error:
Could not convert JavaScript argument arg 0 [nsIDOMDocumentFragment.appendChild]
jQuery is pretty flexible when it comes to the type of data that it can append to the document; but, the one thing that it doesn't seem to account for is an array of jQuery objects.
To get around this, I have created a simple plugin - appendEach() - that takes an array of jQuery objects and collapses each individual collection into a single, flattened collection. This flattened collection is then append to the specified DOM element using the native append() method:
<!DOCTYPE html>
<html>
<head>
<title>Appending An Array Of jQuery Objects To The DOM</title>
</head>
<body>
<h1>
Appending An Array Of jQuery Objects To The DOM
</h1>
<ul class="friends">
<!-- This will be populated dynamically. -->
</ul>
<script type="text/javascript" src="./jquery-1.6.3.js"></script>
<script type="text/javascript">
// JQUERY PLUGIN: I append each jQuery object (in an array of
// jQuery objects) to the currently selected collection.
jQuery.fn.appendEach = function( arrayOfWrappers ){
// Map the array of jQuery objects to an array of
// raw DOM nodes.
var rawArray = jQuery.map(
arrayOfWrappers,
function( value, index ){
// Return the unwrapped version. This will return
// the underlying DOM nodes contained within each
// jQuery value.
return( value.get() );
}
);
// Add the raw DOM array to the current collection.
this.append( rawArray );
// Return this reference to maintain method chaining.
return( this );
};
// -------------------------------------------------- //
// -------------------------------------------------- //
// I am a convenience method for creating a Friend node
// with the given name (returned as a jQuery object).
function createFriendNode( name ){
// Create the friend node.
return(
$( "<li>" + name + "</li>" )
);
}
// Create an array of friends.
var buffer = [];
buffer.push( createFriendNode( "Joanna" ) );
buffer.push( createFriendNode( "Lisa" ) );
buffer.push( createFriendNode( "Tricia" ) );
buffer.push( createFriendNode( "Kim" ) );
// Append the friends to the DOM.
$( "ul.friends" ).appendEach( buffer );
</script>
</body>
</html>
As you can see, once we have built up our detached buffer of new DOM nodes (contained in individual jQuery wrappers), we are then using appendEach() in order to attach the buffer to the visible DOM tree.
When I am creating dynamic HTML fragments, I often like to create individual jQuery objects. This makes node initialization and event binding (when I'm not using delegate()) much easier. The problem with this approach (in addition to it being slower than direct HTML markup insertion) is that it leaves me with an array of jQuery objects, not DOM nodes. By using the appendEach() plugin, however, I can keep the code simple and readable while still getting the benefits of the jQuery API.
Want to use code from this post? Check out the license.
Reader Comments
I've always found that jQuery's
serves me better than a plugin would:
Usually, I'm creating the array and appending it to another element, so the
seems to match the general cases.
Of course, since I've learned how to use
, I'll admit that I'm probably biased towards making things work with
rather than try to work out another solution.
And wow, I guess I went a little overboard with the markup in the above comment. My apologies :-)
@Mike,
No worries - my Code tag assumes a block-level element. Something I've been meaning to fix for a while.
The issue, getting back to the code itself, though, is that I believe in my example, $(buffer) will also run into the same JavaScript issue. Essentially, any attempt to create a jQuery object from an array *of* jQuery objects seems to throw an error. That's why I needed to flatten all the DOM references into one array.
how about
// I am a convenience method for creating a Friend node
// with the given name (returned as a jQuery object).
function createFriendNode( name ){
// Create the friend node.
return(
$( "<li>" + name + "</li>" )[0];
//or $( "<li>" + name + "</li>" ).get(0);
);
}
// Create an array of friends.
var buffer = [];
buffer.push( createFriendNode( "Joanna" ) );
buffer.push( createFriendNode( "Lisa" ) );
buffer.push( createFriendNode( "Tricia" ) );
buffer.push( createFriendNode( "Kim" ) );
// Append the friends to the DOM.
$( "#jq-intro" ).append(buffer);
@Mahdi,
You could definitely do that. The only reason I shied away from that is that once I have an object wrapped in a jQuery container, I'd rather leave it there. This way, if I have to do further updates to it, I already have the jQuery API available.
That said, extracting the raw DOM node in the function would certainly do the trick.
yeah I agree, I could see using this nice plugin in larger projets not sure if $.map is faster than a regular for loop though!
@Mahdi,
Probably not. Every layer of abstraction adds some trade-off in performance. Something very satisfying about map() functions, though, at least for me :) I'm enjoying more functional programming concepts.
@Ben
thank you Ben I agree with you. I will add this plugin to my toolkit :)
// I am a convenience method for creating a Friend node
// with the given name (returned as a text).
function createFriendNode( name ){
// Create the friend node.
return "<li>" + name + "</li>";
}
// Create an array of friends.
var buffer = [];
buffer.push( createFriendNode( "Joanna" ) );
buffer.push( createFriendNode( "Lisa" ) );
buffer.push( createFriendNode( "Tricia" ) );
buffer.push( createFriendNode( "Kim" ) );
// Append the friends to the DOM.
$( "ul.friends" ).append( buffer.join(', ') );
@Daniel
what you have is wrong it will add an extra Comma(,).
this is how I can see this code works.
Really Interesting :)
@mahdi, Vous avez raison.
Également : $( "ul.friends" ).append( buffer.join("") );
J'étais parti de l'idée de l'opérateur "," : $( "ul.friends" ).append("<li>Joanna</li>","<li>Lisa</li>","<li>Tricia</li>","<li>Kim</li>");
In your email, this link www.bennadel.com/blog/2268-Appending-An-Array-Of-jQuery-Objects-To-The-DOM.htm?replyto=mahdi%20pedram#blogcommentform is broken (An error occurred.)
Interesting plugin,
One question though - why use an array as a holder, rather than a jQ object?
If you are going to be inserting one by one anyways, why not replace Buffer with an "off DOM" $(ul) ?
then you could just replace or reposition it into the page context as needed? Or if you need to maintain event bindings on the original element, just move the children?
(assuming you do the sorting either at backend, or need to support front end sorting of final jQ object anyways...)
You can use jQuery's each function like this:
to get the same results.
the fastest way of doing this is using document fragment.
so you don't need to add it an array.
but your code will be pure javascript
@Daniel,
Definitely, using a string buffer (array) is going to be the fastest way to build large portions of the DOM. However, with that speed comes a bit of a trade-off in that you no longer get to be able to leverage the jQuery API if you wanted to do things like attach data() or event bindings.
I'm not saying that's bad - just a trade-off.
Also, around 4AM (when you posted my time), my site typically gets poor performance (hence your error most likely). I think that is when I get indexed by spiders.
@Atleb,
That's a super interesting suggestion. I like it!
@JKirchartz,
Also a good suggestion; though, you could easily create a plugin from that so as to reduce the code (assuming you might re-use this approach in multiple places).
@Mahdi,
It's funny you mention Fragments; I tried to following the jQuery code that powers append()... it's really hard for me to follow... but, it looks like they are creating a fragment for you in order to increase speed. There are checks, in the library, to see if the object you passed in IS a fragment; and if it is not, it looks like it creates one explicitly.
But, like I said, I had a lot of trouble following the jQuery internals - they are so intensely compact!
Also, when I started this code, I did try using:
However, it seems that while that particular line works without error, any subsequent calls to fragment.append() will not actually add objects to the collection.
From the jQuery library code, I believe it checks the nodeType before executing the append. And, the fragment, which has nodeType = 11, fails the check and the append() is ignored.
@ben
I need to research more on how jquery deals with document fragments.
this Looks like a plugin to me :) I will try to write it later tonight.
Thanks alot
@Mahdi,
Sounds good to me. I always have trouble deducing what the jQuery code is doing. So, any light you can shed is much appreciated.
@Ben
I wrote a small plugin that deals with fragments.
apparently jquery doesn't support it. they found it more confusing so the append only accepts Element Nodes.
please take a look at this and let me know what you think.
this small plugin supports both jQuery Dom Objects and native javascript Dom Objects.
http://jsfiddle.net/RZfZ8/1/
Thank you.
@Mahdi,
It looks like jsFiddle is down for maintenance :) I'll have to check back later.