JavaScript Templating with SharePoint 2013 (Part 2)

Welcome to Part 2 of JavaScript Templating with SharePoint 2013. In part 1 I tried to lay a foundation for the next posts. If you haven’t read it, I’d suggest you at least breeze over it so this post makes a little more sense.

As I mentioned in the first post, generally speaking when it comes to JavaScript templating there are 3 players involved: a data source, a data retrieval mechanism, and a template. I also mentioned that we SharePoint developers should also keep in mind that our code should be able to exist on any page the user chooses.

As with the remaining posts in this series, jQuery ajax will be my data retrieval mechanism and I’ll use Handlebars as my templating engine. In this post I’m going to leverage a SharePoint list named Events as my data source.

The Finished Product

Below are some screenshots of the finished product. The template lists upcoming events. Clicking the event title reveals more details.

Collapsed

Events

Expanded

events_expanded

Data Source: Events list

The Events list purpose and contents are pretty self explanatory. The schema is what you would expect as well: Title, Start Date, End Date, Content. One thing I want to note is that the Content is a Rich Text field. That’ll be important later.

events list

Data Retrieval: Events.js

We have a few options for getting the data from the Events list. SharePoint has a JavaScript Object Model available that can be used to do a multitude of things client side including pulling data. It has it’s own syntax, structure, and order of operations so it definitely requires knowledge of the object model. It has some pros but I won’t use it here because, to be frank, it’s annoying, complicated, and useless outside of SharePoint development. No hard feelings SharePoint Dev team. I’m just keeping it real :) . And I’m not the only person: http://www.andrewconnell.com/blog/sharepoint-2013-csom-vs.-rest-…-my-preference-and-why

SharePoint 2013 also has a pretty robust REST api layer that they’ve made available to us to retrieve list data. You can also perform other CRUD operations but I won’t get into that here. The service conforms to OData standards so we can construct OData queries to filter, sort, and join list data BEFORE it gets to the client! That’s good stuff. Finally, since this is a web service, I can use any JavaScript library to make calls to it. The favorite being jQuery.

So, I hate the SharePoint JavaScript object model. But I love me some jQuery. With that in mind, I’m going with option #2. I created Events.js file to handle the retrieving of data and binding to my template. I also have a little utility file that handles a couple things for me. I won’t go into every function in detail but just know that it does the following:

  1. When init() is called it shows the user a loading message. It then makes an ajax call to retrieve the template file.
  2. When the template is returned, it appends the contents on the template file to the current page’s body element.
  3. Next it makes an ajax call to SharePoint’s api to get list data as JSON.
  4. When list data is returned, get the template html that was added to the body in step 2. Then leverage Handlebars to bind the list data to the template html.
  5. Finally, take what Handlebars generates and push it into a dom element container.
EventsControl = function (options) {
this.settings = options;

this.init = function () {
jsUtil.showLoading(this.settings.containerId);

this.getTemplate();
};

this.getTemplate = function () {
var url = this.settings.siteUrl + '/' + this.settings.templateUrl;

//get the template.aspx file and add to the body
$.get(url, null, $.proxy(this.getTemplateSuccess, this));
};

this.getTemplateSuccess = function (template) {
if (template) {
//ad the template to the body so it's available after we get data
$('body').append(template);
//now call function to get data
this.getData();
}
};

this.getData = function () {
var url = this.settings.siteUrl + "/_api/web/lists/getbytitle('Events')/items?$orderby=StartDate asc&$filter=EndDate gt '" + moment().format() + "'";
//go get my data
$.ajax({
url: url,
type: 'GET',
headers: { "Accept": "application/json;odata=verbose" }, //tell sharepoint I want json data
dataType: 'json',
success: $.proxy(this.callSuccess, this),
error: $.proxy(this.callFailed, this)
});
};

this.callSuccess = function (data) {
//get the html contents of the template we appended to the body
var source = $('#' + this.settings.templateId).html();
//thank God for Handlebars
var template = Handlebars.compile(source);
//let Handlebars do all the hard work. bind my data to the template.
var html = template(data);
//take the html handlebars generates and add it to the container
$('#' + this.settings.containerId).html(html);

//set click event on title
$('.event-title').click(function () {
$(this).siblings('.event-content').slideToggle();
return false;
});
};

this.callFailed = function (xhr, status, error) {
jsUtil.showError(this.settings.containerId, "My bad. Something didn't go right.");
};

};

Let’s take a closer look at the getData function. The url consists of a site url (which will be passed in later) and a REST api call with an OData query. I’m getting items from the Events list ordered by StartDate ascending. I’m even setting a filter where EndDate is greater than today. If you noticed the list has 3 items but only 2 were returned because ‘Old Event’ is as it suggests: old. All of this in a single call? Sweet! This is very promising and exciting to me. Here’s a great post if you want to learn more about SharePoint’s OData calls: http://platinumdogs.me/2013/03/14/sharepoint-adventures-with-the-rest-api-part-1/.

A couple things to note: Dates have to be formatted in a particular fashion within the query. Instead of fumbling around with the Date() object, I’m using the Moment.js plugin to format my dates. Do yourself a favor and visit that site. This plugin is a true productivity booster. Once you use it, you’ll never fool with Date() again.

As I mentioned, I use jQuery to make an ajax call to get list data. Notice that if I want JSON data, I have to explicitly add an Accept header to my call for application/json;odata=verbose. In my experience, this is a must have.

The Template: Events.aspx

I placed my template HTML in an aspx page. This is just my preference because I like separating concerns. However, I can see that this probably makes things more complicated than they have to be. I could have just placed my template html in the web part and avoided the additional call to get the template html (getTemplate()) and add append it to the body (getTemplateSuccess()). Granted. But, that’s just my style. Please don’t kill my vibe. :)


<script id="events-template" type="text/x-handlebars-template">
{{#each d.results}}
<div class="event">
<h2 class="event-title">{{Title}}</h2>

<div class="event-date">
<b>Date:</b> {{formatDate StartDate format="dddd, MMMM DD YYYY"}}
</div>

<div class="event-content">
{{{Content}}}
</div>
</div>
{{/each}}
</script>

The template is that it’s simply a script element with html inside. However, the script type is ignored by browsers so nothing attempts to execute. Take note of the ID of the script tag. If you go back to the callSuccess function, I use jQuery to select this script tag by that ID, which will be passed into the function by the web part (more on that later).

Check out that sweet Handlebar syntax. It looks very simple because it is very simple. In English the template says “for each item in d.results, place the Title here, the StartDate there, and the Content over there.” But what the heck is d.results? To understand let’s inspect the JSON data that is returned from the ajax call. For that, we turn to Chrome developer tools.

events_json

SharePoint api calls return a JSON object that wraps the results collection in d.results. Doesn’t that seem eerily similar to the JSON construct WCF endpoints normally return? That’s because the SharePiont 2013 REST api is actually a wrapper for calls to a WCF service! But who cares, as long as it’s JSON right?

So we have to tell Handlebars to loop through the d.results collection. Thus {{#each d.results}}. Once we’re working with a single item in the collection, we use the Handlebar syntax to place item property values (Title, StartDate, Content) within elements. These properties will match the InternalFieldName of your item fields. So as a general rule of thumb, I create list columns with no spaces. Here are the properties of the first item.

event_json

The Title property is a simple string that can be placed directly into an element. Content and StartDate however are a bit more of a challenge.

The Content field returns raw html. As a precaution, Handlebars will encode that html when binding to the template which doesn’t help us. Fortunately we can tell Handlebars that we trust the source property so please render the raw value as is. We accomplish this by using 3 curly braces around the property instead of the normal 2. Easy.

The StartDate field returns an ugly date string. It’s definitely not something we want to display to our users. Again, thank goodness for Handlebars. Notice that instead of just having the StartDate property within the braces, I also have formatDate and a format string. This is a custom Handlebar helper that I wrote.

HandlebarHelpers.js

Handlebars.registerHelper('formatDate', function (value, options) {
if (value) {
var format = 'M/DD/YYYY';
if (options.hash['format'])
format = options.hash['format'];

var html = moment(value.toString()).format(format);

return new Handlebars.SafeString(html);
}
});

Handlebars passes the property value and the format string to my helper. In the helper I simply use Moment.js to build a date object and format into a string that makes sense for humans. You gotta love Handlebars and Moment.js.

Componentization: A Web Part

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Events.ascx.cs" Inherits="LS.JavaScriptTemplates.WebParts.Events.Events" %>

<div id="events-container">
<!--my template will be added here-->
</div>

<!--get the plugins I need -->
<script src="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/scripts/plugins/jquery.1.10.2.js"></script>
<script src="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/scripts/plugins/moment.2.1.0.min.js"></script>
<script src="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/scripts/plugins/handlebars.1.0.0.js"></script>

<!--get the custom scripts I need -->
<script src="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/scripts/handlebarhelpers.js"></script>
<script src="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/scripts/utility.js"></script>
<script src="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/scripts/events.js"></script>
<!--css-->
<link href="<%= SPContext.Current.Site.Url.TrimEnd('/')  %>/content/events.css" rel="stylesheet">

<!--Initialize my js object here to get data and bind template-->
<script>

var eventsControl = new EventsControl({
siteUrl: '<%= SPContext.Current.Site.Url.TrimEnd('/')  %>',
containerId: 'events-container', //the div I want my template rendered in
templateUrl: 'templates/events.aspx', //site relative url of events.aspx
templateId: 'events-template' //matches the ID of the script tag in events.aspx
});

eventsControl.init();

</script>

This is a visual web part that ties it all together. In it I have the container div that I want to hold my list of events. The callSuccess function of Event.js get the html that Handlebars generates and pushes the html inside this DIV. The next several lines are JavaScript and CSS references.

Near the end I call the EventControl function with several options. These options are transferred to this.settings in Events.js. So in Events.js, this.settings.containerId would return ‘events-container’.

Finally I call the init() function. This is where the magic happens!

Wrapping Up

I hope this post triggered ideas of how we can leverage JavaScript templating in SharePoint. I truly believe this is the way to go. I’m drinking the Kool Aid! Of course there are exceptions, but in my opinion client side data retrieval and rendering makes for a much better user experience.

Stay tuned for part 3 where I’ll be using the search api endpoints as the data source. Get the full code below.

The Code

LS.JavaScriptTemplates.zip

13 thoughts on “JavaScript Templating with SharePoint 2013 (Part 2)

  1. Pingback: SharePoint 2013 Good Links | Share your knowledge

  2. Jonathan Gravois

    This is exactly what I need but I am so new to SharePoint that I don’t know where each file goes. Are you creating an app or just a WebPart Page? Where do the ancillary files reside? Is there a repo or zip of the source so I can see the headers and other stuff on the pages that you omitted because I follow exactly what you are doing and I get nothing but errors and no content. I think that means there are other parts of the page that you are taking for granted that I would know about but I am a PHP web developer who has been tasked with adding some features to our SharePoint Intranet and I don’t really know the hidden stuff.

    I am absolutely interested in being able to follow you because this is exactly one of the features I need to implement. Please help.

    Reply
  3. Kipland Iles

    Hey Lester,
    I just realized that after I installed your solution from part 3 that I also had this feature, too. Great Stuff! I just went in and created the Events List just like you described and added a new event and BAM! Works as designed. Very cool! No Managed Properties or Crawl Properties and no ugly CQWP. I also like that I can put this on my Home Page and the Events just open up like an accordian. No leaving the page and having to navigate back. Modifying the CSS and Template with SharePoint Designer is a breeze once I figured out where you were putting everything. I’m using this on a Publishing site with SP2013 SP1 installed and it works fine (I occasionally get an error relating to Trusted Code in the Sandbox when editing other webparts but it always snaps back once I publish the page).

    I can really see the possibilities with this. I have been fighting the CSWP and Display Templates for awhile but your method is sweet. I just need to get my versions of jquery and all the supporting libraries for my other things like stock quotes, weather, sliders, etc. centrally located so I don’t keep running into issues with jQuery versions (thank goodness for noConflict). I’m on a new test farm so It’s a bit disorganized right now.

    Thanks for taking the time to write about this. Much appreciated.

    Reply
  4. Tomas

    Hi,
    Great article, this will be very useful!

    On question though, everything is working great but when I add the visual web part to my page the page load time is about 2 seconds. The rest call itself is very fast.

    Can you think of something that can cause this? I have tried to download moment, handlebars and jquery from CDN instead but problem still exists.

    Reply
    1. LesterLester Post author

      Sounds like the browser processing is slow. The only other bottleneck I can think of is handlebars compiling the html but that’s normally almost instant.

      Reply
  5. ahmed saber

    hello sir,
    am getting this error My bad. Something didn’t go right,
    could you please help me

    Reply
  6. zizo

    good day Lester,
    I have installed the .wsp file and activated the solution successfully, when insert web part in page, I get
    ” My bad. Something didn’t go right”,
    any ideas!, sharepoint online

    Reply
  7. zizo

    {“error”:{“code”:”-1, System.FormatException”,”message”:{“lang”:”en-US”,”value”:”String was not recognized as a valid DateTime.”}}}

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>