UPDATE: A joint announcement has just been made by jQuery and Microsoft that the jQuery Templates, Data Link and Globalization plugins are now 'Official jQuery Plugins'. In addition, a full set of documentation for the jQuery Templates and Data Link plugins is now available on the http://api.jquery.com/ site. See my next post: jQuery Templates is now an Official jQuery Plugin for details.
In my last post, I said I planned to start a series of posts introducing jQuery Templates. This is the first of that series. This post also introduces the Sample Viewer, which you can use to try out your own jQuery templates. In a sense jQuery Templates consists of three plugins: .tmpl(), .tmplItem() and .template(), and each one comes in two flavors: instance plugin and static plugin. From a basic scenario point of view, it is like this:
- .tmpl(): Render the template
- .tmplItem(): Find the template item
- .template(): Compile/Store the template
- ${...}: Evaluate fields or expression
- {{each ...}}...{{/each}}: Iterate without creating template items
- {{if ...}}...{{else ...}}...{{/if}}: Conditional sections
- {{html ...}}: Insert markup from data
- {{tmpl ...}}: Composition, as template items
- {{wrap ...}}...{{/wrap}}: Composition, plus incorporation of wrapped HTML
Rendering a template using local data within the page
Here is some data:var movies = [ { Name: "The Red Violin", ReleaseYear: "1998" }, { Name: "Eyes Wide Shut", ReleaseYear: "1999" }, { Name: "The Inheritance", ReleaseYear: "1976" } ];Here is some markup to be used as a template:
<script id="movieTemplate" type="text/x-jquery-tmpl"> <li> <b>${Name}</b> (${ReleaseYear}) </li> </script>and a target element where we are going to render the result of rendering the template against our data:
<ul id="results"></ul>And here is some code to take the template, render it with the data, and append the resulting HTML as content under our target element:
$( "#movieTemplate" ).tmpl( movies ) .appendTo( "#results" );In the above code, we get a jQuery wrapped set containing our template markup, and use the .tmpl() plugin method (to which we pass the data) to render the template. Then we chain with the appendTo method to append the results under our target element:
Demo:
As you see the template got rendered once for each item in the movies array. Data values are inserted using the template tag ${...}. In fact ${expression} inserts the (HTML-encoded) result of evaluating the expression term, in the context of the current item.
The template engine actually exposes the current data item as the variable $data, and also exposes each of the fields of the current data item as individual variables. So the most simplest use-case of ${...} is just with a field name as expression, such as ${ReleaseYear}. This is actually equivalent to ${$data.ReleaseYear} and inserts the value of that field on the current data item. Pretty straightforward.
Here is the complete code of our example:
<script src="http://code.jquery.com/jquery.js" type="text/javascript"></script> <script src="jquery.tmpl.js" type="text/javascript"></script> <script id="movieTemplate" type="text/x-jquery-tmpl"> <li> <b>${Name}</b> (${ReleaseYear}) </li> </script> <ul id="results"></ul> <script type="text/javascript"> var movies = [ { Name: "The Red Violin", ReleaseYear: "1998" }, { Name: "Eyes Wide Shut", ReleaseYear: "1999" }, { Name: "The Inheritance", ReleaseYear: "1976" } ]; $( "#movieTemplate" ).tmpl( movies ) .appendTo( "#results" ); </script>
Playing with the data and the template: Sample Viewer
To get the feeling of how it works, here is the same demo again, but this time I have used a special Sample Viewer script which is integrated into my blog, so that if you mouse over the demo, you will see a little '+' button that you can click on. The result will be a tabbed view in which you can see the data and the template, as well as the result:
Demo with Sample Viewer (Mouse over...)
Try mousing over the demo above, clicking on the '+' button, and modifying the data or the template under the respective tabs. Go back to the result tab and you will see how it renders. Click on the '-' button, and you will be back with the orginal data and template...
By the way, this sample viewer is actually implemented using jQuery templates, and illustrates the kind of dynamic interactive client-side UI that can be built very easily with jQuery templates. Maybe at some point I'll reach the point of blogging about how I went about building the sample viewer. But for now, let's get back to just playing with it. For example, if you want to just change the data, and see how the template rendering works with your changes, here are some examples of changes to the data that you could explore:
Changing the data
Mouse over the demo above and expand the Sample Viewer. Now try copying and pasting the data examples below into the Data tab, and then switching back to the Result tab. Change values, number of elements etc.[ { "Name": "The BLUE Violin", "ReleaseYear": "1998" } ]Conclusion: It actually is data-driven :-) Remove some fields and add others:
[ { "Name": "The Red Violin", "ReleaseYear": "1998" }, { "Name": "Eyes Wide Shut" }, { "Name": "The Inheritance", "ReleaseYear": "1976", "Director": "Mauro Bolognini" } ]Conclusion: It ignores the missing/undefined values without error. If you put the value of those fields to null or to the empty string, the result is the same. And of course the added fields have no effect, unless you want to add ${Director} to the template. (Try it...) Replace the array with a single object:
{ Name: "The Red Violin", ReleaseYear: "1998" }Conclusion: The templating engine is smart about arrays. Pass an object and it renders the template once, with the object as data item. Pass it an array and it creates a template item (a rendered template) for each of the data items in the array. Set the data to null:
nullConclusion: If you pass no data at all, the templating engine still renders the template once, but the current data item is null. We will see that there are many scenarios where you are just rendering boiler-plate, or where the template pulls in data from other places than the data item, so passing data to the template is not always appropriate or relevant. It may be a nested template, and use data from the parent item. It may have template tags whose parameters are not simple values, but function calls, and the template is driven by the data returned by that function call. For example it might include {{each myApp.getData(foo)}}, or ${myApp.getData(foo)}. In this blog I am staying with much simpler examples, but we will see in later blogs how passing functions to template tags is extremely common. Include some HTML markup in the data:
{ "Name": "The <strong style="color: Red;">Red</strong> Violin", "ReleaseYear": "1998" }Conclusion: This does not change the formatting of the text. Instead, it shows the markup in the rendered UI. This is by design: ${expression} HTML-encodes the value before inserting it into the DOM. If you actually want your markup to get inserted into the DOM, then use the {{html ...}} template tag rather than the ${...} template tag. You can try it now: go to the #movieTemplate tab and replace ${Name} by {{html Name}}. Now the markup will not be escaped, and the data will actually get inserted as HTML. (We'll come back to this lower down in the blog). One detail: the sample viewer is using JSON2 to convert between string expressions and JavaScript objects. So the text you are editing above is actually JSON, not JavaScript literals. That's why the keys are wrapped in quotes. Try removing them, and the sample viewer will tell you that you have a syntax error! But in your script, of course, you have literals, and those quotes can be omitted (as long as you avoid JavaScript keywords!)
Changing the template
Let's try changing the template, now, rather than the data. For convenience, here is the sample viewer again. This time I set it to show the tabs from the get-go, so you don't need to mouse over and click the '+' button... Sample Viewer<li> <b>${Name}</b> (Released in the year ${ReleaseYear}) </li>Conclusion: It works as you would expect! Add some markup and some formatting:
<li> <b>${Name}</b> (<span style="color: Blue;">${ReleaseYear}<span>) </li>Conclusion: It works as you would expect... Add simple JavaScript expressions:
<li> <b>${Name.toUpperCase()}</b> (${parseInt(ReleaseYear) + 100}) </li>Conclusion: This works too. You can put JavaScript expressions as parameters to the tags. But don't go overboard! There is not a complete JavaScript parser in the context of inline expressions in the template. If you have complex code, write a function, and call the function from your template, and pass parameters if you need to: ${myFunction(a,b)}. (More on that in a later blog...) Add another template tag:
<li> <b>${Name}</b> (${ReleaseYear}) - Director: ${Director} </li>Conclusion: Nothing yet - there is no Director field... Now change the data too:
[ { "Name": "The Red Violin", "ReleaseYear": "1998", "Director": "Francois Girard" }, { "Name": "Eyes Wide Shut", "ReleaseYear": "1999", "Director": "Stanley Kubrick" }, { "Name": "The Inheritance", "ReleaseYear": "1976", "Director": "Mauro Bolognini" } ]Conclusion: Yes, it works as expected! Finally let's make the change I mentioned in the previous section, and get a 'teaser' on one of the other template tags to be covered in later blogs: the {{html ...}} tag... First, include HTML markup in the data:
{ "Name": "The <strong style='color: Red;'>Red</strong> Violin", "ReleaseYear": "1998" }Now change the template to use the {{html ...}} template tag instead of the ${...} template tag:
<li> <b>{{html Name}}</b> (${ReleaseYear}) </li>Conclusion: Thanks to the {{html ...}} tag, you can insert HTML markup into the DOM, as HTML. Combining some of the changes above to data and template, here is a working example that illustrates what you did, and which you can use for exploring further changes: Sample Viewer
<script src="http://code.jquery.com/jquery.js" type="text/javascript"></script> <script src="jquery.tmpl.js" type="text/javascript"></script> <script id="movieTemplate" type="text/x-jquery-tmpl"> <li> <b>{{html Name}}</b> (<span style="color: Blue"> ${ReleaseYear}</span>) - Director: ${Director} </li> </script> <ul id="results"></ul> <script type="text/javascript"> var movies = [ { Name: "The <strong style='color: red'>Red</strong> Violin", ReleaseYear: "1998", Director: "Francois Girard" }, { Name: "Eyes Wide Shut", ReleaseYear: "1999", Director: "Stanley Kubrick" }, { Name: "The Inheritance", ReleaseYear: "1976", Director: "Mauro Bolognini" } ]; $( "#movieTemplate" ).tmpl( movies ) .appendTo( "#results" ); </script>
Thanx! Very interesting! Please keep posting on tmpl...
ReplyDeleteThis confuses me a bit: "There is not a complete JavaScript parser in the context of inline expressions in the template" Do you mean there is a partial JavaScript parser in there or that you're filtering stuff out before handing it over to eval?
ReplyDeleteFantastic stuff Boris! Using this in a current project and it's amazingly useful.
ReplyDelete@Bertrand: Yes, there is a regex-based parse which finds the template tags in the markup, and determines the various parameters to pass to the tag implementation.
ReplyDeleteIn some case it is relative complex, since you can do things like {{tmpl(myData) myTemplate}} but myData and myTemplate can be expressions.
So it can get funky, if you want to put some complex JavaScript expressions - say:
{{if {a:1}.a === 2 }}
YES.
{{else ({a:1}.a) === test(1)}}
NO
{{/if}}
That will work, but if you have a JavaScript expression with }} in it somewhere, then you will make the Regex think this is the end of the template tag.
Of course the actual evaluation of JavaScript expressions is by the JavaScript engine, not the regex parsing...
Is it possible to configure the ${..} to something different. It clashes with some serverside templating languages, meaning that its somewhat klunky to embed jquery tempaltes in jsp files for instance.
ReplyDeleteYou can use {{= expression}}, rather than ${expression} if you wish. But if that also conflicts (e.g. Django) then for the moment, no configuration option, no, but on the table for an upcoming update.
ReplyDelete@xtblitz: Naming is always tricky. John Resig's orginal version had .render, but many people felt it was not a good idea to take over the render verb, and that it needed to be explicit that it was a templating method. .templateRender is long, as are all three of your suggestions, compared with all the common jQuery methods, so a lot of typing.
ReplyDelete.tmplItem returns a template item, not a template, and it doesn't find one, in the sense of .find (drilling down using a selector), but returns the item you are actually in.
The main scenario for .template is to associate a name with a template. (In fact for the instance method, it is already compiled). So the notion of compiling is not necessarily primary...
Hi Boris,
ReplyDeleteCongratulations for your work!
Please see what I'm doing with jQuery and family. In www.wisejs.com.
Be patient with the site, because it loads many full javascript libraries.
Your opinion is very important to me.
Best regards,
Carlos Henrique (carlos@infobras.com.br)
MS's involvement seems like recognition that jQuery is the best way to interface the a .net MVC app to the webpage. Once i totally get my head around the plugin i think i'll be able to clean up a lot of static HTML with <%.net stuff.%>
ReplyDeleteVery much looking forward to see how folks start to weave these 2 tools together.
I have an ASP.NET MVC action that retuns Json, how do I bind Json to the template? At the moment, it doesn't display the data. Thanks.
ReplyDelete@xuhanvuz: Just pass the object/array to the tmpl method. The JSON returned should get eval'ed as an object or an array. Look at the remote data examples, here, http://api.jquery.com/tmpl/, or the demos on GitHub for examples...
ReplyDeleteHi Boris, that's what I thought, but I'm having problem.
ReplyDeleteThis works:
var teams = [{ TeamName: "Defenders", Manager: "David" }, { TeamName: "Midfielders", Manager: "Paul"}]
$('#teamsTemplate').tmpl(teams).appendTo('#results');
This does not work, using Firebug, I can see that the "teams" object have values in them:
$.getJSON('/Home/GetTeams', function (teams) {
$('#teamsTemplate').tmpl(teams).appendTo('#results');
});
One more question: How do I put the templates (html) on a seperate file? What extension I should use and what do I need to load it? I want to keep the "view" separated.
Thanks.
@ xuanvuz: If the teams array is identical to the static version, then something else is different :). Is the results container element ready? Try not calling getJSON until after DOM Ready, and see if that works...
ReplyDeleteFor remote templates, it could be any text file, that you load with an AJAX call. It can also be a js file that you load statically or dynamically, in which the template string is defined. Then you should use jQuery.template to compile the template from the string, and go from there, rendering it using jQuery.tmpl.
Would you think that these templates could be used to generate data-entry forms and/or grids ?
ReplyDeleteIf that is the case, which forms & grids plugins would you recommend ?
Didier
In an earlier blog post by Scott Guthrie (http://weblogs.asp.net/scottgu/archive/2010/05/07/jquery-templates-and-data-linking-and-microsoft-contributing-to-jquery.aspx) he mentioned support for declarative data linking. Is something like that supported with the now official libraries? Templates and data linking serve two separate purposes, of course, and it would be great to be able to instantiate a template, append the results to an dom node, and then have the object used to instantiate the template be data linked to the resulting node contents: so if I update a property on the object after the template is instantiated, the html is updated. I'm thinking that can be done by using a template tag with a function that operates on the data item somehow. Is that correct? Is there an easier way?
ReplyDeleteThis is way cool, by the way!
Ah, I see an example of instantiating and then linking in one of the samples for data linking on github: http://github.com/jquery/jquery-datalink/blob/master/demos/demo-contacts.js
ReplyDeleteThat will do, of course. I'll have to dig some more into this, but I suspect you could do this with some code in the template tag that acts on data item that then establishes the link, too.
Hi,
ReplyDeleteI have been looking for a js template engine and came up with jQuery templates und PURE JS. Actually I prefer the way how PURE works due to the fact that I have a strict seperation between HTML, data and directive (each data is passed through the directive which refers to a class). In my opionion this is more flexible than the mix in jQuery template.
The big downside PURE JS is not working beneath IE8 :(
Well I also could append each data by itself but I don't think this is very practicable or?
Florian
@Didier: Yes certainly they can be used to create data-entry forms and grids. You can use templates to do that without using any other plugin. Or you can create your own using templates. There are likely to be some plugins appearing over time such as a gridview plugin, that use templates internally. jQueryUI plans to use jQuery Templates in some future UI plugins... But they are not available yet. (jQuery Templates is still in Beta...).
ReplyDelete@Mark: Yes the integration between Data Linking and Templates will be key to exactly the scenarios you describe. Right now you can do it as in the example you refer to. But stay tuned for much fuller integration in the future. (Including a {{link}} template tag...)
@lapp: You may be interested to know that you can create your own template tags, a bit like in Pure you can provide your own directives. Creating custom template tags is not yet documented as such, but I hope to provide more information and examples in the future. It's up to you whether you include the standard tags in your template - and you can separate code and markup as much as you want. You could also have custom tags themselves be 'owned' by a class...
I like this. I just incorporated it into one of the web applications I am building at Turner. Thanks!
ReplyDeleteHello Boris, thanks for all your work.
ReplyDeleteI have a question about nested templates. They work fine if I pass them a named template for example:
{{tmpl(RelatedInfo) 'relatedInfo' }}
however what if I wanted to pass the template as a string for example:
{{tmpl(RelatedInfo) '${RelatedUrl}'}}
the console gives:
missing ) in parenthetical
[Break On This Error] tent))));}_.push('RelatedStories '...}_.push('\' }}');}return _;
I can pass that string fine when using $.template(
according to the docs,
$.template( template )
and
{{tmpl( [data], [options] ) template}}
I pass the same string in both cases, however the second gets held up by the embedded ${
Is there a way around this? I would like to be able to send a raw string with embedded ${dataElm}'s in the nested template.
followup, i went with using {{each and embedding my html in the content section, that worked fine.
ReplyDeleteBoris, I need to escape a character in a call to replace() like this:
ReplyDelete{{html Description.replace(/\n/g, "<br />")}}
I'm trying to replace all newline characters with line breaks, but it doesn't work. I've tried escaping the backslash with any number of backslashes but still, the replacement does not occur. Do you have any suggestions?
Thanks
How would I go about using this with tables? Let's say I want to create a table template and then pass json values into a specific column?
ReplyDelete@techInvestor: Yes you can use {{each}} or else you can pass a named template. Passing a string to {{tmpl}}be considered as template content is not supported. You can also create a script block containing your template markup, in the usual way, and pass a selector to the script element: {{tmpl(data) "#myTemplate"}}.
ReplyDelete@Joel: It would be better to use {{html replaceNewLines(description)}} and define a function replaceNewLines() which does the replacing.
ReplyDeleteGenerally it is better to put complex code in script, within functions, rather than inline in the template. If you don't want to define global functions, you can pass functions in on the options too, and then call them in the template as: {{html $item.myFunction(foo)}}
@BuralPupp: Absolutely, you can use jQuery templates to render rows, by choosing the <tbody> tag as container, and using a template whose content is one or more <tr>s. Or you can use templates to render the contents of cells. See examples on https://github.com/jquery/jquery-tmpl/tree/master/demos
ReplyDeletefirst off not allowing tags or some way to render applicable snippets is a PITA...
ReplyDeleteI digress... I seem to have a problem getting html table data to work right...
the following works...
[Xscript id="TestTemplate" type="text/x-jquery-tmpl"]
{{if items}}
{{each items}}
[li]
${$value.ID}
${$value.Question}
[/li]
{{/each}}
{{/if}}
[/xscript]
As does...
[Xscript id="TestTemplate" type="text/x-jquery-tmpl"]
{{if items}}
{{each items}}
${$value.ID}
${$value.Question}
{{/each}}
{{/if}}
[/script]
This does not...
[script id="TestTemplate" type="text/x-jquery-tmpl"]
{{if items}}
{{each items}}
[tr]
[td]${$value.ID}[/td]
[td]${$value.Question}[/td]
[/tr]
{{/each}}
{{/if}}
[/script]
Am I missing something obvious?
@Bohemian: For putting tags in comments, you can escape the < as < This is controlled by Blogger comments system, not by me :).
ReplyDeleteFor your scenario, it works for me:
<script id="TestTemplate" type="text/x-jquery-tmpl">
{{if items}}
{{each items}}
<tr>
<td>${$value.ID}</td>
<td>${$value.Question}</td>
</tr>
{{/each}}
{{/if}}
</script>
Click for details:
<table><tbody id="movieList"></tbody></table>
<script type="text/javascript">
var data = { items: [
{ ID: "someid", Question: "a question" }
]};
$( "#TestTemplate" ).tmpl( data ).appendTo( "#movieList" );
</script>
</body>
</html>
Nice work with the layout of the blog – it is very easy on the eye
ReplyDeleteThank you very much.Its very useful tutorial.
ReplyDeleteHey thank you for understandable post.
ReplyDeleteJust {{html Name}} helpt me with my problem!
Thank you very much. Tried to find an easy post to start with jquery templates. Very useful, thanks
ReplyDeleteThanks a lot. This is what I was looking for.
ReplyDeleteHi can you tell me how can i get the index of json list each array object
ReplyDeleteThis is an example of how jQuery Templates are less complete and powerful as a template engine than JsRender. You can access $index from within {{each}}, but not from an array which you pass in to tmpl(). In JsRender you have getIndex() - which works for both scenarios. I would encourage you to switch to JsRender. Also jQuery Templates are no longer supported so I will be unlikely to be able to reply to specific questions of this kind, moving forward...
Delete