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 cf.Objective() 2010 (Minneapolis, MN) with:

Loading Remote jQuery Template Markup Language (JTML) Templates

By Ben Nadel on

All of my previous jQuery Template Markup Language (JTML) demos have involved using templates that were immediately available in the current page. As such, I thought I would try writing some code that loaded a JTML template asynchronously from the server before it could be rendered. I have never personally loaded remote templates before - using JTML or any other templating method; so, I am not quite sure what the best approach is. I didn't want to add the remote capabilities to the JTMLTemplate class because I felt quite strongly that remote-loading was not its responsibility - it's a rendering engine. So, what I ended up creating was a RemoteJTMLTemplate class that wraps the core JTMLTemplate class and handles all of the render queueing and remote template loading.

 
 
 
 
 
 
 
 
 
 

To demonstrate how a remote template might be used, I figured I would take my previous AJAX example and move the local JTML template into a remote JTML template location. In the code below, notice that I am including the additional Javascript library, jquery.remote-jtml.js, which will be used to add the remote functionality. Then, rather than passing the RemoteJTMLTemplate() constructor a script tag selector, I am passing it the URL of the remote JTML template file.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>jQuery Template Markup Language (JTML) AJAX Demo w/ Remote Template</title>
  • <style type="text/css">
  •  
  • #girls {
  • list-style-type: none ;
  • margin-left: 0px ;
  • padding-left: 0px ;
  • }
  •  
  • li.girl {
  • border-bottom: 1px dotted #CCCCCC ;
  • padding-bottom: 14px ;
  • }
  •  
  • li.girl h4 {
  • margin-bottom: 4px ;
  • }
  •  
  • li.girl p.description {
  • margin: 0px 0px 7px 0px ;
  • }
  •  
  • li.girl p.age {
  • font-size: 90% ;
  • margin: 0px 0px 7px 0px ;
  • }
  •  
  • li.girl p.note {
  • color: #999999 ;
  • font-size: 90% ;
  • font-style: italic ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.4.2.js"></script>
  • <script type="text/javascript" src="jquery.jtml.js"></script>
  • <script type="text/javascript" src="jquery.remote-jtml.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize scripts.
  • jQuery(function( $ ){
  •  
  • // Create a new JTML template renderer. We are using a
  • // delayed-load rendering template, so pass in the URL
  • // of the remote template.
  • //
  • // NOTE: I appended 'html' to the remote file name
  • // because my server doesn't know how to server up the
  • // JTML file extension. The file extension doesn't
  • // really matter anyway since the underlying call expects
  • // TEXT data to come back.
  • var girlTemplate = new RemoteJTMLTemplate(
  • "./remote_template.jtml.html"
  • );
  •  
  • // Get a reference to the output.
  • var girls = $( "#girls" );
  •  
  • // Get a reference to the 'create' link.
  • var createGirl = $( "#createGirl" );
  •  
  •  
  • // Bind the create link to make a new girl (via AJAX).
  • createGirl.click(
  • function( event ){
  • // Cancel the event since this isn't a real link.
  • event.preventDefault();
  •  
  • // Make the call to the server to get a new girl.
  • // This will come back as JSON data.
  • $.getJSON(
  • "./random_girl.cfm",
  • function( girl ){
  •  
  • // Given the girl data, populate a girl
  • // template and then just append to girls
  • // output list. Since we are using a
  • // remote template, we need to use a
  • // callback since the remote template
  • // might not be available yet.
  • girlTemplate.render(
  • girl,
  • function( girlHtml ){
  • girls.append( girlHtml );
  • }
  • );
  •  
  • }
  • );
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery Template Markup Language (JTML) AJAX Demo w/ Remote Template
  • </h1>
  •  
  • <h2>
  • Random Girls
  • </h2>
  •  
  • <p>
  • <a id="createGirl" href="#">Create another girl</a> already!
  • </p>
  •  
  • <ul id="girls">
  • <!-- To be populated dynamically. -->
  • </ul>
  •  
  • </body>
  • </html>

Now that the JTML template is being loaded remotely, we cannot be sure that it will be available when we call the render() method. As such, in this approach, rather than returning the resultant HTML directly from the render() method, we need to pass in a callback() function to the render() method that will be executed when the resultant HTML is available. Once the JTML template is available, calls to the render() method will immediately execute the callback() function; however, if the JTML template is still loading, all render() requests will be queued internally until the template is available. After the JTML template loads, any queued render requests will be executed in the order in which they were queued.

By default, the RemoteJTMLTemplate class does not load the remote JTML template until the user's first call to the render() method. However, if you would like to preload the JTML template, you can pass in "true" as a second argument to the RemoteJTMLTemplate() constructor:

  • new RemoteJTMLTemplate( "./remote_jtml.htm", true );

When you do this, the RemoteJTMLTemplate class will immediately launch an AJAX request to load the remote JTML template directly from the constructor. If we do this, we still can't be sure that the template will be available by the time the first render() request is made; however, using the preload will, most likely, allow all render() requests to execute immediately. Of course, you still need to use the callback() approach when dealing with the resultant HTML.

The code for the RemoteJTMLTemplate class is fairly small and straightforward, so I'll show it below. It will also be added to the jQuery Template Markup Language (JTML) project along with the above demo code:

  • // Define the remote version of the JTMLTemplate.
  • function RemoteJTMLTemplate( templateUrl, preloadTemplate ){
  • // Store the URL of our remote template.
  • this.templateUrl = templateUrl;
  •  
  • // I am the JTML template object that will eventually be loaded
  • // once we have the JTML markup.
  • this.template = null;
  •  
  • // I am the queue to hold rendering maps and callbacks that
  • // will need to be merged when the template is available.
  • this.renderQueue = [];
  •  
  • // I am the AJAX request for the template being loaded.
  • this.templateRequest = null;
  •  
  •  
  • // Check to see if the user wants to preload the template (or if
  • // they simply want to wait until the first render call has been
  • // made).
  • if (preloadTemplate){
  •  
  • // Load teh remote template - this will be executed with AJAX
  • // and will happen asynchronoushly.
  • this.loadRemoteTemplate();
  •  
  • }
  • }
  •  
  •  
  • // Define the template class methods.
  • RemoteJTMLTemplate.prototype = {
  •  
  • // I flush the render queue. I only get called once the template
  • // has been loaded.
  • flushRenderQueue: function(){
  • // While the queue has a length, shift the render command
  • // and pass it off to the render method.
  • while( this.renderQueue.length ){
  •  
  • // Get the oldest queue item.
  • var renderItem = this.renderQueue.shift();
  •  
  • // Render the template.
  • this.render( renderItem.map, renderItem.callback );
  •  
  • }
  • },
  •  
  •  
  • // I load the JTML template off of the remote server.
  • loadRemoteTemplate: function(){
  • var self = this;
  •  
  • // Check to see if the template is currently being loaded.
  • if (this.templateRequest){
  •  
  • // The template is already being loaded, so just return
  • // out of this method.
  • return;
  •  
  • }
  •  
  • // Load the template from the remote server. Regardless of
  • // what type of remote file extension we use, we are
  • // explicitly telling jQuery to expect a TEXT data response.
  • this.templateRequest = $.ajax({
  • type: "get",
  • url: this.templateUrl,
  • dataType: "text",
  • success: function( jtmlMarkup ){
  •  
  • // Now that we have loaded the JTML markup, let's
  • // create and store the JTML Template class instance
  • // that will be used to render the tempaltes.
  • self.template = new JTMLTemplate( jtmlMarkup );
  •  
  • // Now that the render template is available, let's
  • // flush any render commands that have been queued up.
  • self.flushRenderQueue();
  •  
  • },
  • error: function(){
  • // Something went wrong.
  • alert( "JTML Template could not be loaded" );
  • }
  • });
  • },
  •  
  •  
  • // I am the render method that merges the map into the loaded
  • // JTML template. If there is not template available yet, the
  • // render command is queued.
  • render: function( map, callback ){
  • // Check to see if the template is available.
  • if (this.template){
  •  
  • // Since we know this will only be called once the
  • // template has been loaded, we can pass this off
  • // directly to the stored JTML Template instance.
  • callback(
  • this.template.render( map )
  • );
  •  
  • } else {
  •  
  • // The template is not yet available so queue the render
  • // command for later rendering
  • this.renderQueue.push({
  • map: map,
  • callback: callback
  • });
  •  
  • // Load the remote temolate.
  • this.loadRemoteTemplate();
  •  
  • }
  • }
  •  
  • };

I know there are a number of existing Javascript libraries that handle remotely loading files that are interdependent; I, however, have not really looked at them yet. If you know of a library that handles remote, dependency loading more effectively than this, please let me know. This was my shoot-from-the-hip, Monster-fueled approach and I am sure there is some good room for improvement. In any case, though, I hope this demonstrates how JTML templates can be used without cluttering up your HTML or putting unnecessary upfront-load times on the request.




Reader Comments

@Bill,

Yeah, I simply meant "remote" to the client (browser). You probably need to stick to the same domain otherwise you're gonna run into cross-domain request security issues (unless you run sort of server-side proxy).

Reply to this Comment

Hey Ben, long time reader first time commenter... ha...

I like what you're doing here it looks great! Could be very very handy. I may actually want to use this for a project in the future, are you looking to build this into a fully fledged product or is this just a side-project experiment? Maybe it's too early for you to even comment on this...

Anyways, I haven't seen anything like this other than Spry. And I don't believe spry offers any sort of remote template loading. Very cool. Keep up the good work!

Reply to this Comment

Yeah man, I would also LOVE to include this in future projects. Nice how it works so smooth'n easy. Maybe you could include cross site support by useing jsonp in another function?
It's a nice idea to have some sort of template server.

Reply to this Comment

@Skinneejoe,

I appreciate you stopping by with a comment :) I would love for this to become a "real" product eventually. Right now, it's just proof-of-concept. As such, any feedback / hopes / wishes would be great to know about.

@Chris,

Hmmm, interesting. I think it would be relatively easy to get this to work with JSONP since it is powered by jQuery underneath. I'll see if I can whip up a demo for that.

Reply to this Comment

You asked for "remote" script loaders... so what about LABjs?

LABjs will automatically detect when the "template" script file loads, and you can then call render().

Reply to this Comment

@Kyle,

The whole non-blocking, parallel script loading concept is something that I am very new to. LabJS looks cool. If I had to guess, this (and things like it) are moving from a Script load to an AJAX load or something like that. Seems like a very cool thing. This will definitely be something worth looking into. Thanks for pointing it out.

Reply to this Comment

I had a problem getting this to work in Chrome (on OS X), though it works fine in Firefox. It was trying to find the property length of undefined. Logging the variables in line 43 (of the 2010-04-29 build) showed that $1 was undefined in Chrome, and an empty string in Firefox. I added a short circuit test to line 47 so it checked for existence of $1 before doing the .length test: if ($1 && $1.length) {... Now it seems to work fine.

Reply to this Comment

@Alec,

Oh, very interesting! That must be a tiny difference in the way regex is handled cross browsers; or rather, not so much how regex is handled but how non-matching groups are passed to the callback function. Firefox passes an empty string; looks like Chrome is passing NULL.

Thanks for the heads up - I'll take a look at this in the morning. I was not aware of this difference.

Reply to this Comment

@Alec,

Thanks again for point this out. I confirmed that Firefox is the only browser that actually passes in an empty string:

http://www.bennadel.com/blog/1916-Different-Browsers-Use-Different-Non-Matching-Captured-RegEx-Pattern-Values.htm

To make this a bit more simple, I'll update the JTML project to just use implicit falsey casting:

if ($1){ ... }

This will allow both "undefined" and "empty string" to be evaluated as false.

Thanks for the hot tip!

Reply to this Comment

@Alec,

Double-thanks for bring this to my attention - apparently there were several cross-browser bugs for the JTML stuff. I had to jimmy some regular expressions to work in IE.

I posted a new build to the project page; I'll clean it up a bit more later.

Reply to this Comment

@Ben Nadel,

Glad I could help. I'm looking forward to the next build. JTML has come in really handy with a project I'm working on.

Reply to this Comment

@Alec,

Awesome my man - if you come up with any good improvement ideas, please drop a comment here with suggestions.

Reply to this Comment

This has been a great little templating engine for us. Thanks for the work.

I'm seeing some strangeness when my templates have deeper trees of elements in which some inner elements are actually rendered outside the parent element. For example, this jtml template:

<pre>
<p class=summary>this is test <span> a span?</span> <ol><li>1</li><li>2</li></ol></p>
</pre>

Ends up being rendered as

<pre>
<p class=summary>this is test <span> a span?</span></p>
<ol><li>1</li><li>2</li></ol>
</pre>

in the browser, once the render method is called.

Any ideas?

Reply to this Comment

@Ben,

Actually, looking at that HTML again, it's not valid. You can't have OL elements inside of P elements. On my end, I get the same behavior (re-organization of elements) whether or not I use a template. This must be the browser just trying to correct the HTML.

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.