Click here to Skip to main content
Click here to Skip to main content

Template Generator for JavaScript - JSRazor

, 9 Jan 2013
Rate this:
Please Sign up or sign in to vote.
JSRazor brings the power of razor-like templates to client-side javascript.

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.js */ 
Calendar.prototype.DayNumber = function (month, year, weekNumber, dayNumber) {
    /* Return appropriate day number, or nothing for a day that is not valid in the month */
    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) {
    /* Find the day number based on the cell references for a given month and year */
    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(); // Create an instance of the template object
    $("#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"
                                      }
                                  }
                               };

        /// <summary>
        /// Return a list of people and their ID's
        /// </summary>
        /// <returns></returns>
        public ActionResult List()
        {
            return Json(new { Success = true, Items = PersonList.Select(p => new { p.ID, Name = p.Name }) });
        }

        /// <summary>
        /// Return a single model object
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        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:  

  1. Bind a change event to the field selected by [name=personID]
  2. Query the field selected to get its default (or later changed) .val()
  3. 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: 

  1. Bind to one or more changeable fields like input, select, textarea 
  2. 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 

  1. 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> 
  2. 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. 
  3. 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

  • Template JS spans lines so FireBug debugging works as expected

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 



License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Adrian Holland
Architect Webefinity
Australia Australia
Adrian is current the Solution Architect at CubeBuild.com.
 
The core of CubeBuild is a website and application platform that is pluggable into ASP.NET MVC. Any MVC application can have content authoring added to its pages with little effort, and new content types are created using IronPython.NET open source components.
 
We are currently deploying a Point of Service (Web based POS) built on CubeBuild which allows a single web channel for face-to-face sales, and sales through your online store. All from a single inventory base, and from any device.

Comments and Discussions

 
GeneralExcellent PinmemberAshraf Sabry25-Sep-13 11:50 
NewsVersion 1.0.0.12 PinmemberAdrian Holland11-Jan-13 4:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 10 Jan 2013
Article Copyright 2013 by Adrian Holland
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid