Tuesday, March 6, 2012

Approaching Beta: What's changing in JsRender and JsViews

Major update to  JsRender and JsViews
In previous posts and elsewhere, I had set the goal of reaching beta for JsRender at the end of February. A major update has just been committed to GitHub for both JsRender and JsViews, which is effectively a beta candidate for JsRender.

This update brings a large number of API changes, new features, and improvements, to both JsRender and JsViews. It follows quite a period of apparent silence on GitHub (as far as commits are concerned), which left some folks with the impression that the project may have become inactive. The truth is quite the 
opposite. JsRender and JsViews are (in my view!) alive and well and growing apace...

So why the silence? In working on the new bits, I had to address the fact that the beta API design I was aiming for would be breaking for many users. Not a big deal usually for alpha software, but in the case of JsRender and JsViews there has been quite a lot of adoption already, so I wanted to avoid causing too much pain with a series of breaking commits. I decided to work on a single breaking update intended to provide a one-step transition to a beta candidate for JsRender (or at least a candidate for a stable beta API), and also introduce much of the future stable beta API for JsViews.

That update has taken a long time to complete, since it is a major rewrite not only of JsRender but also JsViews... The new bits for JsRender have been nearly ready for quite a while now, but the integration between JsViews and JsRender meant also moving JsViews a long way towards its future beta version. That way current users of JsViews can also port their apps to the new JsRender.

What's changed?
This post provides a guide through the changes, and is primarily intended to help existing users of JsViews or JsRender to move to the new version.

It can also be helpful to first-time adopters, but bear in mind that this is still not quite the official beta, so documentation is limited. I hope to provide more documentation (or update this post) as we move to the official beta for JsRender (fairly soon) and JsViews (probably end of April or early May...). In the meantime, take a look too at all the live samples and the corresponding code for JsViews and JsRender.

JsRender: principal changes
  • {{: ...}} and {{> ...}} tags: The basic tag for outputting data values, {{=some.data.path}}, is now {{:some.data.path}}, and supports converter functions, and almost any javascript expression, as in {{myConverter:someExpression}}.
    It is 
    not HTML-encoded by default.
    To HTML-encode, use the tag 
  • Block tags and inline tags: The syntax for block/inline tags is now similar to HTML element syntax in the use of the '/' character:
    • A block tag is written {{myBlockTag ...}} ... {{/myBlocktag}}.
    • A non-block (self-closing, or inline) tag is written {{myInlineTag/}}.
    • A block tag can become an inline tag if it references a template as content:
      {{myBlockTag ... tmpl="myNamedTemplate" /}}.
  • {{#each}} tag is now {{for}}: There was some confusion around the {{each ...}} tag, in that the name suggested that it only worked as an iterator over an array as in {{#each myArray}}...{{/each}}. In fact it worked also against singleton data objects, and this is made more intuitive by renaming it to {{for ...}}. This means you can write {{for myArray}}...{{/for}}, but for other scenarios you might also write  {{for myObject}}...{{/for}}, which will render against myObject as current data item (data context) for the nested content.
  • Expressions within template tags: JsRender template tags now support almost any JavaScript expression (and also allow parens, to any depth).
    For example, you can write:  {{if  price > 3 && price < (maxPrice - 10)}}Special deal{{/if}}.
    However, unlike with jQuery Templates, the expressions are evaluated within a parsing and execution context which precludes accessing global variables or running any code which arbitrarily assigns or modifies data or state. Only template data properties/methods and registered helper functions/parameters can be accessed. 
  • View properties syntax: To access view properties, such as parent, the syntax is now #parent.
  • $itemNumber is now #index: There is now a zero-based index, identical to the index on view objects when using JsViews. This change is thanks to expression support which has made getting a 1-based index very easy, as in: {{:#index + 1}}.
  • Helper functions: There is now a single concept of a helper function, which can be provided either via the options parameter in the $.render (or the $.link method, if using JsViews), or by registering using the $.views.helpers() API call.
    In either case, a registered helper function myHelper(...) will be accessed within the template using a simpler syntax than before: ~myHelper(...), rather than $ctx.myHelper(...). 
    or example, you can write {{:~myFullNameHelper(firstName, lastName)}}.
  • Template parameters: In addition to helper functions, template parameters can be passed in with options or registered using $.views.helpers() - and then accessed by the same template syntax as helper functions, as in {{:~myParameter}}
  • Aliasing variables for use in nested contexts: If you want to access the data in a parent context, you can now provide a variable (accessed like a template parameter) which will be available within nested context. For example: {{for lineItems ~orderTitle=orderInfo.title}}...{{:~orderTitle}}...{{/for}}
  • Registration APIs: Registering of helper functions, converter functions and custom tags each use an equivalent API pattern: $.views.helpers()$.views.converters()$.views.tags().
  • Registering/compiling templates: This uses a similar API, $.templates(), which also allows defining template properties and resources (below).
  • "Intellisense-friendly" APIs for rendering:  New APIs are provided which are convenient for intellisense. For example, you can now write: $.render.detailsTmpl( person ); 
  • Template resources and settings: Registering templates now supports specifying individual settings for the template, and registering resources such as helpers, converters, other templates, etc. so that they are available only in the context of the registered template, and are not exposed globally to other templates.
  • Layout templates: A template may have a setting: layout: true. This will make it render just once, independently of whether the data supplied is an array, null, undefined, false, or any other data value. A {{for}} tag within the template will then render against the data in the normal way.
  • Debug setting: If a compiled template has the setting debug: true, a debugger; statement will be inserted at the beginning of the compiled template function. This makes debugging declarative templates easier.  
  • Unit tests:  JsRender now includes unit tests.
JsViews: principal changes
  • Views: Previously, both JsRender and JsViews had a concept of a view hierarchy, but the two were distinct. Now a single view hierarchy is created during rendering of templates (and nested templates) and the same view hierarchy is maintained (but augmented with additional methods or properties) during data linking by JsViews. 
  • Declarative data binding: data-fromdata-getfrom and data-to are now consolidated into a single data binding attribute: data-link. This provides for more concise and simpler data binding declarations that support both one-way and two-way binding. See the samples for different use-cases, including binding to multiple target attributes on the same HTML element.
  • Data binding expressions: Data binding expressions are evaluated according to the same contextual and parsing rules as template tag expressions. They can access exactly the same user-registered helper functions, template parameters, or converter functions that are available to template tag expressions - whether registered using $.views.helpers, or passed in with the options parameter in a render() call. In addition, helper functions and template parameters can be passed in with the options parameter of any link() call.
  • Automatic dependency detection :  In data binding expressions it is no longer necessary to put square brackets in paths to specify data-binding dependencies.
    <input data-getfrom="shipping.address[street]" data-to=" shipping.address[street] " /> is now written:
    <input data-link="shipping.address.street" />
    This opens the way to supporting [] syntax for array or property accessors.
  • "Intellisense-friendly" APIs for linking:  As with JsRender, JsViews now provides new APIs which are convenient for intellisense, such as:  $.link.movieTmpl( "#movieList", movies );
  • Disabling linking when rendering a template: If JsViews is loaded, rendered template were previously automatically data-linked. It is now possible to set link: false on a template, or as an option in the render call, or as a tag property: {{for link=false}}, to switch off data linking within a rendered template.
    This is useful, for example, if you want to include template tags within attribute markup, and suppress JsViews HTML comment annotations that were automatically inserted for data-linking purposes.
Missing, or not yet available features
  • Programmatic data linking: Programmatic data link APIs such as addLink have been removed. (They will probably return in modified form for JsViews beta)
  • Unit tests:  JsViews is not so close to beta as JsRender, and does not yet include unit tests. They will be added before it reaches beta.  
Performance and code size
In spite of the many new features, total minified code size for JsViews plus JsRender is similar to the previous update, and performance is slightly better. A performance test page is provided.

Specific examples of porting to the current design
The samples available at github.com/BorisMoore/jsrender (live here) and at github.com/BorisMoore/jsviews (live here) have been changed where necessary to work with current API calls and declarative syntax. In some cases there are new features available which would have provided a more elegant approach to the scenario shown, but the samples have generally been changed in a minimalistic way, to illustrate how to port to the new version. (More samples showing new features will be added at a later point, along with other documentation).

And from here?
The plan is to wait for some feedback or bug reports on this new update, before deciding whether to label this version of JsRender as the official beta. Once it has become beta, I hope to progressively add additional documentation.

In the meantime I will continue to work on moving JsViews towards beta. This may still take a while, since there are some significant changes and improvements in the pipeline. My hope is that it will be available late April or early May...