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() 2012 (Minneapolis, MN) with:

Creating jQuery Templates Plug-in Using Textarea Elements (Thanks Kurt Bonnet)

By Ben Nadel on

A while back, Kurt Bonnet introduced me to the idea of using HTML Textarea elements to store the markup for HTML templates for use within jQuery. I used to use ColdFusion as a way to generate my dynamic HTML, but lately, I have been getting more into the idea of passing back raw data from ColdFusion and then using client-side templates to create my new DOM elements. Something about this feels like a really nice separation of "Data" and "View" in a rich-client model. To make this process as easy as possible, I thought I'd create a jQuery plug-in that adds the method template() to Textarea elements. Here is a demo of this in action:

 
 
 
 
 
 
 
 
 
 

Here is the page that I am demoing in the above video:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>jQuery HTML Templates Using Textarea Elements</title>
  • <script type="text/javascript" src="jquery-1.2.6.min.js"></script>
  • <script type="text/javascript" src="jquery.template.js"></script>
  • <script type="text/javascript">
  •  
  • // Hook up the add button handler.
  • $(
  • function(){
  • // Get a refernece to the values text area.
  • var jValues = $( "#values" );
  •  
  • // Get a refernece to the template text area.
  • var jTemplate = $( "#template" );
  •  
  • // Keep track of the templates created.
  • var intCount = 0;
  •  
  • // Bind the click event.
  • $( "button" ).click(
  • function(){
  • // Get the new element from our jQuery
  • // template. When we do this, we are going
  • // to pass in some values that can be
  • // leveraged.
  • jElement = jTemplate.template(
  • eval( "(" + jValues.val() + ")" )
  • );
  •  
  • // Increment the count.
  • intCount++;
  •  
  • // Add the element to the page.
  • $( "body" ).append( jElement );
  • }
  • );
  • }
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery HTML Templates Using Textarea Elements
  • </h1>
  •  
  • <form>
  •  
  • <p>
  • Please enter your template code in the
  • </p>
  •  
  •  
  • <textarea
  • id="values"
  • style="width: 500px ; height: 125px ;">
  •  
  • {
  • count: intCount,
  • name: "Naomi",
  • description: "Sexy"
  • }
  •  
  • </textarea>
  •  
  • <br />
  •  
  • <textarea
  • id="template"
  • style="width: 500px ; height: 125px ;">
  •  
  • <p id="template{count}">
  • <strong>{name}</strong> is <em>{description}</em>
  • I am template {count}.
  • </p>
  •  
  • </textarea>
  •  
  • <br />
  •  
  • <button type="button">Add Template</button>
  •  
  • </form>
  •  
  • </body>
  • </html>

As you can see in the above code, to convert the HTML code in the textarea into a new DOM element, we are simply calling template() on jQuery textarea (jQuery stack containing the textarea as the first element) and passing in the name-value pairs that will be used for the variables. This method returns a new jQuery stack containing the new DOM element. We are using a Textarea element to hold the template value because they are easy to code (the HTML does not need to be massaged to fit the textarea) and the browser won't try to interpret the code inside. Plus, we can easily hide textareas with a little CSS.

The jQuery plug-in that makes this possible is actually quite simple. It is not much more than a loop and a regular expression that swaps out the variable names with the given variable values:

  • // Define the jQuery Template plugin. This takes a textarea
  • // value and converts it into an jQuery DOM elements (outside
  • // of the current DOM) and returns it. It takes only one
  • // argument: the name-value pairs of the values to replace
  • // into the template.
  • jQuery.fn.template = function( objValues ){
  •  
  • // Get a reference to the current jQuery stack.
  • var jThis = this;
  •  
  • // Get the value of the textarea.
  • var strHTML = jThis.val();
  •  
  • // This will be our index variable for looping over the
  • // values that were passed in.
  • var strKey = "";
  •  
  • // Check to make sure we have a value string. If this is
  • // not the right kind of jQuery stack, the HTML string will
  • // be null.
  • if (strHTML){
  •  
  • // Now that we have the proper value, we have to
  • // replace in the mapped values. Loop over each
  • // value that was passed in.
  • for (strKey in objValues){
  •  
  • // Escape all the special values in the key so that
  • // it can be used in a regular expression.
  • strSafeKey = strKey.replace(
  • new RegExp(
  • "([\\[\\]\\.\\+\\*\\{\\}\\(\\)\\$\\?\\-])",
  • "gi"
  • ),
  • "\\$1"
  • );
  •  
  • // Replace the value.
  • strHTML = strHTML.replace(
  • new RegExp( "\\{" + strSafeKey + "\\}", "gi" ),
  • objValues[ strKey ]
  • );
  •  
  • }
  •  
  • // At this point, our HTML will have fully replaced
  • // values. Now, let's convert it into a jQuery DOM
  • // element and return it.
  • return( jQuery( strHTML ) );
  •  
  • } else {
  •  
  • // Return empty jQuery stack.
  • return( jQuery( [] ) );
  •  
  • }
  • }

So far, I am really liking this method. Thanks Kurt for your inspiration!




Reader Comments

Yeah, this is one area where I think Spry really shines. When it comes to 'Get remote crap and display it', I think Spry does it best. Thanks for blogging this - I could have used it for my last LighthousePro update. :)

Reply to this Comment

@Ray,

Happy to share. I think there are still gonna be times where I want ColdFusion to build the interface and just return it; but, I am trying to get my data to be more reusable and let the client handle the display.

Reply to this Comment

@Ben,

I'm glad those links helped out. Nice work on the jQuery plug-in, I love how simple and powerful it is. I look forward to making use of it :) Nice video too; man you went all out for this post!

Reply to this Comment

@Kurt,

Thanks :) I am finding that the video really ties things together nicely, especially when the content of the post is action-based.

Reply to this Comment

Thanks for directing me to this post I think i'll be able to make use of this plugin. In a more practical example the text areas would be hidden right? Therefore hiding your template on the page until the remote data is called and appended to the page? Thanks again for all your posts they are always so practical and clear.

Reply to this Comment

There is one question that I was curious about. How would it be possible to emulate the cfoutput query using the group attribute by looping through the returned json. Ideally the ProductCatName and ProductName variables would be in h4 h5 tags respectively and the list of serial numbers would be in a table. Any thoughts on how to approach generating a template to emulate this type of grouping and display.

<cfoutput query="myQuery" group="ProductCatID">
#ProductCatName#
<cfoutput group="ProductID">
#ProductName#
<cfoutput>
#SerialNum#
</cfoutput>
</cfouput>
</cfoutput>

Thanks in advance for any thoughts on how to accomplish this.

Reply to this Comment

@Tim,

First off, I wouldn't return a query via JSON. Something about queries just don't feel right to me in an AJAX world. I prefer arrays of structures. And, once you are in this model, you can return a JSON object that breaks its data up in the appropriate way: master array in which each index is "Section" of the content complete with title and its own nested array of products.

Not the only way, but it would be really easy to loop over an array of structs than a query.

Reply to this Comment

I have found using Spry on the client side to handle a query via JSON works very well. I can even strip out 'null' values to reduce the size of the response; Spry handles it just fine. Small snippet here (I have extra functions for application security checks, caching, and custom JSON prefixing; done in CF8)

<!--- Vendor Contact --->
<!--- remote function --->
<cffunction name="DFF8D96F_6704_4CE2_98B3_94D077362085" access="remote" returntype="string" output="no" returnformat="plain">
<cfargument name="fooDog" type="numeric" required="yes">

<cfscript>
var checkUser = Request.cfc_Data.CheckUser(VendorID=Arguments.fooDog);
var qry = getVendorContactByID(checkUser.VendorID);
</cfscript>
<cfinclude template="no_cache.cfm">
<cfreturn "//" & Session.UserInfo.PageKey & Replace(SerializeJSON(qry, true), 'null', '', 'ALL')>
</cffunction>

<!--- private function --->
<cffunction name="getVendorContactByID" access="private" returntype="query" output="no">
<cfargument name="VendorID" required="yes" type="numeric">
<cfquery name="q_qResult" datasource="#Request.DBSource#" username="#Request.DBUser#" password="#Request.DBPass#">
EXEC usp_dm_GetVendorContactByID
@VendorID = #VendorID#
</cfquery>
<cfreturn qResult>
</cffunction>

It returns:

//4129655EF4D302F8940D7785A67024BEA53060D2ACFCF32129AFB9D28789F1D21D580CDC3B2D734088474E63E5811359DC1029D6C77F81002ACA91527ED89499{"ROWCOUNT":1,"COLUMNS":["F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","F13","F14","F15","F16","F17","F18","F19","F20","F21","LMS"],"DATA":{"F1":[1],"F2":[0],"F3":[0],"F4":[0],"F5":[0],"F6":[0],"F7":[0],"F8":[0],"F9":[0],"F10":[0],"F11":[0],"F12":[1],"F13":["FooBar"],"F14":["Peter"],"F15":["Senior Programmer"],"F16":[],"F17":[],"F18":[],"F19":[],"F20":[],"F21":[529],"LMS":["2009-02-27 23:39:52.687"]}}

Then in Spry:

var dsCurrentData = new Spry.Data.JSONDataSet("cfc/ops_vendor.cfc?method=DFF8D96F_6704_4CE2_98B3_94D077362085&fooDog={dsVendorList::F1}", optJSON);

My JSON options:

var optJSON = new Object();
optJSON.path = "DATA";
optJSON.pathIsObjectOfArrays = true;
optJSON.preparseFunc = JSONPreparse;

Spry handles the layout as documented.

Reply to this Comment

@Peter,

I have heard really nice things about SPRY, but have not had the chance to use it myself.

Reply to this Comment

@Ben,

Spry is a very nice and flexible AJAX alternative to using Flex. I have products used by clients with older machines and browsers, so Spry makes it possible to work with them until they update to a Flash-based interface. It's easily modified, too, when you need your own tweaks.

Reply to this Comment

Another +1 for Spry. I'm definitely a jQuery FanBoy now, but Spry was my 'gateway' drug into AJAX. Also, if you are a DreamweaverCS4 user, there are -very- tight integrations between Spry and your code.

Reply to this Comment

Quick note - I used this idea to build a similar system. You just have to watch it if you have textareas as part of your content. It breaks the template. In my case, i was able to switch it to a DIV instead but I wondered if there is a better solution.

Reply to this Comment

@Aaron,

I've been enjoying Script-tag-based html data lately; it will, of course, suffere from some nested Script tag issues, but that is less likely than standard HTML elements.

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.