Mar 20 2013

Single Page Applications 3: TreeIterator

Category: Asp.net | MVCFrancesco @ 23:08

Data Moving Plugin Controls

Data Moving Plugin Styling

Data Moving Plugin Forms: Detail Views, Data Filtering UI, and Undo/Redo Stack

Single Page Applications 1: Manipulating the Client Side ViewModel

Single Page Applications 2: Validation Error Handling

Single Page Applications 3: 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 client templates on data items. Typically, template instantiations are triggered by actions executed by javascript code. The Data Moving Plug-in offers a simple way to create dynamically all Html of a SPA, in a declarative data-driven fashion. The TreeIterator 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.

Since the Mvc Controls Toolkit, and consequently the Data Moving Plug-in that is built on top of it, are able to compile any Razor template into a client side template, we can put everything in the templates provided to the TreeIterator: 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:

  1. public class SimpleMenuItem
  2. {
  3.     public SimpleMenuItem();
  4.  
  5.     public List<SimpleMenuItem> Children { get; set; }
  6.     public string Link { get; set; }
  7.     public string Target { get; set; }
  8.     public string Text { get; set; }
  9.     public string UniqueName { get; set; }
  10. }

 

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, then the template of a menu item that links to a target page, then then 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.

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 them 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 settings.

In this example all templates were pre-rendered in the host Html page, however, in more complex multi-page applications templates may be organized in modules and loaded dynamically together with AMD javascript modules.

The video below shows the menu working:

 

That’ all for now!

Stay tuned and give a look also to all other Data Moving Plug-in introductory tutorials and videos

                      Francesco

Tags: , , , , , ,