Monday, December 5, 2011

Upgrading from jQuery Templates to jsRender

In making my recent port from jQuery to Zepto, I was the most worried about porting over from jQuery templates - I knew there were other templating engines out there (like mustache.js, which comes highly recommended) but I also know templating engines can be vastly different and I didn't want to spend a lot of time porting.

Fortunately, I found out that jQuery templates have actually been deprecated in favor of jsRender, a revision of the library that isn't dependent on jQuery. Perfect!

The templating language for jsRender is largely the same as jQuery templates, with a few purely aesthetic syntax changes and one logical difference — atleast from the perspective of the parts that I was using. The jsRender library is still in development, so the differences might change in the future, but in the meantime, I thought I'd write up a quick upgrade guide for anyone else porting over.

The operators are now prefixed with a "#". You can pretty much do a search and replace to port them over - {{if}} to {{#if}}, {{each}} to {{#each}}.

Previously, variables were outputted with ${var} - now, variables are outputted using the same double brackets as operators, like so: {{=var}} and {{=var.property}}. If you're using a server-side templating engine that also uses that bracket notation for templating, you will need to instruct the engine to ignore the jsRender templates. For Jinja2 templates, that means surrounding the script tags with {% raw %}{% endraw %}.

In addition, the syntax for specifying you don't want HTML escaping changed from {{html var}} to {{var!}} (notice the exclamation mark at the end). Finally, if you want to access the value of the currently referenced variable, the syntax changed from ${$value} to {{=$data}}.

To give you an idea of what an upgraded template would look like, here's some example data and templates:

var templateData = {
  label: 'Comments on post for Wednesday, Dec. 5th',
  comments: [
     type: 'freetext',
     creator: {fullName: 'Pamela Fox', profileUrl: 'http://everyday.io/user/1'},
     textHtml: 'Have you tried macademia nut oil instead? You can get it from <a href="http://www.amazon.com">.'
   ]
}

Using jQuery templates:

var dom = $('#stream-comment-tmpl').tmpl(templateData);
<script id="stream-comment-tmpl" type="text/x-jquery-tmpl">
  <span>${label}</span>
  {{each comments}}
  <div class="stream-comment">
    {{if type == 'highfive'}}
     <div>
      <span class="icon-highfive"></span>
      High five from <a href="${creator.profileUrl}">${creator.fullName}</a>!
     </div>
    {{else}}
     <a href="${creator.profileUrl}">${creator.fullName}</a>:
     {{html textHtml}}
    </div>
    {{/if}}
  </div>
  {{/each}}
</script>

Using jsRender templates:

var template = $.template('stream-comment-tmpl', document.getElementById('stream-comment-tmpl').innerHTML);
var html = $.render(templateData, template);
<script id="stream-comment-tmpl" type="text/jsrender-tmpl">
  <span>{{=label}}</span>
  {{#each comments}}
  <div class="stream-comment">
    {{#if type == 'highfive'}}
     <div>
      <span class="icon-highfive"></span>
      High five from <a href="{{=creator.profileUrl}}">{{=creator.fullName}}</a>!
     </div>
    {{else}}
     <a href="{{=creator.profileUrl}}">{{=creator.fullName}}"</a>:
     <div>{{=textHtml!}}</div>
    {{/if}}
  </div>
  {{/each}}
</script>

In jQuery templates, you could pass null as a value in an array and use the if operator to test whether it was defined. In jsRender, the template will not be called at all for a null value. Instead, you need to define objects in the array, set an object property to null, and check to see if that object property is defined. The reasoning behind the change is discussed more in this issue.

To give you an idea of the change, here's a before and after - notice I had to change the data format itself.

Using jQuery templates:

var templateData = ['Walked to work', null, null, 'Biked to work'];
<script id="notes-mini-tmpl" type="text/x-jquery-tmpl">
<div>
  {{each dates}}
    {{if $value}}
     ${value}
    {{else}}
     No notes for this date.
    {{/if}}
  {{/each}}
</div>
</script>

Using jsRender templates:

var templateData = [{notes: 'Walked to work.', {notes: null}, {notes: null}, {notes: 'Biked to work'}];
<script id="notes-mini-tmpl" type="text/jsrender-tmpl">
<div>
  {{#each dates}}
    {{#if notes}}
     {{=notes}}  
    {{else}}
     No notes for this date.
    {{/if}}
  {{/each}}
</div>
</script>

No comments: