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:

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.

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.

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.

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

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.

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.