• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Template-Comparison

Page history last edited by BorisMoore 12 years, 5 months ago

This page gives the results from some studies of alternative template implementations.

 

Template implementations using the jQuery Templates syntax

The following template implementations all implement effectively the same template syntax, taken from jQuery Templates plugin, and corresponding closely to the grammar documented here: Template.

 

 

jQuery Templates 

This is the jquery.tmpl.js plugin which was made an official plugin, and is now being managed by jQuery UI. It was originally a prototype created by John Resig, and most of the subsequent development was by Boris Moore. The repository is at https://github.com/jquery/jquery-tmpl and the API is documented at http://api.jquery.com/category/plugins/templates/.

 

The API pattern used for rendering data to HTML is: $( "#myTemplate" ).tmpl( myData ).appendTo( "#containerElement" ); where myData can be an object or an array. 

 

Rendering depends on the HTML DOM, and results in a set of rendered nodes. So this is not just string concatenation. One advantage of this is that it is easy (using $.tmplItem) to get the template context of any element in the rendered content. This allows getting to the data, as in var thisDataItem = $.tmplItem( thisElement ).data; as well as accessing the whole template hierarchy, other DOM elements in the same rendered template instance (e.g. Grid row), etc. as illustrated in the following samples: http://jquery.github.com/jquery-tmpl/demos/step-by-step.html.

 

Strappend 

Strappend is Mike Samuel's implementation, which has the goal of re-implementing jQuery Templates as pure string concatenation, seeking improved performance, along with the ability to provide contextually driven automtatic encoding features.

 

See Template for details and description. The code is at https://github.com/mikesamuel/jquery-jquery-tmpl-proposal. It does not provide the DOM integration described above for accessing template context.

 

JsRender and JsViews 

JsRender and JsViews are ongoing work by Boris Moore (working at Microsoft), taking the jquery.tmpl.js design forward with the goal of a) separating jQuery Templates into two components:

  • JsRender is a small fast implementation doing pure string-based rendering with improved performance and with no DOM dependency.
  • JsViews takes DOM content rendered by JsRender (using simple innerHTML insertion) and activates it to provide:
    1. Equivalent DOM integration features to jQuery Templates, described above
    2. Full integration of Data Linking. The data linking design is inspired by the original jquery.datalink.js plugin, but provides for additional scenarios, such as declarative data linking, integration with templates, direct binding to 'observable' changes made to objects and arrays, etc.

 

The code is  at https://github.com/BorisMoore/jsrender and https://github.com/BorisMoore/jsviews (with demos at JsRender and JsViews)

 

Feature comparison

The code for comparing the implementations is at  https://github.com/BorisMoore/template-comparison, with some test pages at Template Comparison Tests.

 

A comparison of the main features is shown in the following table:

 

https://spreadsheets.google.com/spreadsheet/ccc?key=0AusvKVL7jmFUdGZLdGFMaEdBNHVIRVBlaE9yT1J1MlE&hl=en_US

 

 

Features

  • DOM Independent: Does not depend on the browser (or any other) environment, does not touch the DOM, can run in other environments like node. Takes a string and an object as input. Has a string as output.
  • Jagged Data: If you reference a property that doesn't exist, no error is thrown. Default values are useful too.
  • Built-in Tags
    • {{if}}: Built-in tag to conditionally render content based on the thruthiness of a property
    • {{each}}: Built-in tag to iterate over an array
  • {{each}} Index: Access to the index of the iteration in an {{each}}
  • Custom Tags: You can define a custom tag and your function gets called as the handler for that tag.
  • Debug Friendly: Errors point to where in the template the error occurred
  • Plugin Wrapper:
    • Render: $( template ).render( data ) // Operates on a script dom element (found from a selector) and renders the data passed in. Returns: String 
  • Template Context: Enables you to get from a generated DOM element back to the template item context object through which it was rendered
    • Link: $( ContainerElement ).link( data, template ) // Renders the data through the template, sets html of ContainerElem to result. Returns: jQuery
    • Link: $( ContainerElement ).link( data ) // Links the data but does not replace the html. Returns: jQuery 
    • View: $.view( DescendentElement ) // Callable on any element inside a template-rendered ContainerElement. Returns: closest template context in DescendentElement's parents() tree 
  • Live Update of Values: Updates DOM elements to reflect changes in observable objects, using data link
  • Render Arrays: Pass an array of data objects instead of a single object. Array is iterated over and template rendered for each object in the array. Saves from having to iterate manually inside or outside the template.
  • Row Index Support: (depends on Render Arrays) Similar to {{each}} Index but provides the index of the iteration by Render Arrays feature.
  • Incremental Rendering: Live Update of Values for observable array rendered through Render Arrays feature. Makes the minimal changes in the DOM that corresponds to minimal observables changes in the array.
  • JS Expressions: Allows for some subset of JavaScript (ie logic) inside template tags. This is in contrast to logic-less templates such as Mustache or Handlebars.
  • Template Composition (aka Nested Templates): Built-in support (not requiring a custom tag) of calling a template within another template.
  • {{wrap}} Tag: A form of template composition. Uses HTML contents of wrap tag as data/context for referenced template. 
  • Contextual Encoding: (TODO: short description)  

 

