Nested Templates and TreeIterator

Client templates instantiation is one of the techniques used to implement SPA applications: the initial page contains just a minimum Html, and the remainder of the Html is created dynamically by instantiating possibly nested client templates on data items. Typically, template instantiations are triggered by actions executed by javascript code. The Data Moving Plug-in offers several tools to map a complex object into a complex Html fragment in a declarative data-driven fashion. Namely the _with and _foreach knockout.js extensions and the ClientTreeIterator.  

The ClientTreeIterator helper scans a hierarchical data structure and decides  which template to use for each data element it meets during this processing. The developer is required to provide just a list of templates, and a function that selects a template given a data item. After that, the provided templates are used as building blocks to produce the whole UI of the page (or of a part of the page) as soon as a new client ViewModel(or a new part of it, such as a Workspace) is sent to the client side. Each Template, in turn, may istantiate other templates by calling the _with, and _foreach knockout.js extensions.

In large SPA list of templates may  organized into modules. If a template is not found in the page, the module it is in is automatically required to the server and loaded dynamically, together with some javascript files it might need. Unused modules are automatically unloaded according to a least recently used cache policy.

Since the Data Moving Plug-in is able to compile any Razor template into a client side template, we can put everything in the templates provided to the ClientTreeIterator _with, and _foreach : paged grids and/or complex controls such as Dual selects , or whole Data Moving Forms.

We will see how the TreeIterator works with a simple example: automatically building a multipage JQuery mobile menu based on nested lists, from the information contained in a hierarchy of objects. We will use the Mvc Controls Toolkit SimpleMenuItem class to define each menu item.

As first step we declare all needed templates:

  1. var menuTemplates = h.DeclareClientTemplates("mobile_menu")
  2.                 .Add<SimpleMenuItem>(
  3.                 @<ul data-role="listview" id="menuRoot">
  4.                         @item.RecurIterationOn(m => m.Children)
  5.                               </ul>
  6.                 )
  7.                 .Add<SimpleMenuItem>(
  8.                 @<li >
  9.                         @item._D(m => m.Text)           
  10.                 </li>
  11.                 )
  12.                 .Add<SimpleMenuItem>(
  13.                 @<li >
  14.                     @item.LinkFor(m => m.Text, m => m.Link)            
  15.                 </li>
  16.                 )
  17.                 .Add<SimpleMenuItem>(
  18.                 @<li >
  19.                     @item.LinkFor(m => m.Text, m => m.Link, target: m => m.Target)          
  20.                 </li>
  21.                 )
  22.                 .Add<SimpleMenuItem>(
  23.                 @<li >
  24.                     @{var mitem = item.ViewData.Model;}
  25.                     @item._D(m => m.Text)
  26.                     <ul data-role="listview">
  27.                         @item.RecurIterationOn(m => m.Children)
  28.                                     </ul>
  29.                 </li>
  30.                 );

This task is easily accomplished with the fluent interface of the DeclareClientTemplates helper. The unique argument of this helper is the templates base name. All templates are named by adding an integer suffix to this base name: the firts template is named “mobile_menu0”, the second one “mobile_menu1”, and so on.

Our first template defines the startup template, that is, the initial template that starts the rendering. The startup template is not necessarily the first one, but the startup template is declared in the call to the TreeIterator helper. Then we have the template to use for displaying pure text, the template of a menu item that links to a target page, the template of a menu item that links to a target page to be opened in a new browser window, and finally the template to be used for a menu item that has sub-menu items.

As you can see both the startup template and the last template call RecurIteration on the children of the current SimpleMenuItem. This is the way the TreeIterator visits recursively, the whole data structure. In general, each, template may contains several calls to RecurIteration if the object has several children arrays. Inside each templates contents may be turned on/off with the help of the _if Mvc Controls Toolkit knockout.js extension.

Once we have defined all templates we need just to render them:

  1. @menuTemplates.Render("menuTemplateChoice")

The single argument of the Render method is the name of the javascript function that selects the template to use for each data item:

  1. function menuTemplateChoice(item) {
  2.     if ((!item.Children()) || item.Children().length == 0) {
  3.         if (item.Link()) {
  4.             return item.Target() ? 'mobile_menu3' : 'mobile_menu2';
  5.         }
  6.         else return 'mobile_menu1'
  7.     }
  8.     else return 'mobile_menu4';
  9. }

The above code is self-explanatory. Finally, we may call the TreeIterator helper that "will do the job”:

  1. @h.ClientTreeIteratorFor(m => m, "mobile_menu0""listviewAfterRender")

The first argument is the root of the objects hierarchy, the second argument is the name of the startup template, and finally the third argument is a javascript function to be called after the whole objects hierarchy has been rendered. In our case the listviewAfterRender javascript function enhances the <ul> <li> nested list created by the ClientTreeIteratorFor transforming it into a JQuery mobile nested listview:

  1. function listviewAfterRender() {
  2.         $('#menuRoot').hide();
  3.         setTimeout(function () { $('#menuRoot').listview(); $('#menuRoot').show(); });
  4.     }

The nice part, is that as soon as we substitute the root of the objects hierarchy with a different data item the UI of the menu is changed immediately to reflect the new data.