Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with:

Using Script Tags As Data Contains In AJAX-Powered jQuery

By Ben Nadel on

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.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

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

Reply to this Comment

@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 :)

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

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>

Reply to this Comment

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

Reply to this Comment

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>

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

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

}
);

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

@ 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 ?!

Reply to this Comment

@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

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.