Scripts Tags Get Moved Only During Window DOM Node HTML Injection

Posted June 19, 2009 at 10:17 AM by Ben Nadel

Tags: Javascript / DHTML

Last week, I posted that performing HTML-based AJAX with jQuery moves Script tags around when injecting HTML into the DOM. At the time, it was unknown to me as to when this tag movement actually took place - when the raw HTML got parsed or when it was injected into the current document object model (DOM). After some further experimentation, it became clear that the Script tags were moved around during the actually DOM injection; simply parsing the raw HTML into a jQuery node collection did not have any affect. What this means is that you can still use the contextual features of Script-based meta data so long as you apply it pre-DOM-injection.

 
 
 
 
 
 
 
 
 
 

As you can see from the video, testing this consists of dynamically populating an unordered list (UL) using an AJAX request. The list items (LIs) that come back from the AJAX request each contain a Script-based meta data JSON value that should be applied to the parent element (the LI):

  • <li>
  • <script type="text/x-json" class="meta-data">
  • {
  • id: 1
  • }
  • </script>
  • <a>Click me to get ID</a>
  • </li>
  • <li>
  • <script type="text/x-json" class="meta-data">
  • {
  • id: 2
  • }
  • </script>
  • <a>Click me to get ID</a>
  • </li>
  • <li>
  • <script type="text/x-json" class="meta-data">
  • {
  • id: 3
  • }
  • </script>
  • <a>Click me to get ID</a>
  • </li>

