Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (Feb. 2009) with: Aaron Foss

Appending An Array Of jQuery Objects To The DOM

By Ben Nadel on

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.




Reader Comments

I've always found that jQuery's

  • appendTo()

serves me better than a plugin would:

  • $(buffer).appendTo('ul.friends');

Usually, I'm creating the array and appending it to another element, so the

  • appendTo()

seems to match the general cases.

Of course, since I've learned how to use

  • appendTo()

, I'll admit that I'm probably biased towards making things work with

  • appendTo()

rather than try to work out another solution.

Reply to this Comment

@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.

Reply to this Comment

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);

Reply to this Comment

@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.

Reply to this Comment

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!

Reply to this Comment

@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.

Reply to this Comment

// 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(', ') );

Reply to this Comment

@Daniel
what you have is wrong it will add an extra Comma(,).
this is how I can see this code works.

Really Interesting :)

  • 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('\n') );

Reply to this Comment

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...)

Reply to this Comment

You can use jQuery's each function like this:

  • $.each(buffer,function(){
  • $("ul.friends").append(this);
  • });

to get the same results.

Reply to this Comment

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

Reply to this Comment

@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:

  • var fragment = $( document.createDocumentFragment() );

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.

Reply to this Comment

@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

Reply to this Comment

@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.

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.