I’ve had a little utility that I’ve been kicking around for some time now that I’ve found to be quite useful in my JavaScript application-building endeavors. It’s a super-simple templating function that is fast, caches quickly, and is easy to use. I have a couple tricks that I use to make it real fun to mess with.
Here’s the source code to the templating function (a more-refined version of this code will be in my upcoming book Secrets of the JavaScript Ninja):
// Simple JavaScript Templating // John Resig - https://johnresig.com/ - MIT Licensed (function(){ var cache = {}; this.tmpl = function tmpl(str, data){ // Figure out if we're getting a template, or if we need to // load the template - and be sure to cache the result. var fn = !/\W/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : // Generate a reusable function that will serve as a template // generator (and which will be cached). new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){} "with(obj){p.push('" + // Convert the template into pure JavaScript str .replace(/[\r\t\n]/g, " ") .split("<%").join("\t") .replace(/((^|%>)[^\t]*)'/g, "$1\r") .replace(/\t=(.*?)%>/g, "',$1,'") .split("\t").join("');") .split("%>").join("p.push('") .split("\r").join("\\'") + "');}return p.join('');"); // Provide some basic currying to the user return data ? fn( data ) : fn; }; })();
You would use it against templates written like this (it doesn’t have to be in this particular manner – but it’s a style that I enjoy):
<script type="text/html" id="item_tmpl"> <div id="<%=id%>" class="<%=(i % 2 == 1 ? " even" : "")%>"> <div class="grid_1 alpha right"> <img class="righted" src="<%=profile_image_url%>"/> </div> <div class="grid_6 omega contents"> <p><b><a href="/<%=from_user%>"><%=from_user%></a>:</b> <%=text%></p> </div> </div> </script>
You can also inline script:
<script type="text/html" id="user_tmpl"> <% for ( var i = 0; i < users.length; i++ ) { %> <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li> <% } %> </script>
Quick tip: Embedding scripts in your page that have a unknown content-type (such is the case here – the browser doesn’t know how to execute a text/html script) are simply ignored by the browser – and by search engines and screenreaders. It’s a perfect cloaking device for sneaking templates into your page. I like to use this technique for quick-and-dirty cases where I just need a little template or two on the page and want something light and fast.
and you would use it from script like so:
var results = document.getElementById("results"); results.innerHTML = tmpl("item_tmpl", dataObject);
You could pre-compile the results for later use. If you call the templating function with only an ID (or a template code) then it’ll return a pre-compiled function that you can execute later:
var show_user = tmpl(“item_tmpl”), html = “”;
for ( var i = 0; i < users.length; i++ ) {
html += show_user( users[i] );
}[/js]
The biggest falling-down of the method, at this point, is the parsing/conversion code - it could probably use a little love. It does use one technique that I enjoy, though: If you're searching and replacing through a string with a static search and a static replace it's faster to perform the action with .split(“match”).join(“replace”)
– which seems counter-intuitive but it manages to work that way in most modern browsers. (There are changes going in place to grossly improve the performance of .replace(/match/g, "replace")
in the next version of Firefox – so the previous statement won’t be the case for long.)
Feel free to have fun with it – I’d be very curious to see what mutations occur with the script. Since it’s so simple it seems like there’s a lot that can still be done with it.
Kyle Simpson (July 16, 2008 at 11:44 pm)
the “text/html” script island for hiding/cloaking data (in this case, a template) is quite interesting. Thanks for the tips!
Iraê (July 17, 2008 at 1:56 am)
Awesome!
Its amusing to see this kind of tricks. Also very useful.
I currently have some pages witch are rendered with all data coming from JSONP calls (I know, it sucks, but it’s not my decision) and I use one external javascript with the following pattern for templates:
markup={};
markup.user = function() {
html='<dl>\
<dt>'+this.name+'</dt>\
<dd>'+this.about+'</dd>\
</dl>';
return html;
}
And I call this functions with:
$('#output').html(markup.user.apply(data));
In my tests this was a pretty fast method. But now I’m looking forward to get to work tomorrow to try out your templates.
I just can’t wait for the book!
michele (July 17, 2008 at 2:15 am)
Very cool, you rock, I can’t wait to get my hands on your next book!
Edwin Khodabakchian (July 17, 2008 at 2:37 am)
Hi John,
You might want to take a look at that:
http://www.devhd.com/item0002.htm
Similar to what you are describing but with pre-compilation support
-Edwin
Lenny (July 17, 2008 at 3:30 am)
Funnily enough I started working on a very similar problem only yesterday, except using XSL loaded via XHR to populate templates with JS objects. Yet again I am made to feel slightly inadequate by your solution…
Very impressed by the type=”text/html” workaround though. I can’t help thinking it’s a little too based around browser quirks for my liking, and as such I might be loathed to use it in production builds in case it gets “fixed” in future browser versions. Maybe I’m being paranoid.
Andrea Giammarchi (July 17, 2008 at 3:33 am)
John, I think it is interesting but I cannot spot portability or “component” re-usability, over performances.
What I mean, is that XSL(T) does similar things, in a more standard way, and using browser core functions to perform those tasks.
Am I that wrong?
Bakyt Niyazov (July 17, 2008 at 3:54 am)
I was looking for it :D
And was thinking to write myself.
type=”text/html” – it is something new and seems to be very very useful.
But no one could do it better than you!
Valentin Vago (July 17, 2008 at 4:27 am)
Excellent!
I was looking at such functionnality/feature and started to write it myself (but i will defenetly use yours as I feel myself under skilled :) )
by the way, i agree with Bakyt Niyazov, no one could do it better than you and I would like to thanks you for all what you share with us!
John Resig (July 17, 2008 at 7:25 am)
@Edwin: Seems like an interesting solution. I don’t think there’s really that big of a hit in the compilation stage (at least not for what I’m showing above) so handling it in pure JavaScript would seem to be fine, for now. Obviously for something that’s really long a pre-compiled solution would be better.
@Lenny: This isn’t really a feature that browsers can “fix”. It’s fully supported by browsers that you can use multiple scripting languages within a page (browsers may not support the individual languages, but they allow them to exist and don’t manipulate them). Search engines and screen readers definitely agree on this point so it’s actually a very safe solution.
@Andrea: Except that all browsers don’t support client-side, JavaScript-accessible, XSL transformations. If they did then that would certainly be a viable option.
Ludwig Pettersson (July 17, 2008 at 7:51 am)
Best book name, ever.
I haven’t seen a client side template system before, so it’s pretty strange – it feels wrong somehow. Still, great work!
Andrea Giammarchi (July 17, 2008 at 9:18 am)
@John,
>> Except that all browsers don’t support client-side, JavaScript-accessible, XSL transformations
and here we come, don’t we? :D
It could be interesting to base a client side template with XSL-T model, so browsers that support them will be truly fast, while others still compatible (but I think a lot of browsers support native XSL transformation).
Anyway, is there some library capable to implement them?
Brian R (July 17, 2008 at 9:28 am)
Seems like everybody wants to write a JS templating solution. I’ve been using TrimPath JST for quite a while with good success.
That trick with the script block though — very cool.
Lenny (July 17, 2008 at 9:45 am)
@John – on second thoughts, you are of course correct, I think I was just distracted by you saying “the browser doesn’t know how to execute a text/html script” and my mind thinking that browsers should naturally be able to understand text/html.
You could obviously put anything in place of text/html (as long as it isn’t an actual script type) and render my paranoia moot.
As you said, again, this is only really a quick and dirty solution for “sneaking templates into the page”. I’m guessing if you were going to apply this more rigorously then you’d probably use some kind of XHR to load the templates?
If I get anywhere with my XSL based implementation (and can persuade the people paying me to write it that they don’t mind) then I’ll post it somewhere.
John Resig (July 17, 2008 at 9:56 am)
@Andrea: Ah – specifically I was thinking of Safari 2. Although the usage of that browser has dropped dramatically so it’s probably far less of an issue, now. Here’s an XSLT.js script I found after a minute of searching.
@Lenny: Correct – any sort of content-type would work there – and absolutely using XHR to load in complex material would probably be ideal.
Kyle Simpson (July 17, 2008 at 11:52 am)
@John:
Have you tried using this with a src attribute on your script tag? In other words, pulling in a template externally instead of embedding it in the HTML stream?
I can’t seem to make this work. IE-Debugbar and FF-Firebug seem to show that the contents do get loaded, but I can’t access them via innerHTML like I can with inline stuff… wonder why… If I could figure that part out, I would think this would be a really helpful technique.
Wade Harrell (July 17, 2008 at 12:05 pm)
Does client side XSLT rear its head again?!? Oh Safari 2 how I loath thee. I have been waiting for the acceptance of client-side XSLT for what seems like forever now… but then JSON came along and nobody wants to send XML to the client anymore. (oh the heartache to choose between json and xslt)
Kevin H (July 17, 2008 at 1:45 pm)
I’m used to seeing templates embedded in a hidden textarea. Do you think there are benefits to using script over textarea?
As far as the templating function, it is a nice compact little thing, but I prefer a more feature-filled template language, like TrimPath JST
Leo Horie (July 17, 2008 at 4:42 pm)
@Kevin: off the top of my head, I reckon doing forms with a textarea-bound template would cause a few headaches since I assume having another textarea inside the template would effectively close the container textarea prematurely. A script tag sounds safe in that sense assuming you use unobtrusive javascript.
@John: Just by curiosity, what benefits do you see to using client-side templates over doing the templating on the server-side, taking into account what you mentioned about XHR being an ideal way of loading the content?
Chuckchillout (July 17, 2008 at 5:21 pm)
I’ve used JSONT a few times and this seems to accomplish the same. Does anyone see any advantages/disadvantages between the 2 approaches?
Jethro Larson (July 17, 2008 at 6:02 pm)
I’ve been playing with ejs and that’s been fun. It still feels very alpha but I’d like to see it get more robust. I like your solution, I would just like to see it get a little bigger.
Jon (July 17, 2008 at 8:58 pm)
@Wade I would like to see pure XML/xform (not predefined XHTML) and CSS . I just feel XSLT is a technology that shouldn’t be used to remedy browser inadequacy. Just need to have script capabilities in XML files. Xform functions are not powerful enough.
Iraê (July 18, 2008 at 12:31 am)
@Kevin H: Templates in textareas are a nightmare. Depending on how you access this information the data could be parsed by ID and you get an weird output. I’ve seen a really old code in where I work witch used textareas for
<object><embed /></object>
that IE6 and IE7 returned only the ’embed’ part, stripping the entire ‘object’ tag.@Leo Horie: I think that John expects this templates to be useful in pages where you fetch your data or process your data in javascript by need, not by choice. i.e. when you are accessing an external JSONP API or when you wish to work with data in a desktop-like fell, such as Y~Mail. At least I wouldn’t use it purposefully on any kind of content I could use serverside parsing, since SEO is a big concern today.
Bertrand Le Roy (July 18, 2008 at 1:33 am)
Interesting. We converged on similar ideas on our own template engine: we also build a function (once) for the template and run it to instantiate, use JavaScript as the expression language and use “with” to bring the data object’s members into scope. We totally differ on the template parsing though, and one thing that isn’t quite clear from reading your code is whether you’re handling encoding and how resilient this is to injection.
John (July 18, 2008 at 6:01 am)
Thanks for sharing!. I’m learning so much from trying to run and understand your code. I get this error when I run the micro template code:
text is not defined
[Break on this error] + “‘);}return p.join(”);”
This is how I’m calling the tmpl function:
var results = document.getElementById("results");
var dataObject = {};
dataObject.from_user = "#";
dataObject.profile_image_url = "/test/";
dataObject.id = "test";
results.innerHTML = tmpl("item_tmpl", dataObject);
Can anyone help?
Peter Benoit (July 18, 2008 at 10:14 am)
I might be wrong, but it appears your template looking for “text” from your dataObject?
John Resig (July 18, 2008 at 2:54 pm)
@John: Do you have a demo page up anywhere so I can take a quick peek? I tweaked a demo that a user sent me yesterday and put it online here, if you want to see more examples.
henrah (July 21, 2008 at 10:45 am)
Surely the greatest advantage of doing inline templating is that in a server-side javascript environment, you can choose on the fly whether to do template processing beforehand and serve a static page or do dynamic updates on the client, depending on the capabilities of the requesting browser.
Bravo for this excellent work.
Sean Catchpol (July 22, 2008 at 11:43 am)
John this is excellent. The
<script type="text/html">
is particularly useful.Josh Rehman (July 22, 2008 at 8:01 pm)
I don’t understand how this code works! For example:
1. Why this.tmpl = function tmpl(…){}? Why not “tmpl”: function(){}? (This is a matter of convention I think, and not all that important).
2. I really don’t understand the body of tmpl()! What the heck is going on there?
WRT #2, I think it would be interesting to share how you built this script – for example, did you start with a concrete test template and data, write a script that did the interpolation, and then generalize it?
Also WRT#2 it would be interesting to know which functions are “built in” and which you are defining. What makes it particularly confusing is the absence of an eval() – all I see is this mysterious push().
Another problem, I think, is the syntax highlighting. The third replace in the anon function is highlighted as a literal string. Since there is already some meta-programming going on, this confuses things. :)
There’s actually a third problem – the API might be better documented: tmpl takes the ID of a DOM elt that contains template data, and an Object that represents the data, and returns the interpolated string. One thing that puzzles me
Mr Speaker (July 22, 2008 at 8:30 pm)
The demo template uses: “i % 2” to set the class name – Presumably “i” is the loop index… I was wondering how you set “i” in your data object? Do you have to do “user[i].i = i” in the loop before you call the tmpl function?
diyism (July 22, 2008 at 9:53 pm)
Why not use textarea as template container just like that in TrimPath JS Template?
<textarea id="item_tmpl" style="display:none;">
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</textarea>
Josh Rehman (July 22, 2008 at 11:45 pm)
@Mr Speaker I’ve spent some time this afternoon unfolding this code, and the simple answer to your question is because the template is converted to JavaScript by stripping the tokens and wrapping the rest of it in string literals, with special care taken for the case.
The code is terse, fast, completely unreadable, and a great exercise for the JavaScript student to decipher. :) There is a high density of interesting things here.
As interesting as this code is, and potentially useful, I’m concerned that it might lead people astray – templating is BAD. One of the reasons I like jQuery is that you can do “passive templating” with it – use plain old static HTML as a template, using contextual hints to do your “templating”. I mean, I’m all for client side templating – but of the passive, unintrusive sort.
diyism (July 23, 2008 at 12:04 am)
Guys, try these:
<script>
(function(){
var cache = {};
if (document.all) // ie6
{var tag_start='<!';
var tag_end='!>';
}
else
{var tag_start='<!--';
var tag_end='-->';
}
this.tmpl = function tmpl(str, data){//alert(document.getElementById(str).innerHTML);
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split(tag_start).join("\t")
.replace(new RegExp("((^|"+tag_end+")[^\\t]*)'", "g"), "$1\r")
.replace(new RegExp("\\t=(.*?)"+tag_end, "g"), "',$1,'")
.split("\t").join("');")
.split(tag_end).join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
if (data)
{document.getElementById(str).innerHTML=fn( data )
}
else
{return fn;
}
};
})();
</script>
<div id="tpl"> <br>
<!for (i=0;i<users.length;++i){!>
Name:<!=users[i].name!><br>
<!}!>
</div>
<script>
var data={users:[{name:'name1'},
{name:'name2'}
]
};
//var results = document.getElementById("tpl");
tmpl("tpl", data);
var data={users:[{name:'second name1'},
{name:'second name2'}
]
};
tmpl("tpl", data);
</script>
Josh Rehman (July 23, 2008 at 12:46 am)
Well, I just learned that code in comments have to be specially treated. :)
Anyway, I’ve posted a pretty detailed code-review to my blog. There are still two pieces I don’t quite understand, but I hope it’s helpful anyway.
Henry Belmont (July 25, 2008 at 11:41 am)
@diyism Agreed…I think using the textarea is the way to go if you really must have the template code in your HTML (check out PureJSTemplate which is also available as a JQuery plug-in).
I’d much prefer to keep everything in a separate file and load in via Ajax.
Cool example though… Thanks John!
diyism (July 25, 2008 at 7:46 pm)
Henry Belmont, do you realized that my sample code above is showing how to use micro-template without a seperate template container? That’s the point.
m0n5t3r (July 28, 2008 at 10:06 am)
very cool, indeed, but unfortunately if you slap this kind of template into a html fragment loaded via Ajax, jQuery will kill it somehow (I think it moves all script elements to the head section so they are executed, then removes them for good) :(
Derek Fowler (July 28, 2008 at 5:53 pm)
That
.split("match").join("replace")
performance oddity is true of VBScript as well.Nice utility anyway. Cheers.
Rich Collins (July 28, 2008 at 5:58 pm)
Reposting
<% jQuery.each(foos, function(i, foo) { %gt;
<span><%= foo %></span>
<% if(i < foos.length - 1){ %>
|
<% } %>
<% }); %>
diyism (July 28, 2008 at 9:35 pm)
Guys, i’ve got an idea: using micro-template as jquery extension,
my code is ugly, John Resig, could you help me to improve it?
<script src="jquery-1.2.3.js"></script>
<script>
jQuery.fn.extend({drink: function(json)
{window.tpl_cache = (typeof(window.tpl_cache)=='undefined')?{}:window.tpl_cache;
var name_class = this.attr('class');
var tpl = tpl_cache[name_class] = tpl_cache[name_class] || unescape(this.html());
tpl=tpl.replace(/[\r\t\n]/g, " ")
.replace(/<!--/g, "<!")
.replace(/-->/g, "!>")
.split('<!').join("\t")
.replace(/((^|!>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)!>/g, "',$1,'")
.split("\t").join("');")
.split('!>').join("p.push('")
.split("\r").join("\\'");
var json_2_html
=new Function('the_json',
"var p=[];with (the_json){p.push('"+tpl+"');}return p.join('');"
);
this.html(json_2_html(json));
}
}
);
</script>
<div id="test1" class="users"><br>
<!for (i=0;i<users.length;++i){!>
Name:<a href="<!=users[i].name!>"><!=users[i].name!></a><br>
<!}!>
</div><br>
somthing else ...
<div id="test2" class="users">
</div>
<script>
var data={users:[{name:'name1'},
{name:'name2'}
]
};
$('#test1').drink(data);
var data={users:[{name:'second name1'},
{name:'second name2'}
]
};
$('#test1').drink(data);
$('#test2').drink(data);
</script>
diyism (July 28, 2008 at 10:58 pm)
Thus is more concise:
jQuery.fn.extend({drink: function(json)
{var name_class = this.attr('class');
var name_tpl_fun_cache = 'tpl_fun_cache_'+name_class;
if (typeof(window[name_tpl_fun_cache])=='undefined')
{var tpl=unescape(this.html())
.replace(/[\r\t\n]/g, " ")
.replace(/<!--/g, "<!")
.replace(/-->/g, "!>")
.split('<!').join("\t")
.replace(/((^|!>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)!>/g, "',$1,'")
.split("\t").join("');")
.split('!>').join("p.push('")
.split("\r").join("\\'");
window[name_tpl_fun_cache]
=new Function('the_json',
"var p=[];with (the_json){p.push('"+tpl+"');}return p.join('');"
);
}
this.html(window[name_tpl_fun_cache](json));
}
}
);
m0n5t3r (July 29, 2008 at 6:48 am)
jQuery.sprintf($(‘%(value1)s and %(value2)s’, {value1: ‘this is text 1′, value2:’this is text2’}) => “this is text 1 and this is text 2”
I find this works just fine for my needs :)
m0n5t3r (July 29, 2008 at 6:49 am)
erm, there is a typo above, but you get the point :-/
ak (July 29, 2008 at 8:51 pm)
PLEASE NOTE: Make sure you give your template an id without dashes – this will break the template function. So instead of id=”tpl-detail” use id=”tpl_detail”. John does so himself but he didn’t mention this explicitly. Nonetheless, great piece of code.
diyism (July 30, 2008 at 2:44 am)
Maturer example, with data application of template-in-template:
<script src="jquery-1.2.3.js"></script>
<script>
$.fn.drink = function(json)
{if (!(arguments.callee.name_class = this.attr('class')))
{return false;
}
var name_tpl_fun_cache = 'tpl_fun_cache_'+arguments.callee.name_class;
if (typeof(window[name_tpl_fun_cache])=='undefined')
{var tpl=unescape(this.html())
.replace(/<!--|<!/g, "<!")
.replace(/-->|!>/g, "!>")
.replace(/\r/g, ' ')
.replace(/\n/g, "\\\n")
.split('<!').join("\r")
.replace(/((^|!>)[^\r]*)'/g, "$1\\'")
.replace(/\r=(.*?)!>/g, "',$1,'")
.split("\r").join("');")
.split('!>').join("p.push('");
window[name_tpl_fun_cache]
=new Function('the_json',
"var p=[];with (the_json){p.push('"+tpl+"');}return p.join('');"
);
}
if (!json)
{return false;
}
this.html(window[name_tpl_fun_cache](json));
};
</script>
<div id="test1" class="users"><br>
<!for (var i=0;i<users.length;++i){!>
<div id="user_<!=i!>" class='user'>Name:<a href="<!=users[i].name!>"><!=users[i].name!></a></div>
<!}!>
</div><pre>somthing
else ...</pre>
<div id="test2" class="users">
</div>
<script>
$('.user').drink();
var data1={users:[{name:'name1.1'},
{name:'name1.2'}
]
};
$('#test1').drink(data1);
var data2={users:[{name:'name2.1'},
{name:'name2.2'}
]
};
$('#test2').drink(data2);
data1.users[1].name='name1.2.changed';
$('#test1 #user_1').drink($.extend(data1, {i:1}));
</script>
diyism (July 30, 2008 at 2:49 am)
Take care, in the middle, it should be:
{var tpl=unescape(this.html())
.replace(/<!--|<!/g, "<!")
.replace(/-->|!>/g, "!>")
diyism (July 31, 2008 at 6:56 am)
New edition:
$.fn.drink = function(json)
{if (!(arguments.callee.name_class = this.attr('class')))
{return false;
}
var name_tpl_fun_cache = 'tpl_fun_cache_'+arguments.callee.name_class;
if (!window[name_tpl_fun_cache])
{var tpl=unescape(this.html())
.replace(/<!--|<!/g, "<!")
.replace(/-->|!>/g, "!>")
.replace(/\r/g, ' ')
.replace(/\n/g, "\\\n")
.split('<!').join("\r")
.replace(/(^|!>)(([^\r]*?')*)/g,
function(str0, str1, str2)
{return str1+str2.replace(/'/g, "\\'");
}
)
.replace(/\r=(.*?)!>/g, "',$1,'")
.split("\r").join("');")
.split('!>').join("p.push('");
window[name_tpl_fun_cache]
=new Function('the_json',
"var p=[];with (the_json){p.push('"+tpl+"');}return p.join('');"
);
}
if (!json)
{return false;
}
this.html(window[name_tpl_fun_cache](json));
};
Michal (July 31, 2008 at 9:47 pm)
very nice idea!! also i like the idea of using the “src” attribute as Kylie mentioned .. maybe for XHR
Christiaan (August 1, 2008 at 4:07 am)
I gave a jquery extension for this nice tmpl function a go, since the one I’ve seen here already didn’t fit my needs.
(function($){
var cache = {};
$.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split(")[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
$.fn.tmpl = function(str, data){
return this.each(function(){
$(this).html($.tmpl(str, data));
});
};
})(jQuery);
Flug Bangkok (August 1, 2008 at 4:55 am)
it´s fun to read the codes and all your ideas! that´s nice peace of work.
Stephen McKamey (August 3, 2008 at 7:38 pm)
Thanks John. I enjoy reading about your seemingly endless supply of projects.
This post inspired me to finally write up a demo of some templating ideas I’ve been working on. It is on a similar vein but with a twist: I pre-compile the ASP/JSP style template on the server to an intermediate format that I call JsonML+BST. Because it is valid JavaScript it doesn’t need any special markup islands, just a script tag.
For a description: http://jsonml.org/BST/
For an example: http://jsonml.org/BST/Example/
Thanks,
smm
Yves Hiernaux (August 5, 2008 at 1:58 am)
Hi John,
It seems templating utility tools are quite fashion those days.
I am part of a fresh web start up and during the development phase of our solution we wanted to find an easy way to render JSON on the client.
We have taken a different approach than you.
You can sum up our expectations with this:
1) It should only be based on JavaScript and HTML/CSS
2) It should provide the 4 key templating functionalities: assign values, include templates within templates, iterative and conditional statements
3) It should be totally unobtrusive (no % tags of any kind)
4) It should compile the templates in JS for fast rendering and bandwidth savings
After some researches, we hadn’t found anything as we wanted. So we decided to give it a try ourselves.
We created PURE (PURE Unobtrusive Rendering Engine).
To our main surprise it resulted in a totally new way of building web pages (at least we like to think so).
There are no real templates anymore and no template language either.
You can easily turn any part of the HTML into a template-like utility.
We created an Open Source project with the code and the first version is supporting jQuery. We will package it soon to have a real jQuery plug-in.
We have posted the full story on our blog: http://beebole.com/blog/2008/07/31/generate-html-from-a-json-without-any-template-but-html-and-javascript/
And there is a dedicated website: http://beebole.com/pure/
If you have a look, any feedback is more than welcome.
diyism (August 5, 2008 at 3:29 am)
Yves Hiernaux, sorry for my straightness, but your solution is very obtrusive in fact, we needn’t those span’s or div’s indeed, they are ruining the pure text or html, to give a try to my “micro_template v.k885”:
<script src="jquery-1.2.3.js"></script>
<script>
$.fn.drink = function(json)
{if (!(arguments.callee.name_class = this.attr('class')))
{return false;
}
var name_tpl_fun_cache = 'tpl_fun_cache_'+arguments.callee.name_class;
if (!window[name_tpl_fun_cache])
{var tpl=unescape(this.html())
.replace(/<!--|<!|\/\*/g, "<!")
.replace(/-->|!>|\*\//g, "!>")
.replace(/\r|\*="/g, ' ')
.replace(/\n/g, "\\\n")
.split('<!').join("\r")
.replace(/(?:^|!>)(?:[^\r]*?')*/g, function(){return arguments[0].replace(/'/g, "\\'");})
.replace(/\r=(.*?)!>/g, "',$1,'")
.split("\r").join("');")
.split('!>').join("p.push('");
window[name_tpl_fun_cache]
=new Function('json',
"var p=[];with (json){p.push('"+tpl+"');}return p.join('');"
);
}
if (!json)
{return false;
}
this.html(window[name_tpl_fun_cache](json));
};
</script>
<div id="test1" class="users"><br>
<!for (var i=0;i<users.length;++i){!>
<div onMouseOver="/*=users[i].color?'this.style.color=\''+users[i].color+'\';':''*/" id="user_<!=i!>" class='user'>Name:<a href="<!=users[i].name!>"><!=users[i].name!></a></div>
<!}!>
</div>
<pre>somthing
else ...</pre>
<div id="test2" class="users">
</div><br><br>
<div id="test3" class="names">
<!for (var i=0;i<json.length;++i){!>
<!=json[i].name!><br>
<!}!>
</div><br><br>
<div id="test4" class="checks">
<input type="checkbox" name="is_done" *="<!=is_done?'checked':''!>*=" value="1">
<input type="checkbox" name="is_gone" *="<!=is_gone?'checked':''!>*=" value="1">
<input type="checkbox" name="is_post" *="<!=is_post?'checked':''!>*=" value="1">
</div>
<script>
$('.user').drink();
var data1={users:[{name:'name1.1'},
{name:'name1.2', color:'yellow'}
]
};
$('#test1').drink(data1);
var data2={users:[{name:'name2.1', color:'red'},
{name:'name2.2', color:'yellow'}
]
};
$('#test2').drink(data2);
data1.users[1].name='name1.2.changed';
$('#test1 #user_1').drink($.extend(data1, {i:1}));
var data3=[{name:'name3.1'}, {name:'name3.2'}];
$('#test3').drink(data3);
$('#test4').drink({is_done:0, is_gone:1, is_post:1});
</script>
Mic (August 5, 2008 at 5:24 am)
Hi diyism.
I’m a member and main contributor of the PURE project, and I’ll be straight too ;)
In fact any HTML tag or attribute can be used as a placeholder for data.
The span and div’s you are referring to are for design purpose only of the example 1.
Take a look at the others.
I took one of your example above:
” class=’user’>Name:“>
With PURE the logic(JS) and the view (HTML) are totally separated
Your HTML would look like this:
Name:User name
And your logic would sit somewhere in a JS file.
Which one do you think is less “obtrusive”?
Mic (August 5, 2008 at 5:41 am)
Sorry, I missed de code wrapping in the post above.
An extract from your code:
<div id="test1" class="users"><br>
<!for (var i=0;i<users.length;++i){!>
<div onMouseOver="/*=users[i].color?'this.style.color=\''+users[i].color+'\';':''*/" id="user_<!=i!>" class='user'>Name:<a href="<!=users[i].name!>"><!=users[i].name!></a></div>
<!}!>
</div>
With PURE the HTML would be like this:
<div id="test1" class="users"><br>
<div class='user'>Name:<a href="#">User name</a></div>
</div>
While the logic would sit somewhere in an JS file.
So, which one do you think is less “obtrusive”?
diyism (August 7, 2008 at 2:34 am)
micro_template v.k887:
<script src="http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js"></script>
<script>
$.fn.drink = function(json)
{if (!(arguments.callee.name_class = this.attr('class')))
{return false;
}
var name_tpl_fun_cache = 'tpl_fun_cache_'+arguments.callee.name_class;
if (!window[name_tpl_fun_cache])
{var tpl=unescape(this.html())
.replace(/<!--|<!|\/\*/g, "<!")
.replace(/-->|!>|\*\//g, "!>")
.replace(/\r|\*="/g, ' ')
.split('<!').join("\r")
.replace(/(?:^|!>)[^\r]*/g, function(){return arguments[0].replace(/'/g, "\\$&").replace(/\n/g, "\\n");})
.replace(/\r=(.*?)!>/g, "',$1,'")
.split("\r").join("');")
.split('!>').join("p.push('");
window[name_tpl_fun_cache]
=new Function('json',
"var p=[];with (json){p.push('"+tpl+"');}return p.join('');"
);
}
if (!json)
{return false;
}
this.html(window[name_tpl_fun_cache](json));
this.show();
};
</script>
<span style="display:none;">Take care, you must use "/* ... */" in onclick property etc. to avoid errors poped in ie.</span>
<div id="test1" class="users">
<!for (var i=0;i<users.length;++i)
{!>
<div onMouseOver="/*=users[i].color?'this.style.color=\''+users[i].color+'\';':''*/" id="user_<!=i!>" class='user'>Name:<a href="<!=users[i].name!>"><!=users[i].name!></a></div>
<!}
!>
<pre>'somthing'
else ...</pre>
</div>
<div id="test2" class="users">
</div><br><br>
<span style="display:none;">Take care, currently you must use comparison operator "<" or "%3e", not ">"!</span>
<div id="test3" class="names">
<!for (var i=0;i<json.length;++i){!>
<!if (0<i){!><!='<hr%3e'!><!}!>
<!=json[i].name!><br>
<!}!>
</div><br><br>
<span style="display:none;">Take care, you must use '*="' at both begin and end in tag property location!</span>
<div id="test4" class="checks">
<input type="checkbox" name="is_done" *="<!=is_done?'checked':''!>*=" value="1">
<input type="checkbox" name="is_gone" *="<!=is_gone?'checked':''!>*=" value="1">
<input type="checkbox" name="is_post" *="<!=is_post?'checked':''!>*=" value="1">
</div>
<script>
$('.user').drink();
var data1={users:[{name:'name1.1'},
{name:'name1.2', color:'yellow'}
]
};
$('#test1').drink(data1);
var data2={users:[{name:'name2.1', color:'red'},
{name:'name2.2', color:'yellow'}
]
};
$('#test2').drink(data2);
data1.users[1].name='name1.2.changed';
$('#test1 #user_1').drink($.extend(data1, {i:1}));
var data3=[{name:'name3.1'}, {name:'name3.2'}];
$('#test3').drink(data3);
$('#test4').drink({is_done:0, is_gone:1, is_post:1});
</script>
diyism (August 7, 2008 at 9:06 pm)
micro_template v.k888:
<script src="http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js"></script>
<script>
$.fn.drink = function(json)
{if (!(arguments.callee.name_class = this[0].className))
{this.each(function(){json[this.id] && (this.innerHTML=json[this.id]);});
return;
}
var name_tpl_fun_cache = 'tpl_fun_cache_'+arguments.callee.name_class;
if (!window[name_tpl_fun_cache])
{var tpl=unescape(this.html())
.replace(/<!--|<!|\/\*/g, "<!")
.replace(/-->|!>|\*\//g, "!>")
.replace(/\r|\*="/g, ' ')
.split('<!').join("\r")
.replace(/(?:^|!>)[^\r]*/g, function(){return arguments[0].replace(/'/g, "\\$&").replace(/\n/g, "\\n");})
.replace(/\r=(.*?)!>/g, "',$1,'")
.split("\r").join("');")
.split('!>').join("p.push('");
window[name_tpl_fun_cache]
=new Function('json',
"var p=[];with (json){p.push('"+tpl+"');}return p.join('');"
);
}
if (!json)
{return;
}
this.html(window[name_tpl_fun_cache](json));
this.show();
};
</script>
Welcolm, <span id="username"></span><br><br>
<span style="display:none;">Take care, you must use "/* ... */" in onclick property etc. to avoid errors poped in ie.</span>
<div id="test1" class="users">
<!for (var i=0;i<users.length;++i)
{!>
<div onMouseOver="/*=users[i].color?'this.style.color=\''+users[i].color+'\';':''*/" id="user_<!=i!>" class='user'>Name:<a href="<!=users[i].name!>"><!=users[i].name!></a></div>
<!}
!>
<pre>'somthing'
else ...</pre>
</div>
You have received a message: <span id="message"></span><br><br>
<div id="test2" class="users">
</div><br>
<span style="display:none;">Take care, currently you must use comparison operator "<" or "%3e", not ">"!</span>
<div id="test3" class="names">
<!for (var i=0;i<json.length;++i){!>
<!if (0<i){!><!='<hr%3e'!><!}!>
<!=json[i].name!><br>
<!}!>
</div><br>
<span style="display:none;">Take care, you must use '*="' at both begin and end in tag property location!</span>
<div id="test4" class="checks">
<input type="checkbox" name="is_done" *="<!=json[0]?'checked':''!>*=" value="1">
<input type="checkbox" name="is_gone" *="<!=json[1]?'checked':''!>*=" value="1">
<input type="checkbox" name="is_post" *="<!=json[2]?'checked':''!>*=" value="1">
</div><br>
This page has been viewed <span id="visit_times"></span> times.
<script>
$('.user').drink();
var data1={users:[{name:'name1.1'},
{name:'name1.2', color:'yellow'}
]
};
$('#test1').drink(data1);
var data2={users:[{name:'name2.1', color:'red'},
{name:'name2.2', color:'yellow'}
]
};
$('#test2').drink(data2);
data1.users[1].name='name1.2.changed';
$('#test1 #user_1').drink($.extend(data1, {i:1}));
var data3=[{name:'name3.1'}, {name:'name3.2'}];
$('#test3').drink(data3);
var data4=[1, 0, 1];
$('#test4').drink(data4);
var data_odds={username:'diyism', message:'Peter nudged you.', visit_times:54321};
$('#username,#message,#visit_times').drink(data_odds);
</script>
Jethro Larson (August 11, 2008 at 12:35 am)
I wrote some little erb-like helpers. They’re practically self explanitory, but if you hadn’t considered it here’s a sample:
function link_to(){
return "<a href='"+arguments[0]+"'>"+arguments[1]+"</a>";
}
// Usage: <%=link_to(url,text) %>
I’ll probably expand it to take more parameters but it’s just a simple addition to keep my template a little cleaner.
tatut (August 14, 2008 at 7:05 am)
Works perfectly in the Rhino version shipped with JDK 6.
So this can be used on the server side with the javax.script API.
Thanks John, you truly are a JavaScript Ninja :).
DanH (August 14, 2008 at 7:20 am)
John,
Late response so not sure if you’ll get it.
I like the use of with, but it’s worth noting the dangers of the global polution:
window.name = 'Ronald';
console.log(tmpl("Hello, <%= name %>!")({nsme:'Dan'}));
Which could lead to confusion for some.
Real nice code though :)
Jan Kumer (August 14, 2008 at 3:16 pm)
Hi,
just my recent observation: in case we’re using “Content-Type:application/xhtml+xml;” header, we should probably use XML data island like that:
">
]]>
And also tmlp() should be tweaked here:
document.getElementById(str).innerHTML
to:
document.getElementById(str).textContent
Jan Kumer (August 14, 2008 at 3:25 pm)
Hi,
just my recent observation: in case we’re using “Content-Type:application/xhtml+xml;” header, we should probably use XML data island like that:
<xml id="user_tmpl"><![CDATA[<%
for ( var i = 0; i < users.length; i++ ) {
%><li><a href="<%=users[i].url%>"><%=users[i].name%></a></li><%
}
%>]]></xml>
And also tmlp() should be tweaked here:
document.getElementById(str).innerHTML
to:
document.getElementById(str).textContent
diyism (August 14, 2008 at 9:12 pm)
micro_template v.k88f:
<script src="http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js"></script>
<script>
$.fn.drink = function(json)
{if (!(arguments.callee.name_class = this[0].className))
{this.each(function(){json[this.id] && (this.innerHTML=json[this.id]);});
return;
}
var name_tpl_fun_cache = 'tpl_fun_cache_'+arguments.callee.name_class;
if (!window[name_tpl_fun_cache])
{var tpl=unescape(this.html())
.replace(/<!--|<!|\/\*/g, "<!")
.replace(/-->|!>|\*\//g, "!>")
.replace(/\r|\*="/g, ' ')
.split('<!').join("\r")
.replace(/(?:^|!>)[^\r]*/g, function(){return arguments[0].replace(/'/g, "\\$&").replace(/\n/g, "\\n");})
.replace(/\r=(.*?)!>/g, "',$1,'")
.split("\r").join("');")
.split('!>').join("write.push('");
window[name_tpl_fun_cache]
=new Function('json',
"var write=[];with (json){write.push('"+tpl+"');}return write.join('');"
);
}
if (!json)
{return;
}
this.html(window[name_tpl_fun_cache](json));
this.show();
};
</script>
Welcolm, <span id="username"></span><br><br>
<span style="display:none;">Take care, you must use "/* ... */" in onclick property etc. to avoid errors poped in ie.</span>
<div id="test1" class="users">
<!for (var i=0;i<users.length;++i)
{!>
<div onMouseOver="/*=users[i].color?'this.style.color=\''+users[i].color+'\';':''*/" id="user_<!=i!>" class='user'>Name:<a href="<!=users[i].name!>"><!=users[i].name!></a></div>
<!}
!>
<pre>'somthing'
else ...</pre>
</div>
You have received a message: <span id="message"></span><br><br>
<div id="test2" class="users">
</div><br>
<span style="display:none;">Take care, currently you must use comparison operator "<" or "%3e", not ">"!</span>
<div id="test3" class="names">
<!for (var i=0;i<json.length;++i){!>
<!if (0<i){write.push('<hr%3e');}!>
<!=json[i].name!><br>
<!}!>
</div><br>
<span style="display:none;">Take care, you must use '*="' at both begin and end in tag property location!</span>
<div id="test4" class="checks">
<input type="checkbox" name="is_done" *="<!=json[0]?'checked':''!>*=" value="1">
<input type="checkbox" name="is_gone" *="<!=json[1]?'checked':''!>*=" value="1">
<input type="checkbox" name="is_post" *="<!=json[2]?'checked':''!>*=" value="1">
</div><br>
<span style="display:none;">Take care, you must use '*="' when setting style property!</span>
<div id="test5" class="bids">
<!for (var i=0;i<json.length;++i){!>
Bid price:<span *="<!if (json[i]['bid']%3e=1) {write.push('style=%22color:red;'+(json[i]['bid']%3e=5?'font-weight:bold;':'')+'%22');}!>*="><!=json[i]['bid']!></span><br>
<!}!>
</div><br>
This page has been viewed <span id="visit_times"></span> times.
<script>
$('.user').drink();
var data1={users:[{name:'name1.1'},
{name:'name1.2', color:'yellow'}
]
};
$('#test1').drink(data1);
var data2={users:[{name:'name2.1', color:'red'},
{name:'name2.2', color:'yellow'}
]
};
$('#test2').drink(data2);
data1.users[1].name='name1.2.changed';
$('#test1 #user_1').drink($.extend(data1, {i:1}));
var data3=[{name:'name3.1'}, {name:'name3.2'}];
$('#test3').drink(data3);
var data4=[1, 0, 1];
$('#test4').drink(data4);
var data5=[{bid:2.3}, {bid:5.3}, {bid:0.9}];
$('#test5').drink(data5);
var data_odds={username:'diyism', message:'Peter nudged you.', visit_times:54321};
$('#username,#message,#visit_times').drink(data_odds);
</script>
diyism (August 20, 2008 at 9:43 pm)
I’ve submit micro_template.v.k88f to jquery plugins:
http://plugins.jquery.com/project/micro_template
diyism (August 25, 2008 at 10:23 pm)
New release http://plugins.jquery.com/node/3694
A little bug fixed when single quote exist in plain text
Shawn_S (August 28, 2008 at 7:21 am)
Templating seems to be quite an issue at the moment. Seems we are all dealing with tons of AJAX data that has to somehow become markup.
I have created a jquery plugin that does the same but without any use of javascript except the actual declaration to fill a template.
You have for each loops here in the markup, I was trying a different approach in that if a template is bound to a single object it is filled once, if bound to an array of objects it is filled for each object.
I also had the need to create the ability to recurse down and object graph with one template and without knowing how deep the graph goes e.g. if you needed your template to display nested replies to a posting it would be impractical to add a subtemplate for each reply level.
In fact the forum on the site uses exactly that.
Perhaps you might like to have a look at it:
http://jsrepeater.devprog.com/
diyism (September 3, 2008 at 5:19 am)
Guys, take care, “jquery micro template” doesn’t support safari and opera,
because in that two you must use “<!–” and “–$gt;” instead of “<!” and “!$gt;”, that’s too bothering, so just fuck away safari and opera.
diyism (September 3, 2008 at 5:19 am)
Guys, take care, “jquery micro template” doesn’t support safari and opera,
because in that two you must use “
diyism (September 3, 2008 at 11:06 pm)
Because “google chrome browser” use AppleWebKit engine, so “jquery micro template” also doesn’t support it.
Kof (September 6, 2008 at 12:03 pm)
Does anybody know any way to get the template content, that John put into tag, without to include this directly into the page or load it via ajax. Its not so sexy to include the javascript plugin first and additionally include the template for this plugin. It is also not so good if the plugin will load his template via ajax by using this plugin in a big ajax powered applications.
Rick Strahl (October 9, 2008 at 6:03 am)
Looks like there’s a problem with the template engine when the template content contains single quotes. It appears single quotes are Ok, but more than 1 are causing a problem.
bombs the parser where
& Rick's ice cream
does not.
I played around with this a bit, but I can’t figure out how to make this work. My first thought was to escape any single quotes in the input template string, but that’s causing some side effects with the odd extra quotes showing up in the content.
Rick Strahl (October 9, 2008 at 6:07 am)
Ah crap, you gotta be kidding me – you’re not auto escaping this input on a tech blog??? :-}
Anyway, this fails for me:
<div id='name'><%= company %></div>
Neil Donewar (November 5, 2008 at 10:10 pm)
@Rick: I fixed the multiple quotes issue by rewriting the splits and replaces. Here’s the code, which is a drop in replacement for the “Convert the template…” section. Lines 2-4 are the only fundamentally different lines.
str.replace(/[\r\t\n]/g, " ")
.replace(/'(?=[^%]*%>)/g,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/<%=(.+?)%>/g, "',$1,'")
.split("<%").join("');")
.split("%>").join("p.push('")
+ "');}return p.join('');");
Jeremy Chone (November 10, 2008 at 3:13 pm)
Thanks a lot, this is a great utility. I use it with Neil’s fix, and it works great.
Would be cool to have micro-templating in jQuery. For example, append(..), html(..), text(..), could take template selector and data.
diyism (November 24, 2008 at 6:08 am)
Because of there’s no lookbehind regexp supporting in js.
We could work around it by callback function like micro_template v.k88f as above, we also could use lookahead instead of it:
var tpl=unescape(this.html())
.replace(/<!--|<!|\/\*/g, "<!")
.replace(/-->|!>|\*\//g, "!>")
.replace(/\r|\*="/g, ' ')
.split('!>').join("\r")
.replace(/('|\\)(?=[^\r]*?($|<!))/g, "\\$&")
.replace(/\n(?=[^\r]*?($|<!))/g, "\\n")
.replace(/<!=(.*?)\r/g, "',$1,'")
.split("<!").join("');")
.split("\r").join("write.push('");
but, maybe using callback is more speedy.
David Murdoch (December 10, 2008 at 9:07 am)
“<code>” will give you an error if you have your templates and code inline on an asp.net page. If anyone knows a way around this let me know.
David Murdoch (December 10, 2008 at 9:08 am)
Hopefully this will work this time:
“
<%
” will give you an error if you have your templates and code inline on an asp.net page. If anyone knows a way around this let me know.Steven Eberling (January 17, 2009 at 3:17 am)
You can get Opera and Safari support by using a delimiter other than . Safari and Opera correctly treat those tokens as identifying new tags.
Here’s a drop-in replacement with the quote-fix, too. Tested on FF3 and Opera. This replaces “” with “%}”.
str.replace(/[\r\t\n]/g, " ")
.replace(/'(?=[^%]*%})/g,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/{%=(.+?)%}/g, "',$1,'")
.split("{%").join("');")
.split("%}").join("p.push('")
+ "');}return p.join('');");
Cheers again John! This is great work. Have you considered doing micro-linking as well, where blocks on a page load like miniature webpages, each powered by a template? I’d be interested in your thoughts on a clean implementation.
diyism (January 17, 2009 at 10:46 pm)
@Steven Eberling,
Have you give a try to this?
http://plugins.jquery.com/project/micro_template
I don’t meant “John’s Micro-Templating” doesn’t support Opera and Safari,
i meant “jQuery micro template” never support Opera and Safari/Chrome.
some (January 30, 2009 at 4:38 pm)
Interesting idea, but since the content of script-tags is CDATA, the document will no longer be valid. Even if the content of the script tag is inside a html-comment there are errors for every end tag ( because it is not legal to have </ inside a CDATA)
I did some profiling and found that if ‘with (obj)’ is removed (you have to write ‘obj.id’ instead of just ‘id’ in the template) and the print-function too, (instead write ‘p.push(text)’ if you need the functionality) it will be 200%-400% faster (depending on browser, biggest difference in Chrome)
Kyle Simpson (February 15, 2009 at 1:59 pm)
Wondering if this idea would prove useful if combined with JSONML?
Oskar Austegard (February 24, 2009 at 5:51 pm)
<offtopic>
Uhm – David forgot to close his <code> tag and now everything is in fixed width font.
Let’s see Did this fix it?
</offtopic>
Oskar Austegard (February 24, 2009 at 5:51 pm)
Yes it did… ;-)
Jonathan Quimbly (March 6, 2009 at 2:13 am)
Sorry to say, but this is really and truly awful Javascript code. I have a feeling John’s flexing his muscles, showing off his hard-earned JS skills. Macho code, but difficult to work with.
First off, it fails silently when you have a JS syntax error in a template scriptlet or expression.
Second, the resulting template doesn’t consistently debug in Firebug, because it’s an anonymous function with no source file.
Third, there’s no handling for run-time errors caused by invalid data references.
It certainly is a clever-looking piece of code. But that scores no points when it comes to paying work. I invested an hour trying to convert a bunch of inline JS concatenation templates over to Resig’s JSP style, with it failing over and over without emitting any error. That’s a giant step backward for computer science.
In the end, I figured out what John was doing, and wrote my own version, which is itself debuggable, and also incorporates try/catches into the compiled template, so there’s some chance of catching runtime errors.
I’ll be back when my rendition is polished. Sorry John! Way too terse.
CurlyFro (March 6, 2009 at 9:24 pm)
@Jonathan Quimbly,
i can’t wait for you to share…
leonwoo (March 8, 2009 at 10:28 am)
I am curious.
Does this micro template cause any memory leak?
Stephen McKamey (March 8, 2009 at 3:57 pm)
@Kyle Simpson – try JBST:
http://jsonml.org/bst/example/
http://starterkit.jsonfx.net/#jbst
diyism (March 10, 2009 at 9:41 pm)
@Stephen McKamey
Why not give a try to “micro_template.v88f” in the upper comments.
diyism (March 10, 2009 at 9:42 pm)
@Stephen McKamey
Why not give a try to “micro_template v.k88f” in the upper comments.
Evan Byrne (April 18, 2009 at 8:42 am)
Again – and it seems like I’ve said it too many times – but templating should be done server-side, for accessibility reasons alone!
Vito Botta (May 4, 2009 at 8:49 am)
Hi John,
I am trying to use this excellent trick, but in a XSS context (need to display a survey on third party domains).
I am using JSONP to read the template’s HTML first, and then the data (with a second JSONP request), but I can’t find a way to successfully inject the SCRIPT element dynamically when the template’s HTML has been loaded.
For some reasons, the SCRIPT element is not appended as expected (I can’t see it with Firebug) and I get a number of cryptic errors.
If I use a DIV instead of a SCRIPT element, the HTML is loaded correctly but your tmpl function doesn’t properly interpret its content.
For example I can see the template’s HTML on the page, but I can also see the etc. and the for… loop is ignored (code is displayed only once).
Any ideas?
Many thanks in advance,
Vito
Vito Botta (May 5, 2009 at 8:58 am)
Still struggling with this… The templating works fine as long as the script block with type=’text/html’ containing the template, is already present in the original html of the page.
It does not work if instead I try to inject that script block dynamically (as said I need to display surveys in third party websites, so I cannot have that script block already in the main page).
If I append the script block, with jQuery or with pure JavaScript, I can see its content etc, but the tmpl function fails and I can’t find why (that code is not exactly the most straightforward to understand for me :D).
Any ideas?
Thanks in advance
Vito Botta (May 5, 2009 at 9:43 am)
Third comment in a row – sorry :)
Just found that I had made some changes to the page generating the JSON data, and hadn’t updated the template accordingly..
That was the issue. So all is working now, a huge thanks John for this cool stuff!
The only thing that I still want to investigate a bit further is that I could get it working by injecting the script tag with:
document.body.innerHTML += “” + templateHTML + “”;
However else I have tried using jQuery syntax, it would not work.
However I need to try again, perhaps this was not working properly for the same reason as above.
Again, many thanks.
Saurabh (May 15, 2009 at 1:11 am)
Hi John,
I am trying to use this template in Sharepoint Page. but doesn’t work for me
it didn’t accept
and it shows me an error of “System.Runtime.InteropServices.COMException”
any ideas??
Kind regards,
Saurabh
Ravi (May 19, 2009 at 5:22 am)
Hi John,
Why do we need print=function(){p.push.apply(p,arguments);};