Using Script Tags As Data Contains In AJAX-Powered jQuery

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

Tags: Javascript / DHTML

Last week, I blogged about how cool it was that the jQuery metadata plugin used Script tags to store application meta-data. I liked this for a number of reasons including the mime-typed nature of the script tags and their contextual embedding. After demonstrating the script tag meta-data use in a static way, I thought it would be good to demonstrate it in an AJAX-powered way. In this example, I am going to be pulling down HTML table rows with embedded meta-data script tags:

 
 
 
 
 
 
 
 
 
 

As you can see in the data, I start off with an HTML table that only has its THEAD defined. When the DOM is ready to be interacted with, I then use jQuery to pull down the table's TBODY. This TBODY contains not only the row data, it also contains Script tags that contain row meta-data. Because each Script tag contains data for a given row, I am returning a Script tag for each TR:

  • <!--- Bulid the query to be used in the demo. --->
  • <cfset qGirl = QueryNew( "" ) />
  •  
  • <!--- Add the ID column. --->
  • <cfset QueryAddColumn(
  • qGirl,
  • "id",
  • "cf_sql_integer",
  • ListToArray( "1,2,3,4,5" )
  • ) />
  •  
  • <!--- Add the name column. --->
  • <cfset QueryAddColumn(
  • qGirl,
  • "name",
  • "cf_sql_varchar",
  • ListToArray( "Libby,Molly,Sarah,Kim,Kit" )
  • ) />
  •  
  •  
  • <!--- Return the rendered HTML. --->
  • <cfoutput>
  •  
  • <tbody>
  • <cfloop query="qGirl">
  •  
  • <!---
  • We need to create a unique ID for the row so that
  • the JSON meta data can be associated with it.
  • --->
  • <cfset strRowID = "row-#CreateUUID()#" />
  •  
  • <!--- Send meta data. --->
  • <script
  • type="text/x-json"
  • rel="#strRowID#"
  • class="metadata">
  • {
  • id: #qGirl.id#,
  • name: "#HtmlEditFormat( qGirl.name )#"
  • }
  • </script>
  •  
  • <tr id="#strRowID#">
  • <td>
  • #qGirl.id#
  • </td>
  • <td>
  • #qGirl.name#
  • </td>
  • <td>
  • <a class="delete">Delete</a>
  • </td>
  • </tr>
  •  
  • </cfloop>
  • </tbody>
  •  
  • </cfoutput>

While the Script tags are created contextually to the TR to which they refer, I have to give the TR a unique ID and pass that via the REL attribute of the Script tag. This is because the Script tags are moved around by the browser when the html is appended to the table. As you can see from the video, after our append() method, the Script tags are all added to the table itself and are no longer accessible in the context of the associated TR. The REL attribute allows us to gather the script tags from their new location and still associate their data with the original row.

The association of this meta-data is done by the jQuery code that handles the AJAX response:

  • <!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-Table Data</title>
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM has loaded, gather the table data.
  • $(
  • function(){
  • $.ajax(
  • {
  • method: "get",
  • url: "./scripts_data.cfm",
  • dataType: "html",
  • success: populateTable
  • }
  • );
  • }
  • );
  •  
  •  
  • // I take the AJAX response and populate the table with
  • // initailized records.
  • function populateTable( strHTML ){
  • var jTable = $( "#table" );
  •  
  • // Append raw HTML to the table. We can initialize the
  • // html data after we append it.
  • //
  • // NOTE: After we append the HTML, the Script tags
  • // will have been stripped out and placed at the
  • // bottom of the table!
  • jTable.append( strHTML );
  •  
  • // Log table HTML for demo.
  • console.log( jTable.html() );
  •  
  • // Bind the meta data (remember, script tags became
  • // children of the table itself).
  • jTable.find( "> script.metadata" ).each(
  • function( intI, objScript ){
  • var jThis = $( this );
  • var jRow = $( "#" + jThis.attr( "rel" ) );
  •  
  • // Bind the meta data to the row.
  • jRow.data(
  • "metadata",
  • eval( "(" + jThis.html() + ")" )
  • );
  • }
  • );
  •  
  • // Gather records.
  • var jRows = jTable.find( "tbody > tr" );
  •  
  • // Now that the meta data is bound to the record,
  • // let's hook up the row action links.
  • jRows.find( "a.delete" )
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( objEvent ){
  • var jRow = $( this ).parents( "tr:first" );
  •  
  • // This doesn't really do anything - just
  • // for demonstration purposes. Showing
  • // how we can grab meta data from the row.
  • confirm(
  • "Are you sure you want to delete " +
  • jRow.data( "metadata" ).name +
  • " from the table?"
  • );
  •  
  • // Prevent default event.
  • return( false );
  • }
  • )
  • ;
  • }
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery And Script Tags With AJAX-Table Data
  • </h1>
  •  
  • <table id="table" cellspacing="1" border="1" cellpadding="5">
  • <thead>
  • <tr>
  • <th>
  • ID
  • </th>
  • <th>
  • Name
  • </th>
  • <th>
  • Actions
  • </th>
  • </tr>
  • </thead>
  •  
  • <!--- Table data will loaded via AJAX. --->
  • </table>
  •  
  • </body>
  • </html>

As you can see, after the AJAX response HTML is appended to the table, we loop over the Script tags and associate the JSON meta-data with the target TR using jQuery's data() method. Once this is done, we can easily gather the action links in each row and perform actions based on the associated meta-data.

I was a bit irritated and disappointed that the Script tags were stripped out of the HTML when it was appended to the table. I feel that this hurts the usefulness of script-based data storage. I will keep doing some experimentation to see if I can find a way to stop that from happening. But, until then, this code was a nice demonstration of how I was thinking about using the Script tags as data containers in AJAX-powered applications.




Reader Comments

Jun 9, 2009 at 8:19 AM // reply »
26 Comments

Interesting.

I've been playing with the idea of metadata Javascript objects to make DOM manipulation easier. I think I like this approach better than what I was doing, though.


Jun 9, 2009 at 8:29 AM // reply »
11,238 Comments

@Matt,

I'm still playing around with it. I'm not thrilled with the way that the script tags get moved around. I find it somewhat shady :)


Jun 9, 2009 at 8:52 AM // reply »
26 Comments

@Ben

It seems to me that if you're using the rel attribute and maybe storing a hashmap in the metadata, the position in the page souldnt matter as much.

Something I'm curious to play with.


Jun 9, 2009 at 8:56 AM // reply »
11,238 Comments

@Matt,

Yeah, that seems to be the way to solve this. From my experimentation, it seems that the script tags are always stripped out and added after the top-most element in the AJAX data.


Jun 11, 2009 at 3:15 PM // reply »
6 Comments

Hey Ben,

You can post Javascript along with your HTML if you're willing to do a little extra work on the server side. Something like this:

---- PAGE.CFM ---
<HTML>
<head>
<script src="script.cfm"></script>
<script>
function callback(content)
{
// do stuff with my content here...
// or call foo() from my included script
}
</script>
</head>
<body>
<cf_whatever>
</cf_whatever>
</body>
</HTML>

---- SCRIPT.CFM ---
<cfoutput>
// This file includes a regular javascript, and a bunch of HTML embedded in a callback function.
function foo(bar) {return bar++;}
callback("#JsStringFormat(myHtmlContentHere)#");
</cfoutput>


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

@Ben,

Yeah, you can definitely pass HTML and Javascript along. And, what's nice is that in jQuery, the DOM ready method:

$( function(){} );

... will fire immediately if inside that Javascript.


Dec 9, 2009 at 6:08 PM // reply »
3 Comments

Thanks for this, helped prevent me from pulling out more of my hair.

My situation is that I am writing an jQuery-AJAX-powered facebook app which individually renders XFBML elements using facebook's js library. Example content injected into the DOM:

<code>
<div id="myXfbml">
<script type="text/fbml">
<fb:fbml>
My FBML elements...
</fb:fbml>
</script>
</div>
<script type="text/javascript">
$(document).ready(function () {
// Add XFBML elements on the fly
FB_RequireFeatures(["XFBML"], function(){
FB.Facebook.init();
FB.XFBML.Host.autoParseDomTree = false;
FB.XFBML.Host.addElement(new FB.XFBML.ServerFbml(document.getElementById('myXfbml')));
});
});
</script>
</code>

