Appending An Array Of jQuery Objects To The DOM

Posted October 7, 2011 at 10:42 AM by Ben Nadel

Tags: Javascript / DHTML

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

Oct 7, 2011 at 1:34 PM // reply »
6 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.


Oct 7, 2011 at 1:35 PM // reply »
6 Comments

And wow, I guess I went a little overboard with the markup in the above comment. My apologies :-)


Oct 7, 2011 at 1:49 PM // reply »
11,314 Comments

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


Oct 7, 2011 at 2:21 PM // reply »
10 Comments

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


Oct 7, 2011 at 2:25 PM // reply »
11,314 Comments

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


Oct 7, 2011 at 2:29 PM // reply »
10 Comments

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!


Oct 7, 2011 at 2:42 PM // reply »
11,314 Comments

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


Oct 7, 2011 at 2:44 PM // reply »
10 Comments

@Ben

thank you Ben I agree with you. I will add this plugin to my toolkit :)


Oct 7, 2011 at 4:59 PM // reply »
2 Comments

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


Oct 7, 2011 at 5:10 PM // reply »
10 Comments

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


Oct 8, 2011 at 4:26 AM // reply »
2 Comments

@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 http://www.bennadel.com/blog/2268-Appending-An-Array-Of-jQuery-Objects-To-The-DOM.htm?replyto=mahdi%20pedram#blogcommentform is broken (An error occurred.)


Oct 8, 2011 at 6:08 AM // reply »
13 Comments

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


Oct 11, 2011 at 9:58 AM // reply »
2 Comments

You can use jQuery's each function like this:

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

to get the same results.


Oct 12, 2011 at 11:28 AM // reply »
10 Comments

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


Oct 12, 2011 at 1:51 PM // reply »
11,314 Comments

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


Oct 12, 2011 at 2:15 PM // reply »
10 Comments

@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


Oct 12, 2011 at 2:24 PM // reply »
11,314 Comments

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


Oct 12, 2011 at 6:28 PM // reply »
10 Comments

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


Oct 29, 2011 at 7:22 PM // reply »
11,314 Comments

@Mahdi,

It looks like jsFiddle is down for maintenance :) I'll have to check back later.



Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 19, 2013 at 11:33 AM
Filter vs. ngHide With ngRepeat In AngularJS
In your assessment, is it correct to say that given a list of say 500 items its more performant to use the `ngHide` method over the `filter` method? ... read »
Jun 19, 2013 at 10:18 AM
ColdFusion Path Usage And Manipulation Overview
Anyone happen to know if the file created by getTempFile will be automatically removed at any point? Nothing mentioned in the docs, and restarting CF doesn't remove them, so it seems it needs manu ... read »
Jun 19, 2013 at 9:41 AM
Working With Inherited Collections In AngularJS
I actually just ran into this same situation with a demo I was putting together. Your implementation of multi-lvl $scope's > Mine :) ... read »
Jun 19, 2013 at 8:17 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Prateek, to match a word or text you should use .toContain('word') that's a jasmine reference. website is : http://pivotal.github.io/jasmine/ ... read »
Jun 19, 2013 at 8:10 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Hi Guys, Actually i am doing e2e test of angular js of my project but i am not getting one thing that is how to press enter key through the test when my form is filled as i am not using a button but ... read »
Jun 18, 2013 at 9:20 PM
Mapping AngularJS Routes Onto URL Parameters And Client-Side Events
I couldn't find examples of passing multiple arguments using the when() routing statement so figured out through trial and error that you can pass multiple arguments using the following format: .whe ... read »
Jun 18, 2013 at 3:39 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
Hi Ben, THANKS! While not bleeding edge, it is new to me & I like learning new things every day! ... read »
Jun 18, 2013 at 12:30 PM
Disabling Auto-Correct And Auto-Capitalize Features On iPhone Inputs
Also spellcheck="false" should be mentioned as part of html5 specs ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools