Introduction
If you have struggled to find a javascript templating engine that is fast, familiar and extensible, then perhaps JSRazor is for you.
JSRazor converts an HTML template into a Javascript object that you can use to generate HTML on the client/browser, based on a model object which is usually passed back from your server as JSON.
As we grow JSRazor, we are finding it is a great way to define and package client side javascript controls, we can package all the templates and support code into a single minimized file, easily.
Updates
- Added easy template binding capabilities using a provided JQuery plugin
Availability
CubeBuild.JSRazor is available at http://www.bitbucket.org/cubebuild/jsrazor. Issues can also be submitted there.
The repository contains 60 and growing unit tests.
CubeBuild.JSRazor is also available for .NET as a NuGET package CubeBuild.JSRazor
Documentation can be found on our wiki at http://www.cubebuild.com/jsrazor
Background
When you move to a rich web application, using AJAX, from pages generated on the server, you'll miss the simplicity of a strong and easy templating language You can generate the HTML from the server for a fragment of the page, but that tends to bloat requests to a size that is much larger than you need.
You may also have tried a number of javascript templating engines. We have, and we found several problems common to "most" of them:
- The templates themselves are often mixed in with your HTML, making their management difficult.
- The syntax of most are not like Razor, do you really need to use two vastly different templating languages.
- Most are interpreted, leading to difficulty in extending them using javascript.
- Most don't allow you to cleanly mix behaviour with the template.
JSRazor is a custom syntax parser that understands HTML with Razor-like markup and generates javascript objects. The resulting javascript object contains a render method that takes a "Model" object and produces HTML output.
It is important to us that JSRazor also support the following:
- Command line generation of javascript for non .NET platforms.
- Cross-Platform support for Mono
- Convention-based template locations for ASP.NET MVC
- Aggregation of a number of templates and javascript files into a single .js download.
- Any App that uses HTML(5)/Javascript on any platform should be able to benefit from JSRazor.
An Example, a 60 source-line Javascript Calendar Control
In order to learn and use JSRazor, I have created a simple example, of around 60 lines of JSRazor template and Javascript that is able to render a calendar control to the browser, to show events for a month, it looks like this:
The use case for this calendar is for a client-side calendar that is updated based on an object passed back from a JSON call to any server, from the browser client, so you can skip through months quickly.
The JSON returned from the server will include the month and year to display, then a list of events with dates:
var Model = {
Month: 0,
Year: 2013,
Events: [
{ Date: "1/1/2013", Event: "School Starts" },
{ Date: "1/3/2013", Event: "Free Day" },
{ Date: "1/7/2013", Event: "Car Serviced" },
{ Date: "1/7/2013", Event: "Dinner with Friends" },
{ Date: "1/12/2013", Event: "Rubbish Collected" },
{ Date: "1/18/2013", Event: "Town Planning Meeting" },
{ Date: "1/23/2013", Event: "School Curriculum Day" }
]
}
While you could build HTML on the fly in the browser, or build up objects using a javascript library like jQuery, both become difficult to maintain very quickly.
As an alternative we can do it with a JSRazor template in just 36 lines of Razor-like syntax and a couple of support functions that work out the weekdays:
@* Calendar.jshtml *@
<h1>@(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][Model.Month]), @Model.Year</h1>
<table>
<tr>
<th>Sunday</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
</tr>
@for (var week = 0; week <= 4; week++)
{
<tr>
@for (var day = 0; day <= 6; day++)
{
<td>
@this.DayNumber(Model.Month, Model.Year, week, day)
@this.ShowEvents(Model.Month, Model.Year, week, day, Model.Events)
</td>
}
</tr>
}
</table>
@helper ShowEvents(month, year, weekNumber, dayNumber, events)
{
/* Show events for the specific day, for events valid on that day */
var day = this.Today(month, year, weekNumber, dayNumber);
for (var i = 0; i < events.length; i++)
{
var event = events[i];
var thisDate = new Date(event.Date);
if (thisDate.getDate() == day && thisDate.getMonth() == month && thisDate.getFullYear() == year) {
<div>@event.Event</div>
}
}
}
JSRazor generates a single javascript object that contains two methods:
- ShowEvents - helper that renders events for a specific day in a list of events.
- render(Model) - based on the passed model, render the model according to the rules of the template.
You may notice calls to this.Today
, and this.DayNumber
, two functions that are defined in a separate file and incorporated into the template as:
Calendar.prototype.DayNumber = function (month, year, weekNumber, dayNumber) {
var actualDayNumber = this.Today(month, year, weekNumber, dayNumber);
if (actualDayNumber <= 0) { return ""; }
var availableDays = new Date(year, month + 1, 0).getDate();
if (actualDayNumber > availableDays) { return ""; }
return actualDayNumber;
}
Calendar.prototype.Today = function (month, year, weekNumber, dayNumber) {
var firstDate = new Date(year, month, 1);
var dateOffset = firstDate.getDay();
return (weekNumber * 7) + dayNumber - dateOffset + 1;
}
As you would expect, you can use the JSRazor syntax to call:
- Any function or helper defined in the template
- Any base javascript function
- Any library separately included in the HTML
- Helpers from this template, or from other shared templates
Build-time Generation
For generation of the final javascript that is downloaded to the browser, you use CubeBuild.JSRazor.Command.
CubeBuild.JSRazor.Command Calendar.jshtml Calendar.js > Calendar_template.js
takes a list of files or directories (which it probes for all .jshtml and .js files) and it writes transformed templates and all javascript to standard output, so Calendar_template.js contains all the found source files.
Runtime Generation - ASP.NET MVC
CubeBuild.JSRazor.Web.MVC contains helpers for ASP.NET MVC that allow you to, by convention, locate the jshtml and javascript, then cache the generated template at runtime.
Create an action capable of returning the consolidated javascript similar to:
public ActionResult JSTmpl(string viewController, string viewAction)
{
return this.JSTemplate(viewController, viewAction);
}
By convention, this will look for javascript templates in a folder with the same name as the view, or in the shared folder, and stream them back as javascript, which is minimized using WebGrease for Release builds. The general structure for the calendar example is:
Views
Calendar - action view folder
Index.cshtml - razor server side template
Index - convention folder for JSRazor content
Calendar.js - support code for jsrazor template
Calendar.jshtml - jsrazor template
You can then reference the javascript via your special JSTmpl action using a <script/> tag, and you will get back the generated template and the javascript support code.
Any number of templates and support javascript files can be included in the folder, allowing you to separately define all the templates and code you need for the page in miltiple design time files.
Using the Template
The generated template code is used by:
- Creating an instance of the template class
- Calling template.render, passing it a model object
- Doing something with the generated HTML
Using JQuery, you might do the following:
$(function () {
var t = new Calendar();
$("#calendar").html(t.render({
Month: 0,
Year: 2013,
Events: [
{ Date: "1/1/2013", Event: "School Starts" },
{ Date: "1/3/2013", Event: "Free Day" },
{ Date: "1/7/2013", Event: "Car Serviced" },
{ Date: "1/7/2013", Event: "Dinner with Friends" },
{ Date: "1/12/2013", Event: "Rubbish Collected" },
{ Date: "1/18/2013", Event: "Town Planning Meeting" },
{ Date: "1/23/2013", Event: "School Curriculum Day" }
]
}));
});
In this example the model object is defined in code, it could just as easily be the result of an AJAX call to a server, which returns a JSON object.
AJAX
If you are using JQuery for AJAX calls, the following jquery template plugin might help:
$.fn.postTemplate = function (url, data, template) {
$.each(this, function (nodeix, node) {
var target = node;
$.ajax({
url: url,
data: data,
type: 'POST',
cache: false,
dataType: 'json',
success: function (data) {
if (data.Success) {
var t = new template();
$(target).html(t.render(data));
if (t.OnRender) {
t.OnRender($(target));
}
}
else {
$(target).html(data.Message);
}
}
});
});
};
With this helper, you can post a call to the server, and place the returned model into the page using the template in one call:
$("#calendar").postTemplate("/calendar/get", { month : 0, year: 2013 }, Calendar);
This will get the JSON from the server, render it into the template, then call an OnRender function on the template to do any JQuery setup, passing in the JQuery container object. An example of an OnRender definition in a Javascript file separate to the template is:
Calendar.prototype.OnRender = function(elem) { $(elem).addClass("calendar"); });
Data Binding
Given a template is an object, we can refresh the content any time based on a new dataset, and with a simple JQuery add-in called
bindTemplate you may bind changes on fields, or at the click of something, to a template.
As an example (found in the example project) consider a page that displays a dropdown of people, and allows you to select a person and view their details. The back end might look like this:
Person[] PersonList = {
new Person() {
ID = 1,
Name = new Name() { First = "Adrian", Last = "Holland"},
Address = new Address() {
Street = "Jackson Crt",
City = "Strathfieldsaye",
State = "Victoria",
Country = "Australia",
Postcode = "3553"
}
},
new Person(){
ID = 2,
Name = new Name() { First = "Sam", Last = "Taylor"},
Address = new Address() {
Street = "Pines Rd",
City = "Robe",
State = "South Australia",
Country = "Australia",
Postcode = "8343"
}
},
new Person() {
ID = 3,
Name = new Name() { First = "Greg", Last = "Jones"},
Address = new Address() {
Street = "Yates Blvd",
City = "Caroline Springs",
State = "Victoria",
Country = "Australia",
Postcode = "3345"
}
}
};
public ActionResult List()
{
return Json(new { Success = true, Items = PersonList.Select(p => new { p.ID, Name = p.Name }) });
}
public ActionResult Object(int id)
{
return Json(new { Success = true, Person = PersonList.Where(p => p.ID == id).FirstOrDefault() });
}
public class Person
{
public int ID { get; set; }
public Name Name { get; set; }
public Address Address { get; set; }
}
public class Name
{
public string First { get; set; }
public string Last { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public string Country { get; set; }
}
That gives us some data to work with. Firstly we need a page to display the selector and the Person details, so here is a view Index.cshtml:
@{
ViewBag.Title = "Index";
}
@section head {
<script src="http://www.codeproject.com/ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
@Html.IncludeJSTemplates()
}
<h2>Binding Example</h2>
<div id="personSelector"></div>
<fieldset>
<legend>Person</legend>
<div id="person"></div>
</fieldset>
<button id="adrian">Adrian</button>
My intent for these placeholders is:
- #personSelector - a dropdown that shows the names of people
- #person - shows the detail of the selected person, and changes when a new person is selected
The first point of interest is the dropdown selector, we could render that from the Razor page, but as we are using JSRazor, ill create a template that shows the selector on the client side:
@* Selector.jshtml *@
<select name="personID">
@foreach (var person in Model.Items) {
<option value="@person.ID">@person.Name.Last, @person.Name.First</option>
}
</select>
Once a person is selected, and indeed when the page is first displayed, we need to show the person as well, so here is a person template to display the person:
@* Person.jshtml *@
<div class="person">
<div class="field">
<span class="label">ID</span>
<span class="value">@Model.Person.ID</span>
</div>
<div class="field">
<span class="label">Name</span>
<span class="value">@Model.Person.Name.Last, @Model.Person.Name.First</span>
</div>
<div class="field">
<span class="label">Address</span>
<span class="value">@Model.Person.Address.Street</span>
<span class="value">@Model.Person.Address.City</span>
<span class="value">@Model.Person.Address.State, @Model.Person.Address.Postcode</span>
<span class="value">@Model.Person.Address.Country</span>
</div>
</div>
The only thing remaining is the javascript to wire them together, basically two lines (and one more I will describe later):
$(function () {
$("#personSelector").postTemplate("/binding/list", {}, Selector);
$("#adrian").bindTemplate("/binding/object", { id: 1 }, Person, "#person", "click");
});
Selector.prototype.OnRender = function (obj) {
obj.bindTemplate("/binding/object", { id: new Binding.Value("[name=personID]") }, Person, "#person");
}
The initial call to postTemplate will, when the page is loaded, post back to the server and get a list of people, and render the list using the Selector template.
OnRender for the Selector template needs then to wire up the binding, we call bindTemplate to do that, following is a description of each parameter:
- "/binding/object" - url - The URL to call to receive the JSON of the person object
- { id: ... } - Bindings - Links the {id} field (passed to the server) to the JQuery .val() of the field with the name personID, in this case that is the single select that is rendered out of the selector template
- Person - Template - The name of the template to use to render the data received
- "#person" - The JQuery selector target location for the rendered template
So, to summarize, the actions of the binding will be:
- Bind a change event to the field selected by [name=personID]
- Query the field selected to get its default (or later changed) .val()
- Pull the object with that {id} from the back end and display it
If the field defined by the binding is changed, step 2 and 3 are called again, pulling the new Person down and displaying them.
bindTemplate
bindTemplate supports two use cases:
- Bind to one or more changeable fields like input, select, textarea
- Bind to the click (or other event) of a specific object
The second call to bindTemplate is an example of binding to a click event rather than change, It says, when "#adrian" is clicked, post to "/binding/object" and get person with {id = 1} then display it using the Person template in the location "#person".
The following binding types are defined (with the JQuery equivalent code that derives the bound value):
- Binding.Value(selector) - find the selector within the targeted JQuery node, and use the value = $(selector, this).val()
- Binding.Attribute(selector, attr) - find the selector within the targeted JQuery node, and use the value of the attribute = $(selector, this).attr(attr);
- Binding.Self() - pull the value off the node itself = $(this).val()
- Binding.ExplicitAttribute(selector, attr) - query the entire DOM and get an attribute = $(selector).attr(attr)
- Binding.ExplicitValue(selector) - query the entire DOM and get a value = $(selector).val()
- Binding.SelfAtribute(attr) - pull the value of an attribute off the node itself = $(this).attr(attr)
- Any other type is passed explicitly, so {id:1} will bind to the constant value 1.
The bindings field can accept any number of arguments, as an example you might use town and country in a service that returns postcodes, in this case your bindings might be:
{town: new Binding.Value("[name=town]"), country: new Binding.Value("[name=country"])}
This would result in the values of both fields being passed to the server, and a change in either field causing the update.
The full source for bindTemplate can be found in the example project attached, and in our repository.
Points of Interest
As we have been using JSRazor, we are finding we can more easily push capability to the browser, so we are slowly moving from having single templates, to multiple templates for pages, which allows us to create a richer client experience.
We also find that using JSRazor to include the javascript on the page is more reliable than managing a number of links in the page, we just place all the javascript for the action into the JSRazor content folder, and it all gets to the page automatically, and is minimized automatically for us.
Tricks and Traps
- The JSRazor generated helpers and functions are members of the template, not part of the DOM, you can not call then from html events as below. Even if there is a ShowEvents defined in the template, the method will not be found the context of "this" will no longer be the template when the link is clicked:
<a onclick="this.ShowEvents()">Events</a>
- Defining support functions in separate JS files is easier when using prototype, especially if your IDE supports javascript, as you get proper syntax highlighting and intellisense.
- Visual Studio 2012 interprets .jshtml as Razor, so the Razor like syntax is presented quite well, but the template language is javascript, not C#, so it does get confused by the embedded code.
Version History
Version 1.0.0.11
Version 1.0.0.7
- Added WebGrease minimization for release builds
- Consolidate all templates and associated javascript into one file
- Added @function helper to render a javascript function