My Solution For HTML Templating
I am a big fan of separating application logic from presentation as much as possible. JavaScript naturally tends to blur the lines between these two areas which makes it extremely easy to just throw them both into a giant interface mixing pot. This method proves to be extremely inefficient when you are building applications that require maintenance by individuals who are not expected to know a bit of JavaScript. I have landed on a happy medium for several of my recent projects. I would like to share that process and inquire as to how you may handle this.
The Problem
We will start with a page that has sent an XHR request to the server, a search perhaps. This search may return ten to several hundred results. You could simply have the server return a giant chunk of HTML containing your full search results. The JavaScript then needs to only append the results to the appropriate place on the document. That would be the easy way out. HTML adds a bunch of unimportant pieces of data to the stream that our browser is handling.
Simple Solution
To help our browser while it chokes down the data we can instead request a JSON object from our server. Several hundred results may still be large, but we’ve likely cut our total size down by a significant value. This leaves us with the problem of displaying this information. Each record has the same display properties, just different values. We could just loop over each object in our code and use a replace command on a pre-determined string to populate our data correctly. This is how it might look in vanilla JavaScript:
var htmlSnip = "<p>Your name is: {name}, Your Position is: {title}</p>";
var content = '';
for (var x=0; x<mydata.length; x++){
tmp = htmlSnip.replace('{name}', mydata[x].firstName);
tmp = tmp.replace('{title}', mydata[x].title);
content += tmp;
}
document.getElementById('wrapper').innerHTML = content;
Loop over each record in our data set and replace our predetermined variables with information from the data. In this example I have used {name} and {title} to create a variables for replacing later.
This scenario however is less than ideal if we are working with a team that may have minimal or no JavaScript knowledge. The site may have rolled to production months ago and is now handled by a new HTML developer. It would be ideal if we can move our HTML out of the JavaScript entirely. If we move it to the body of the HTML document it will reside in the same place as the rest of the presentation. Here is a sample:
<div id="template" style="display: none;">
<p>Your name is: {name}, Your Position is: {title}</p>
</div>
The template now sits in the HTML document, removed completely from our JavaScript logic.
var htmlSnip = "<p>Your name is: {name}, Your Position is: {title}</p>";
becomes:
var htmlSnip = document.getElementById(‘template’).innerHTML;
A YUI3 Version Using Substitute
It’s possible that we will need to re-use this code. For that, I have called in the help of YUI3. Converting the JavaScript above to YUI3 yields the following:
var content = '';
var htmlSnip = Y.one('#template').get('innerHTML');
Y.each(mydata, function(person){
content += Y.substitute( htmlSnip, {
name: person.firstName,
title: person.title
});
});
Y.one('#wrapper').set('innerHTML', content);
If you are processing a lot of templates you could turn this into an object and simply call:
var myTemplate = new Y.fastTemplate({
data: mydata,
templateNode: Y.one('#template'),
wrapperNode: Y.one('#wrapper')
});
myTemplate.process();
The base object for that looks like this:
YUI.add('fastTemplate', function(Y) {
function fastTemplate( config)
{
fastTemplate.superclass.constructor.apply( this, arguments );
}
fastTemplate.NAME = "fastTemplate";
fastTemplate.ATTRS = {
data : {},
variables: {},
templateNode: {},
wrapperNode: {}
};
Y.extend( fastTemplate, Y.Base, {
process : function() {
var content = '';
var myData = this.get('data');
var htmlSnip = this.get('templateNode').get('innerHTML');
Y.each(myData, function(i){
content += Y.substitute( htmlSnip, i);
}, this);
this.get('wrapperNode').set('innerHTML', content);
}
});
Y.fastTemplate = fastTemplate;
}, '0.0.1' ,{requires:[ 'node', 'base', 'substitute']});
View a sample of the base object in action.
In Closing
I’m curious what other people are using out there. I’d like to know if you have any suggestions or updates to what I have here.
Converse
1. Jeromy Evans added the following to the conversation
This is great and logical. However I’ve tried it before myself and there’s several dedicated JS templating libraries, but personally I couldn’t achieve sufficient performance in IE6&IE7 for it to scale to a significantly complex template. At least, not compared to rendering an HTML fragment on the server-side and sending it.
When performance is no longer an issue, you could potentially use the same templates for client-side rendering or server-side rendering. With some smarts, you could render server-side as a degraded mode for older browsers too.
2. Jeromy Evans added the following to the conversation
If just wanted to add that although I failed, I certain someone (you) will achieve it.
A related idea: you could design a client-side caching system to ensure idential templates are never rendered more than once. One solution I’ve seen compiled each template into JavaScript to achieve optimal performance for each re-use.
3. Jeromy Evans added the following to the conversation
PS. Sorry for the poor typing. The comment box doesn’t allow me to review all the text before posting from a phone.
4. David added the following to the conversation
Luke - I had not seen that construct I will have to start playing around with it.
Jeromy - My apologies then for the poor comment form, just getting it worked out. I like the idea of the caching system. That’s worth looking into.
5. apipkin added the following to the conversation
You may also want to consider the possibility of having multiple sets of data returned, like with the search results example. It would be good to keep a local string then append to the wrapper node after the recursion is complete.
6. apipkin added the following to the conversation
Actually scratch that, you already have :)
Also is the eval() needed?
7. David added the following to the conversation
apipkin you’re absolutely correct the eval is not needed. We can instead just use:
Y.each(myData, function(i){ content += Y.substitute( htmlSnip, i); }, this);
8. David added the following to the conversation
This module has been ported to the YUI3 Gallery. http://yuilibrary.com/gallery/show/fast-template
9. I can't get a signal <a href=" http://www.mone.at/tlc/cyrahirul/weblog ">Delightful Nudes Toplist </a> =-))) <a href=" http://www.mone.at/tlc/apiebiqop/weblog ">Japanese Models </a> qjb <a href=" http://www.mone.at/tlc/ifoparoni/weblog ">Torrent Hussyfa added the following to the conversation
I can’t get a signal Delightful Nudes Toplist =-))) Japanese Models qjb Torrent Hussyfan 29286 Panty Models 82249 Teen Lolita Hussyfan Pics =))) Pedo Hussyfan 21025 Hussyfan Pics fzty Girl Sleeping Hussyfan xhvmey Pthc Cp Toplist kgknra Young Boy Lovers Toplist >:-O Lesbian Pedo pvpoe Ando Bien Pedo Los Recoditos ejcn Lolita Girls Toplist : >:P 502 488642 cuzqvd %P 212 oasl 69410 rshrj qzc 43101 = =O lwaw >:)) zlrzf rusbu
10. I'm sorry, he's <a href=" http://www.fotolog.com/nyhujeryd/about ">Fucking Black Model </a> sxkv added the following to the conversation
I’m sorry, he’s Fucking Black Model sxkv
11. I'm sorry, he's <a href=" http://www.fotolog.com/nyhujeryd/about ">Fucking Black Model </a> sxkv added the following to the conversation
I’m sorry, he’s Fucking Black Model sxkv
12. I'm sorry, he's <a href=" http://www.fotolog.com/nyhujeryd/about ">Fucking Black Model </a> sxkv added the following to the conversation
I’m sorry, he’s Fucking Black Model sxkv
0. Luke Smith added the following to the conversation
Have you posted this to the YUI 3 Gallery?
A syntax that I find helpful to reduce code size is
Y.FastTemplate = Y.Base.create(‘fastTemplate’, Y.Base, , { /* prototype / }, { ATTRS: { … }, / other statics / });
The static NAME is the first parameter.
Also, try out Y.Lang.sub( template, valuesObject ); It may be faster.
Thanks for sharing!