Notice that there is nothing tying the Script tag to the parent node other than its context (ie. we're not using any REL attribute or other relational markers).

Here is the page that requests the above HTML via jQuery ajax:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>jQuery And Script Tags With AJAX Data</title>
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • // I am a jquery plugin that looks for script tags of class
  • // meta-data and applies the JSON to its parent element in
  • // the metaData key.
  • jQuery.fn.applyMetaData = function(){
  • // Find all the embedded script meta-data tags and
  • // iterate of them to individually apply them to the
  • // direct parent node.
  • this.find( "script.meta-data" ).each(
  • function( nodeIndex, scriptTag ){
  • var script = $( this );
  • var parent = script.parent();
  • var metaData = {};
  •  
  • // Try to evaluate the JSON meta data.
  • try {
  • metaData = eval( "(" + script.html() + ")" );
  • } catch ( error ){
  • // JSON was not valid.
  • }
  •  
  • // Store meta data into parent.
  • parent.data(
  • "metaData",
  • jQuery.extend(
  • {},
  • parent.data( "metaData" ),
  • metaData
  • )
  • );
  • }
  • )
  •  
  • // Once the script tags have been processed,
  • // remove them from the DOM.
  • .remove()
  • ;
  •  
  • // Return the collection without the script tags.
  • return( this.not( "script.meta-data" ) );
  • }
  •  
  •  
  • // I handle the html data response from the AJAX request.
  • function populate( htmlData ){
  • var html = $( htmlData ).applyMetaData();
  •  
  • // Put HTML into DOM.
  • $( "#list" )
  • .empty()
  • .append( html )
  • ;
  •  
  • // Bind click handlers.
  • $( "#list a" )
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( clickEvent ){
  • // Alert parent ID.
  • alert( $( this ).parent().data( "metaData" ).id );
  •  
  • // Prevent default event.
  • return( false );
  • }
  • )
  • ;
  • }
  •  
  •  
  • // When DOM loads, initialize.
  • $(
  • function(){
  • // Grab the remote data via AJAX request.
  • $.ajax({
  • type: "get",
  • url: "script3_data.htm",
  • dataType: "html",
  • success: populate
  • });
  • }
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery And Script Tags With AJAX Data
  • </h1>
  •  
  • <ul id="list" />
  •  
  • </body>
  • </html>

Notice that the first thing I do in the AJAX response handler - populate() - is to parse the HTML into a jQuery node collection and then immediately call the applyMetaData() plugin on the collection:

  • var html = $( htmlData ).applyMetaData();

Because simply parsing the HTML does not move the script tags around, we know that the call to $( htmlData ) will leave the Script tags in the same position they had in the raw HTML data. This fact allows the applyMetaData() plugin to search the resultant jQuery collection for Script tags of class "meta-data". Then, using context-only, the applyMetaData() plugin gets a reference to the parent element of the given Script tag and appends the Script's JSON data to the parent tag's metaData (using jQuery's data() method).

Once the jQuery applyMetaData() plugin has applied the Script tag JSON information, it strips the Script tags out of the transient DOM and then, for safe measure, returns a new jQuery collection that filters out any Script tags of class "meta-data". This resultant collection is then injected into the window DOM and the new link click events are bound to alert the ID of the their parent element's metaData id.

When I first learned about the Script-based meta data concept, it was the contextual nature of the Script tags that really blew my mind. When I thought last week that perhaps this contextual nature could not be used in conjunction with dynamically retrieved AJAX data, it definitely felt like a bit of a defeat. Knowing now, however, that the Script tag context is preserved in transient document object models and is only moved during primary DOM injection, I am feeling much more confident in this approach to meta-data delivery.




Reader Comments

Jun 22, 2009 at 3:16 AM // reply »
2 Comments

It's so funny that I found the same issue on the same date of your post.

My colleague found out this is caused by jQuery not strictly checking for the type attribute. It is around Line 956 of jQuery-1.3.2 source.

Could this be considered a jQuery bug?


Jun 22, 2009 at 3:46 AM // reply »
1 Comments

Around line 955 in jQuery v1.3.2:

if ( ret[i].nodeType === 1 )
ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
fragment.appendChild( ret[i] );

Replace it with:

if ( ret[i].nodeType === 1 ) {
var scriptChildren = jQuery.makeArray(ret[i].getElementsByTagName("script"))
for (var j = 0; j < scriptChildren.length; j++) {
if (scriptChildren[j].type && scriptChildren[j].type.toLowerCase() !== 'text/javascript') {
scriptChildren.splice(j, 1);
j--;
}
}
ret.splice.apply( ret, [i + 1, 0].concat(scriptChildren) );
}
fragment.appendChild( ret[i] );

This will solve the problem.


Jun 22, 2009 at 4:18 AM // reply »
2 Comments

I've opened a ticket and a patch:

http://dev.jquery.com/ticket/4801


Jun 23, 2009 at 5:58 PM // reply »
11,246 Comments

@Aaron, @Rainux,

Very interesting. I never even thought of looking in the jQuery library itself for the source of the issue! Hmmm. Yeah, I guess that would be considered a bug. Nice catch!


Jun 23, 2009 at 9:33 PM // reply »
2 Comments

Ben,
Nice approach!
If we have schema AJAX - [XML] - XSLT - [XML] - [applyMetaData] - inject.
Both XSLT and [applyMetaData] performs some kind of transformation.
XSLT - do specific to request and [applyMetaData] is same for all requests.
Could anything be done on XSLT phase to simplify things?
I mean, it's good enough already, but still? out of curiosity?


Jun 24, 2009 at 8:21 AM // reply »
11,246 Comments

@Oleg,

I am not sure. Since you need to apply the JSON via the jQuery data() method, I am not sure that the XSLT phase will add much.


Jun 24, 2009 at 2:42 PM // reply »
2 Comments

Ben,
Thank for your reply.
In my schema AJAX calls XLM web service which is shared by many different applications.
XSL transformation is our standard phase - and that's how we decouple "data" from "presentation" (partially) and designers from programmers as well (almost 100% ;-).
Hence was my question to insert [applyMetaData] into XSL phase.
Obviously XSL cannot generate any java script objects?
Fortunately [applyMetaData] step is the same - so we have "no problemo" there!


Jun 24, 2009 at 3:25 PM // reply »
11,246 Comments

@Oleg,

I don't understand 100%, but ultimately, applyMetaData() has to be executed in the Javascript, which part of the rendered content.


Jul 19, 2010 at 12:05 PM // reply »
1 Comments

ok, so far I've come to the conclusion that jQuery 1.4.2 moves the script tags when parsed rather then on DOM insertion.

Also jQuery 1.4.2 has gone crazy where if you alert/console.log the return html from the ajax as a jquery object, you will see the script tags, but if you try to "grab" one, it will seemingly not exist.


Jul 20, 2010 at 9:26 PM // reply »
11,246 Comments

@Liam,

Hmm. I wonder if this behavior has switched in the latest releases of jQuery.


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 25, 2013 at 10:01 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Avi, Really glad to help! @Jaredwilli, I'm finding a this image hits home with a lot of people :) Hopefully we can all work through the rough patches together! @Prateek, AngularJS has error ... read »
May 25, 2013 at 9:53 PM
Nested Views, Routing, And Deep Linking With AngularJS
@Mrsean2k, I'm glad I could help! I haven't been able to keep up with the ui-router stuff. I keep saying that I'll carve out time, but I just haven't gotten to it :( ... read »
May 25, 2013 at 9:49 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, Thanks for the book recommendations. I am looking them up right now. I can see that Object Thinking is available for the Kindle App - sweet! Also, I just recently heard Martin Fowler on the ... read »
May 25, 2013 at 9:41 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
@Chris, I'm super excited to hear that my posts are helpful. I am also loving AngularJS; but, it definitely has some caveats and some odd behaviors and some things that just don't seem to "wor ... read »
May 25, 2013 at 9:36 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam, @Jason, After reading these comments, I double-checked my latest implementation and I am happy to report that I am using listFirst() and listRest(). ... read »
May 25, 2013 at 9:31 PM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Daxesh, I am not sure I understand the question about the current node. If you already have a reference to the current node, why would you need to query for it? As for parent node, I believe that ... read »
May 25, 2013 at 10:08 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Ben, my question is that i want the current node with its tag and its parent node. i just want only that data. So, give me the solution for that. and remember solution is working on " xpath 1.0 ... read »
May 25, 2013 at 10:01 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
hey ben, i want get my current node tag and also want the root node tag withing. So, how can i fix it.. ! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools