10 Oct 2010

jQuery: Automatic HTML element manipulation

Source code

When working on my latest project I discovered a feature of jQuery that wasn’t apparent to me in the API.  When using the .append() method I noticed it would automatically close HTML elements.  After taking a closer look at the jQuery 1.4.2 code, I found that .append() makes use of an internal function called domManip(). Other similar methods that also use domManip() are:

I created a few tests to see exactly how jQuery’s domManip() handles different scenarios and how it can cause frustration if misunderstood. Each of the tests have a yellow ‘parent’ DIV containing some text. In each test I use jQuery’s .append() method to add a red ‘child’ DIV with text, but in a few different ways. Let’s begin!

Test 1: Missing closing tag

HTML Before

<p>Test if .append() automatically closes a HTML element.</p><div id="test1" class="parent-container">
   <p>parent-container</p>
</div>

 jQuery JavaScript

$('#test1').append('<div class="child-container"><p>child-container</p>');

You can see we are going to append a small HTML string that is missing the closing </div> tag.

HTML After

<p>Test if .append() automatically closes a HTML element.</p>
<div id="test1" class="parent-container">
   <p>parent-container</p>
   <div class="child-container">
      <p>child-container</p>
   </div>
</div>

Here the added HTML can be seen in red.  jQuery automatically added the closing </div> tag.  This ensures the DOM remains valid for all jQuery DOM manipulations.

Very nifty!  🙂

The result (Firefox)

jQuery .append() test 1

Everything’s in good order after jQuery added the missing closing tag to maintain a valid DOM.

Test 2: Extra ‘orphan’ closing tag

HTML before

<p>Test if .append() automatically removes an orphan HTML element.</p>
<div id="test2" class="parent-container">
   <p>parent-container</p>
</div>

 jQuery JavaScript

$('#test2').append('<div class="child-container"><p>child-container</p></div><strong></div></strong>');

In this test, I’ve added an extra closing </div> tag.

HTML after

<p>Test if .append() automatically removes an orphan HTML element.</p>
<div id="test2" class="parent-container">
   <p>parent-container</p>
   <div class="child-container">
      <p>child-container</p>
   </div>
</div>

The result (Firefox)

jQuery .append() test 2

Once again, everything’s in place and the HTML is clean thanks to jQuery.  While test 1 and 2 are very good to ensure a valid DOM, it can also lead to programming mistakes if misunderstood as we’ll see next.

Test 3: The trap with nesting HTML elements

HTML before

<p>Test if .append()'s element manipulation breaks line-for-line nesting.</p>
<div id="test3" class="parent-container">
   <p>parent-container</p>
</div>

jQuery JavaScript

$('#test3').append('<div class="child-container">');
$('#test3').append('<p>child-container paragraph one.</p>');
$('#test3').append('<p>child-container paragraph two.</p>');
$('#test3').append('<p>child-container paragraph three.</p>');
$('#test3').append('</div>');

In this test, we’re adding multiple lines of HTML.  If jQuery were to append this verbatim, as text, this would be fine.  However jQuery .append() has parsed each line as a separate DOM injection and tried to correct ‘mistakes’ without any foresight…

HTML after

<p>Test if .append()'s element manipulation breaks line-for-line nesting.</p>
<div id="test3">
   <p>parent-container</p>
   <div class="child-container"></div>
   <p>child-container paragraph one.</p>
   <p>child-container paragraph two.</p>
   <p>child-container paragraph three.</p>
</div>

The first .append() was automatically given it’s missing closing </div> tag – no what we wanted!  The the last .append() removed our closing </div> tag because it seemed to be an orphan!  It’s obvious now that .append() and similar jQuery functions shouldn’t be used line-for-line like this.

The result (Firefox)

jQuery .append() test 3

Test 4: The simple solution

HTML before

<p>The neat solution to Test 4.</p>
<div id="test4" class="parent-container">
   <p>parent-container</p>
</div>

jQuery JavaScript

var htmlString = '<div class="child-container">';
htmlString +=    '<p>child-container paragraph one.</p>';
htmlString +=    '<p>child-container paragraph two.</p>';
htmlString +=    '<p>child-container paragraph three.</p>';
htmlString +=    '</div>';
$('#test3').append(htmlString);

The solution is to append everything in one go.  This is made neat and tidy using htmlString in the example.

HTML after

<p>The neat solution to Test 4.</p>
<div id="test4">
   <p>parent-container</p>
   <div class="child-container">
      <p>child-container paragraph one.</p>
      <p>child-container paragraph two.</p>
      <p>child-container paragraph three.</p>
   </div>
</div>

The result (Firefox)

jQuery .append() test 4

Here we’ve finally achieved the result we were after.

While the concept and tests are simplistic, the background DOM manipulation by jQuery is very important to understand. I was unable to find this sufficiently documented anywhere, so hopefully this can help a few other green jQuery developers like myself 😉

Further reading:

http://api.jquery.com/category/manipulation/dom-insertion-inside/

About the Author:

Hardware and software engineer with experience in product development and building automation. Director at Cabot Technologies and Product Manager at NEX Data Management Systems.

2 comments

  1. Ryan

    Just wanted to make a note that using the string building method has a HUGE performance benefit. There is a bit of overhead involved with jQuery’s append() method: DOM parsing, code corrections, DOM manipulation… etc. By reducing the amount of calls to append(), we are greatly increasing our program’s performance.

    • I agree that building a string as much as possible before inserting it improves performance and is a good rule of thumb. It’s always a balance between the convenience of jQuery’s checks and balances and the performance hit that comes with them. I didn’t mention performance in this post, but a benchmark of different methods would be interesting to see.

      String building methods in JavaScript will always be a matter of contention though! 😉

Leave a Reply to Ryan Cancel Reply

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.