Notes: 

  • Column 4 '+ JsViews' shows the features obtained by using JsViews along with JsRender.
  • Column 6 'Strappend2' corresponds to a modified version of Mike Samuel's Strappend code in which support has been added for:
    • Using a plugin wrapper, as in $( "#myTemplate" ).render( data )
    • Directly rendering arrays, via the render() method, or the {{tmpl}} tag
    • a few minor fixes...
  • Column 7 - Strappend-auto is a version created from Mike's code to be able to have a single file providing smart contextual escaping without having to pre-compile templates.
  • Data for mustache based on mustache.js docs and mustache docs

 

Feature differences: Details

Important for Grid - essential features:

  • DOM independence: This can be important for people who use templates on the server, e.g. for intitial rendering. Less important for the jQuery UI Grid itself.
  • Custom tags are important for users. There is also a built-in feature (in a branch, not yet in main) available for JsViews which allows undefined tags to be interpreted as jQuery plugins. So if you to put a tag like {{datepicker}} around an element, as in: {{datepicker(options)}}<input data-from="[startDate]" data-to="[startDate]" />{{/datepicker}}, and if you don't actually have a custom datepicker tag defined, but you do have a datepicker plugin, then JsViews will attach an instance of the datepicker plugin to the input, and bind it to the startDate property of the current data item. We can use this feature in the Grid, but only if we support custom tags.
  • Jagged Data: This refers to collections of data (e.g. from a JSON response) which may have some properties optionally present on different data items. For example, there may be an optional street2 key in the returned JSON. Support for 'jagged data' means the ${street2} will render an empty string if street2 is undefined, rather than throwing. Requiring street2 to be defined on the JSON will add to the payload, and is often therefore not the case in real JSON services. Often data items are greatly reduced projections of server types, but which properties are missing may depend on different factors in the request or the data, so a dynamic 'tolerant' approach to undefined properties is generally more appropriate, more performant, and places less burden on the template or page developer (since special converter functions can be avoided). 
  • {{If}} and {{each}: Most template languages do support these directives/tags, or similar.  
  • {{each}} index: makes the index of the each iterator available in the template

Valuable for Grid - desirable features:

  • Debug mode, (with an associated more strict policy of throwing errors), is a useful feature that is provided by Strappend, and which could and should be added to JsRender and JsViews. 
  • Template Context and Data Link integration can both be very important for people using templates for interactive pages, and also for the Grid, in simplifying the way selection, editing, sorting, filtering etc. are implemented - and providing the possibility of significantly better performance through the built-in incremental rendering support provided in JsViews (Data Link integration). Data Link also allows declarative binding within custom templates. Data Link is also associated with 'observable' events and APIs, so that if the Grid is showing data that might be modified by other code or components in the page, it will continue to remain in sync with current data.
  • Plugin wrapper: This is the $.tmpl() or $.render() wrapper allowing you to write var renderedHtmlString = $( "#myTemplate" ).render( myData ); (Support for this has been added to Strappend2).
  •  Render Arrays: This is important. It allows directly rendering arrays, whether using the .render() API or the {{tmpl}} tag, as in $( "#myTemplate" ).renderphoneNumbers, or {{tmpl(phoneNumbers ) '#myTemplate'}}. Otherwise you need to wrap in a for loop, or an {{each}} tag. Also in doing that you will lose the templateContext hierarchy, with the associated access to index, parent data array, etc. (Support for this has been added to Strappend2)
  • Row index support (index of template item / subView) is an often requested feature not currently supported in jQuery templates.
    That support was added in JsRender and JsViews, since the integration of Data Link and Templates means that deleting/adding preceding rows, sorting, filtering, etc. are possible while still maintaining correct index values on all rows, without user code. Data Link then allows the modified indices to trigger incremental rendering updates.
    Examples of this are shown in this demo (or this demo which is a variant using an MVVM-style data model). If many rows are added, and then arbitrary rows are inserted or deleted, then the alternating background color and the row number or subsequent rows continue to show correctly, without user code, thanks to declarative data linking. The updating of the the row number text, and the background color CSS property occurs without the row item itself being completely re-rendered.
    We can of course use this feature to provide alternating background colors in editable or sortable Grids, without significant impact on performance.
  • {{wrap}} tag: Much less important than the ((tmpl}} tag, and harder to implement, but useful nonetheless. It should be possible to add support for {{wrap}} to Strappend. 
  • Incremental rendering: Provided by JsViews, and important for performance in the case of row insertion/deletion, sorting and filtering, for the Grid.
  • JavaScript Expressions in templates: The jQuery Template syntax implies support for evaluation of JavaScript expressions in the template rendering, e.g. {{if a() < b()}}. Many or most template languages either deliberately exclude expressions, or incompletely support them. 
  • Template Composition: This feature is important for users, and for the Grid. Many or most template language do not support nested templates. 
  • Live update of values:  This feature means that when data values rendered in Grid cells get modified by code or controls outside the Grid, then the change is detected, and the current value continues to be displayed. If the templating layer provides this feature
    (through 'observable' events and data linking of values) then the grid will not need special code to achieve this, and the page developer will not need to trigger a complete re-render of the grid when one or more data values are modified outside the grid.  

Not necessary for Grid - nice-to-have, but not really necessary: 

  • Automatic contextual encoding provides an alternative to the normal approach in template of manually determining encoding;
    • The manual approach is to choose between available tags or encoding converter functions in order to ensure correct encoding. You will use ${myDataValue} to guarantee HTML encoding of your data, or {{html myDataValue}} if you don't want HTML encoding. In cases where you want other encoding, such as rendering data into JavaScript literals, or if you want strict minimal Url Encoding, then  you can pass your data through appropriate encoding functions, as in ${myEncoder(myDataValue))}, or through a custom tag: {{jsonEncode myDataValue}}.
    • Automatic contextual encoding is provided by Strappend-auto, which allows you to use ${myDataValue} in different contexts, whenever you want encoding to happen by default.

In the case of the Grid, this feature should not be necessary, however, since the templates in the Grid are built-in, and we, the Grid developers, can guarantee the user of the appropriate encoding for a given context in the template. 

 

Perf comparison

 

The following table provides a selection of perf figures taken from these test pages: http://borismoore.github.com/template-comparison/tests/benchmarks/benchmarks.html.

 

  Chrome  IE9
  No encoding  Encoding  No encoding  Encoding 
  Compile Render Compile Render Compile Render Compile Render
jquery.tmpl 0.083 0.009 0.008 0.022 0.365 0.012 0.008 0.022
JsRender 0.106 0.003 0.106 0.007 0.266 0.004 0.281 0.038
Strappend 0.131 0.006 0.172 0.016 0.284 0.005 0.284 0.011
Strappend-auto 0.331 0.012 0.313 0.019 Not working currently

 

Differences: Details and Notes

Of note is that differences in the rendering performance, once a template is compiled, are fairly insignificant, though jquery.tmpl is slightly slower at encoding, particuarly in IE. (The encoding could be made faster fairly easily, by following the approach Mike Samuel has taken to basic HTML encoding, in Strappend. JsRender figures here already leverage that approach.)

 

On the other hand, compilation is significantly slower with Strappend, because of the more sophisticated pluggable approach which is used there, (and which is needed in order to be able to plug in the contextual encoding provided in Strappend-auto).

 

The question that this therefore raises is whether that compilation-time cost of Strappend is justified by features or other advantages:

  • The primary advantage of Strappend-auto is that users who provide templates will not need to worry about manually choosing the appropriate tags (or tag options) to ensure 100% safe encoding of any data that might be potentially malicious.
  • However in the case of the jQuery UI Grid, the templates currently planned for are defined internally in the grid implementation, so the correct encoding is determined by the jQuery UI Grid component developer, not the user of the Grid. If we did allow custom templates to be provided to the Grid, then by choosing Strappend-auto we would be imposing not only slower initial rendering (because of compilation), and slightly slower subsequent rendering, but also we would be preventing people creating those templates from having access to template context, using custom tags - such as a {{datepicker}} tag, etc., since those features are not available in Strappend. 
  • There is also a file-size cost of using Strappend-auto, except for users who go through the process of pre-compiling templates out-of-band, and adding them to the page as functions. 

 

Conformance Suites

For tests of conformance with jQuery Templates grammar and semantics See conformance suite links from template comparison tests.

 

Examples for template composition

 

MANUAL

<script id="layout-1" type="text/x-jquery-tmpl">

    <body>

        <h1>Title</h1>

        <div id="content">

        </div>

    </body>

</script>

 

<script id="content-1" type="text/x-jquery-tmpl">

    Hello ${username}!

</script>

 

render( "layout-1" ).find( "#content" )

  .append( render( "content-1", { username: "Scott" } );

 

<!-- ######################################## -->

 

WRAPPING

<script id="layout-1" type="text/x-jquery-tmpl">

        <body>

                <h1>Title</h1>

                <div>

                        {{html $child}}

                </div>

        </body>

</script>

 

<script id="content-1" type="text/x-jquery-tmpl">

        {{wrap layout}}

                Hello ${username}!

        {{/wrap}}

</script>

 

render( "content-1", { layout: "layout-1", username: "Scott" } );

 

<!-- ######################################## -->

 

NESTED

<script id="layout-1" type="text/x-jquery-tmpl">

    <body>

        <h1>Title</h1>

        <div>

            {{template $child}}

        </div>

    </body>

</script>

 

<script id="content-1" type="text/x-jquery-tmpl">

    Hello ${username}!

</script>

 

render( "layout-1", { $child: "content-1", username: "Scott" } );

 

 

Comments (3)

Scott González said

at 11:03 am on Jul 21, 2011

The equivalent of Handlers' helperMissing would be very useful for debugging. Also a similar hook for property/data missing.

Richard D. Worth said

at 11:05 am on Jul 21, 2011

s/Handlers'/Handlebars'/

BorisMoore said

at 10:48 am on Aug 17, 2011

Yes, I'm thinking of providing something along those lines, along with the 'debug mode' switch

You don't have permission to comment on this page.