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,238 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,238 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,238 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,238 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,238 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,238 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
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools