Today, I was working on a code base for a time sheeting system. It does a lot of rendering on client side like retrieving appointments and source code comments from third party system and then renders it onto the page.
Unfortunately, the javascript that did the rendering post ajax call was something like this:
for (var i = 0; i < wi.CheckinHistory.length; i++) {
history += "<li><i class=\"add-note\" title=\"Add this checkin comment to the Notes field\">" + wi.CheckinHistory[i] + "</i></li>";
}
var item = "<span class=\"wi-link\"><a href=\"" + wi.Url + "\" target=\"_blank\">[" + wi.Id + "]</a></span>" +
"<span class=\"wi-title\">" + wi.Title + "</span>" +
"<span class=\"wi-state\">" + wi.State + "</span>" +
"<span class=\"wi-project\">" + wi.ProjectName + "</span>" +
"<span class=\"wi-checkins\"><ul>" + history + "</ul></span>" +
"<span class=\"wi-modified\">" + "(Last Modified " + modDate + " by " + wi.ChangedBy + ")</span>";
return item;
This is obviously not maintainable as it's not really readable and prone to error.
I refactored this code and re-implemented it using a javascript templating engine. There are several around and LinkedIn has a good smackdown of several options.
I chose to use Handlebars because of the easy to read syntax and extensible (but mainly because it has an awesome logo).
Installing and using Handlebars
Grab it from NuGet using :
Install-Package Handlebars.js
Reference the handlebars.min.js in your layout/master page.
Create your template, the javascript that builds html above will become nice and readable:
<script id="tfsChangeSetTemplate" type="text/x-handlebars-template">
<h3>Changesets ({{count result}})</h3>
{{#each result}}
<div class="wi-item">
<span class="wi-link"><a href="{{Url}}" target="_blank">[Changeset {{Id}}]</a></span>
<span class="wi-title">Checked In {{CommitDateFormatted}} by {{CommittedBy}} </span>
<span class="wi-state">{{Changes}} file(s) changed</span>
<span class="wi-checkins">
<ul>
<li>{{#if Comment}}
<i class="add-note" title="Add this checkin comment to the Notes field">{{html Comment}}
</i>
{{else}}
<i>No comment was entered
</i>
{{/if}}
</li>
</ul>
</span>
{{#if WorkItems}}
<span class="wi-workitems">
<ul style="list-style: none; margin-left: 20px;">
{{#each WorkItems}}
<li>
<span class="wi-link"><a href="{{Url}}" target="_blank">[WI {{this.Id}}]</a></span>
<span class="wi-title">{{Title}}</span>
<span class="wi-state">{{State}}</span>
<span class="wi-project">{{ProjectName}}</span>
</li>
{{/each}}
</ul>
</span>
{{/if}}
</div>
{{/each}}
</script>
You'll notice I put in some conditional logic using the IF block and made extensive use of the EACH block to loop through the JSON data.
The javascript to get and render the template is:
if (data.length > 0) {
var tfsWorkItemTemplate = Handlebars.compile($("#tfsChangeSetTemplate").html());
var html = tfsWorkItemTemplate({ result: data });
$tasks.append(html);
}
Nice and simple!