Handlebars + JSLink

First, I want to apologize for such a long silence. Project work and life in general has kept me very busy over the last year, so it has been difficult to find a time to write new content.

This article describes a specific scenario I experienced as part of my standard project work. It hasn’t been extended or tested for other scenarios (yet!). Handle with care.

Since the release of SharePoint 2013, I have been doing a lot of customizations utilizing JSLink on lists and list views. One of the things that I really do not like about JSLink is all of the string concatenation that tends to happen when building the output for your JSLink customization. It is not a huge challenge when you are working with a small template or limited output, but when you start working with complex HTML strings, it can be a bit unwieldy.

In recent years, there have been a lot of JavaScript frameworks that solve this specific problem by providing a way to create a template, or representation of your UI, that is later dynamically populated and put into place. One of these tools is Handlebars.js. The challenge with using this framework is that it expects to have a script tag somewhere in the page that has an ID. One way to solve this challenge is to insert a script editor web part into your page with the content of the template. However, I would prefer to have my template isolated as a file that I can modify apart from the script running on the file. The challenge is how to get the template loaded dynamically so that Handlebars.js can make use of it.

After performing a search, I found a great article on how to dynamically load templates for Handlebars.js using JQuery by Gabor Szabo. The key is using the function with a callback outlined in the improved JavaScript code section of the article.

Using this article as my starting point, I began by building out my handlebars template and placing it in a document library named Site Assets on the site where I needed to use this script. I saved the template with a .handlebars extension (I could have easily just left it as HTML).

<div id="Vendor_Record">
	<h1>{{CompanyName}}</h1><h2>FEIN: {{FEIN}}</h2>
		<hr style="color:red;"/>
	<div class="address">
		{{WorkAddress}}<br/>
		{{WorkCity}}, {{State}} {{ZipCode}}
	</div>
	<div class="contact">
		{{ContactName}} {{{ContactEmailAddress}}}<br/>
		{{PhoneNumber}}
	</div>
	<div class="status">
		<table>
			<tr>
				<th>Category</th>
				<td>{{VendorCategory}}</td>
			</tr>
			<tr>
				<th>Status</th>
				<td>{{VendorStatus}} - {{ApprovalDeniedReason}}</td>
			</tr>
		</table>
	</div>
</div>

Now the hard part, getting JSLink to work with my template. In my final solution, I need to only show a single record and I am overriding the entire view. I’ll first include the code and then describe what each part of the code is doing.

var SomeCompany = SomeCompany || {};

SomeCompany.Product = SomeCompany.Product || {};
SomeCompany.Product.ViewVendorRecord = SomeCompany.Product.ViewVendorRecord || (function() {
	var itemID = null,
		divID = null,
		render = false,
		template;
		
	function getTemplateAjax(path, callback){
		if(!template){
			var source;
			$.ajax({
				url: path,
				cache: true,
				success: function(data) {
					//console.log(data);
					source = data;
					template = Handlebars.compile(source);
					if(callback) callback(template);
				}
			});
		} else
		if(template) {
			if (callback) callback(template);
		}
	};
	
	return {
		renderHeader: function (ctx) {
			return "<input type=\"hidden\" id=\"" + itemID + "\" value=\"" + encodeURI(JSON.stringify(ctx.ListData.Row[0])) + "\"/><hr/>";
		},
		renderFooter: function (ctx) {
			return "<hr/>";
		},
		renderItem: function (ctx) {
			return "<div id=\"" + divID + "\"></div>";
		},
		onPostRender: function (ctx) {
			var data = JSON.parse(decodeURI($("#" + itemID).val()));
			getTemplateAjax("/sites/Product/siteassets/handlebartemplates/vendor.handlebar", function(template) {
				$("#" + divID).html(template(data));
			});
		},
		onPreRender: function (ctx) {
			render = ctx.ListData.Row.length > 0;
			if(render){
				itemID = "item_" + ctx.ListData.Row[0].ID;
				divID = "div_" + ctx.ListData.Row[0].ID;
			}
		}
	};
})();

(function() {
	var overrideCtx = {};
	
	overrideCtx.Templates = {};

	overrideCtx.Templates.Header = SomeCompany.Product.ViewVendorRecord.renderHeader;
	overrideCtx.Templates.Item = SomeCompany.Product.ViewVendorRecord.renderItem;
	overrideCtx.Templates.Footer = SomeCompany.Product.ViewVendorRecord.renderFooter;
	overrideCtx.Templates.OnPostRender = SomeCompany.Product.ViewVendorRecord.onPostRender;
	overrideCtx.Templates.OnPreRender = SomeCompany.Product.ViewVendorRecord.onPreRender;
	overrideCtx.BaseViewID = 1;
	overrideCtx.ListTemplateType = 100;
	
	SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
})();

This code is setup so that it shouldn’t pollute the global namespace, something I advocated for in a previous post.

Lines 10-27: This is the function used to dynamically load the handlebars template. This is pretty much the same code presented in the article by Gabor Szabo I referenced earlier. The only change is that I store the template in a variable for the module.

Lines 30-32: The renderHeader outputs a single hidden input and stuffs the contents of the first item in the list into it. This could be omitted as I could perform the same action in the renderItem function (untested at the moment). The change would be to use ctx.CurrentItem instead of ctx.ListData.Rows[0] to get the data.

Lines 36-38: My only output in the renderItem is a div with an ID to be the target for Handlebars.js.

Lines 45-51: JSLink give you both an OnPreRender and OnPostRender function that fires for every item rendered in the list for the current view. The onPreRender function simply calculates a unique ID for each item and stores it for later use by the JSLink file. This is another area that would need changes in order to generate a unique ID for Handlebars.js to utilize as a target.

Lines 39-44: OnPostRender is responsible for making sure the template is loaded (line 41) and provides the callback function with will actually place the rendered content into the target DIV.

I want to stress that this code has only been tested rendering a single item in a list view (as this is the requirement for my project). I will continue to experiment to see if I can reuse this idea for rendering multiple items (or even individual fields) and post any findings. Please feel free to share any of your experiences as well.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s