Since jQuery moves the text/fbml to the bottom of the content, nothing gets rendered.

My fix was to HTML comment out the text/fbml script tags, and then uncomment them immediately before rendering them. jQuery won't move em if they're commented out:

<code>
<div id="myXfbml">
<script type="text/fbml">
<!--
<fb:fbml>
My FBML elements...
</fb:fbml>
-->
</script>
</div>
<script type="text/javascript">
$(document).ready(function () {
// Add XFBML elements on the fly
FB_RequireFeatures(["XFBML"], function(){
FB.Facebook.init();
FB.XFBML.Host.autoParseDomTree = false;
$("#myXfbml").html($("#myXfbml").html().replace(/<!--|-->/g, ''));
FB.XFBML.Host.addElement(new FB.XFBML.ServerFbml(document.getElementById('myXfbml')));
});
});
</script>
</code>


Dec 13, 2009 at 5:23 PM // reply »
11,238 Comments

@Tim,

Interesting technique. How do you uncomment it?

How are you finding building FaceBook apps? I have not tried it yet, but I have to say I have been very interested in trying.


Dec 13, 2009 at 6:14 PM // reply »
3 Comments

@Ben Nadel,

> How do you uncomment it?

I use jquery and a JS regex to replace the inner html of the container div. In my previous example, I didn't properly comment out the script tags. Here's a better example of content with script tags injected into the DOM:

<div id="foo">

<!--

<script type="text/fbml">

<fb:fbml>

My FBML elements...

</fb:fbml>

</script>

-->

</div>

To uncomment, do this after the content is injected into the DOM:

$("#foo").html($("#foo").html().replace(/<!--|-->/g, ''));

> How are you finding building FaceBook apps?

Facebook apps are proving a bit frustrating to build - poor Facebook API performance/reliability is very common. But given facebook's traffic volume, I suppose ya can't blame them. However, their API is easy to implement and yields quite a bit of data.


Dec 13, 2009 at 7:49 PM // reply »
1 Comments

- data containers, not data contains (contain is a verb only)
- different from, not different than


Kay
Dec 15, 2009 at 1:16 AM // reply »
1 Comments

I do like this to un-comment the facebook FBML, but not working :(

$("#id").click(
function(){
$("#contentArea").load("main/load"); $("#fbml").html($("#fbml").html().replace(/<!--|-->/g, ''));

}
);


Dec 15, 2009 at 10:56 AM // reply »
3 Comments

@Kay,

jQuery ajax calls are asynchronous by default(check the jQuery.ajax docs). This means your javascript continues executing while waits for the response from your ajax call. So your code to uncomment the script tag won't work because the content has been loaded yet.

Typically, you use a callback function to handle the response. However, .load() executes the callback function before it inserts the response content into the DOM. This is no good because as the article points out, the script tags get moved around when they're injected into the DOM.

So the bottom line solution is change your ajax calling code to this:

$("#id").click(

function(){

$("#contentArea").load("main/load");

}

);

And then in your *response HTML content* - comment out your FBML script tags, and add another set of script tags to process and uncomment the FBML script tags after the content is loaded and injected into the DOM:

<div id="fbml">

<!--

<script type="text/fbml">

<fb:fbml>

My FBML elements...

</fb:fbml>

</script>

-->

</div>

<script type="text/javascript">

$(document).ready(function () {

$("#fbml").html($("#fbml").html().replace(/<!--|-->/g, ''));

/*** code to render XFBML here ***/

});

</script>


Dec 15, 2009 at 6:21 PM // reply »
11,238 Comments

@Tim,

Ah, I see what you're saying now. I originally thought you were only commenting out the *contents* of the script tag, the entire script tag. That makes much more sense. Thanks.


Feb 2, 2010 at 3:13 PM // reply »
11,238 Comments

@Paul,

I don't mean to be rude, but I am trying to limit the off-topic, large chunks of code that people post in these comments. Your question is quite off topic. Questions like this should either be submitted via my "Ask Ben" form or perhaps on the jQuery mailing list.


Feb 2, 2010 at 3:23 PM // reply »
2 Comments

@ Ben,

I apologies Sir,

But my question was exactly on point what is being discussed in this trhread. I see that You've taken away my post but if you had left it there everyone would see that what I asked about was exactly what Kay and Tim are discussing, i.e, relating to using ajax to load content with fbml tags!

Could you please explain what you mean by my question being off topic ?!


Feb 2, 2010 at 3:27 PM // reply »
11,238 Comments

@Paul,

@Tim was discussing FBML in the context of AJAX transfer within SCRIPT tags (which is what this blog post was about). I felt that your question had nothing to do with SCRIPT tags, only with FBML which was really only incidentally touched upon.

However, if you feel that it was on-topic, I will re-post it here:

---------------------------------------------
---------------------------------------------

@ Tim

I sent you a little e-mail. I would be very delighted if you'd please take a look at it. I am URGENTLY in need of a solution

I will post the question here as well.

I am using a Jquery Ajax script to load extract and load external content into div. I would like though for the script to be able to load content with FBML tags. Could you please take a look at and advise me on what changes to make in order for the script to also successfully load content within fbml tags:

$(document).ready(function() {

var contentWrapID2 = '___content-wrapper2';

$('#content2').wrap('<div id="' + contentWrapID2 + '"></div>');

function showNewContent2() {
$("#" + contentWrapID2).slideDown();
$('#load').fadeOut();
}

function pageload2(hash2) {
if(hash2) {
$("#" + contentWrapID2).load(hash2 + " #content2",'',function(){
if($('img:last',this).get(0)) {
$('img:last',this).load(function(){
showNewContent2();
});
} else {
showNewContent2();
}

});
} else {
$("#" + contentWrapID2).load("index.html #content2");
}
}
$.historyInit(pageload2);

$('#vert-navbar li a').click(function(){

var hash2 = $(this).attr('href');
hash2 = hash2.replace(/^.*#/, '');
$("#" + contentWrapID2).slideUp(300,function(){
$.historyLoad(hash2);
});
if(!$('#load').get(0)) {
$('#container2').append('<span id="load">LOADING...</span>');
}
$('#load').fadeIn('normal');
$('#vert-navbar li a').removeClass('current');
$(this).addClass('current');
return false;

});

});

.............................
In the html document it simply says:

<div id="content">
<fb:fbml> </fb:fbml>
</div>

I'm badly in need of a quick answer
Thanks in advance


Feb 2, 2010 at 3:34 PM // reply »
2 Comments

@ Ben

In all fairness Sir, more than 40% of this discussion in this thread i.e., "Using Script Tags As Data Contains In AJAX-Powered jQuery" has been about what I asked about, both you, Tim and Kay have taken part in this discussion.
Also the amount of code I attached was within the margins of the amount that Kay posted for instance.

I would be very thankful sir if you would re-post my question, but even more grateful if you could help me with a solution.


Feb 2, 2010 at 3:40 PM // reply »
11,238 Comments

@Paul,

I re-posted your comment within my last comment. The length of the code was not so much the issue as it was that your question had nothing to do with script-tag data transfer (which all the other comments did).

Let's move on, however; Tim can review your question when he can.



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 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
May 20, 2013 at 4:29 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, That's if you're trying to reference a specific row. In this case, we're trying to reference the entire query column as one cohesive value. So, you are correct that if you wanted to output a ... read »
May 20, 2013 at 4:24 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I thought when you used array notation to reference queries you always had to have the row or it would throw a similar error as well? ... read »
May 20, 2013 at 11:45 AM
Using jQuery's Animate() Step Callback Function To Create Custom Animations
This is really useful. I found out that you don't actually have to use a dummy css property (surprisingly). To animate a property in a linear-gradient for instance I did this this.css('someLinearGra ... read »
May 20, 2013 at 10:51 AM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Josh, Oh snap! You're totally right! I'm not sure I've ever tried that. I did know that you can call a number of other array-methods on ColdFusion query columns: http://www.bennadel.com/blog/167 ... read »
May 20, 2013 at 10:45 AM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Ben - I believe you can achieve the same functionality with ColdFusion's built in ArrayToList() function. ArrayToList( users[ "id" ] ); ... read »
May 20, 2013 at 10:21 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Is there any error logging and handling framework in angularjs, if not then in what way I can do this. ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools