Client-Side Templates 

Requires MVCControlToolkit.Controls.Core.x.x.x.js, MvcControlToolkit.Bindings-x.x.x, knockout.x.x.x.js, knockout.mapping.x.x.x.js and  MvcControlToolkit.Utils-x.x.x.js.

MvcControlToolkit.Utils-x.x.x.js must be placed AFTER knockout.x.x.x.js, since it checks if knockout.js features have been installed.

Requires the inclusion of the namespace: MVCControlsToolkit.Controls.Bindings

Client-Side Templates allow a rich UI to be defined just once in a template, and then to use this template to render each of the items of a big collection contained in the Client-Side ViewModel. This way the rich interface is obtained with a low Internet BandWidth cost. The price we pay for this improvement is that the html is not indexed by the search engines, since  the content is created dynamically by Javascript. However, we can render only a part of the page data with templates, while some other data are rendered normally in such a way that search engines have enogh information to index the page. 

The client side template engine of the Mvc Controls Toolkit is based on the  client template engine of the knockout library that we have enhanced with the following features:

  1. Client Templates can be defined on the server side as normal server-side templates. Therefore, they can use server controls, such as TypedTextBoxesTypedEditDisplay,  DateTimeInputs, and DualSelect Box  plus some javascript.
  2. Bindings between input controls and data items can be defined either implicitly matching the Html names of the input controls with the names of the associated data item properties, or explicitly by using knockout style bindings attributes. For instance, if the implicit bindings definition is on, by simply writing @TextBoxFor(m => m.Name) we render a textbox and define its binding with the Name property of the data item, since in this case the name of the textbox matchs the name of the data item property. Helpers to define explicit binding with knockout style attributes is described in Bindings Reference. Once a binding is defined modification to the input control are automatically reflected to the data item and vice versa.
  3. One-way bindings, that is, bindings that are evaluated just once, when the template is istantiated can be defined with the jwuery templates syntax, ie, for instance  {fieldName}, or with the _P, _F, and _D helpers (described later on in this section). The use of the _P, _F, and _D helpers enhance the re-usability of the templates because they renders the same Html also when they are used in server side templates.
  4. All input fields created dynamically on the client-side are validated both on client-side, and on the server-side. Server side validation is performed not only on the inputs associated to input fields but to all elements of the Client-Side ViewModel once this is received on the sever and deserialized: both property level and model level validation rules are applied and the ModelState is updated accordingly. In order to apply the server-side errors to all input fields we are required to call the HandleServerErrors() method of the IBindingsBuilder interface that is returned when we create the Client-Side ViewModel(the same interface we use to create the bindings).  Attention: both client-side and server-side validation require unobtrusive validation to work properly. In particular, validation is not performed in Mvc 2.
  5. All names and ids of the input fields are exactly the same as if they would have been created on the server-side(please, remember that the Client-Side ViewModel, that is used to bind the templates, is just a part of the Server-Side ViewModel).

A client side template is declared either implicitly when using the ClientBlockRepeater, or explicitly with the helper:

 

 public static MvcHtmlString ClientTemplate<T>(this HtmlHelper htmlHelper, string uniqueName, object template, bool? applyAutomaticBindings=null, T prototype=null)

 

Where uniqueName is the unique id of the template on the client-side, and template is an Mvc Controls Toolkit template. If automaticBindings is set to true, implicit binding definition is used with all input controls. The default of applyAutomaticBindings is null, in which case implicit bindings will be used if the helper is called within a Client Block, and explicit bindings otherwise.

If prototype is provided it will be serialized in json and used to define on the client side a javascript prototype for the objects T that can instantiate the template. It will be put in a javascript global variable having the same name of the template. Thus, for instance, to create a new element of Type T on the client side it will be enough to call new templateName(); where templateName is the name that was given to the template.

In order to bind the template to data just once, when the template is istantiated one can either use the {fieldName} syntax, or _P, _F, and _D that will be defined below. In order to bind templates to data in a two- way fashion, so that modification to input controls are automatically reflected to the data item and vice versa one can either set automaticBindings to true  and names all input controls adequately, or use the knockout bindings as described in the Client-Side ViewModel section

Using implicit bindings.

The simplest way to define two-ways bindings is just setting  automaticBindings to true and relying on a name convention to bind the input controls to the data item properties. In turn, the simplest way to use the "right" names for the input controls is through the use of the Html helpers based on Lambda expression. The example below shows a practical use of implicit bindings:

@{var template = Html.ClientTemplate<ProductView>(
                    "productViewTemplate",
                    _S.H<ProductView>(
                        @<tr>
                            <td>
                            @item._D(m => m.Description)
                            </td>
                            <td>
                            @item.TypedTextBoxFor(m => m.Quantity)
                            @item.ValidationMessageFor(m => m.Quantity, "*")
                            </td>
                            <td>
                            @item.DateTimeFor(m => m.DeliveryDate, DateTime.Today,
                                        false).Date()
                            </td>
                     </tr>
                    ), true);}
                @template

 

As you can see the template is identical to a an usual server side template, and it can be used also as server side template.

Once we have defined template we can use it by specifying both an html node where to attach the new content created and a collection of the Client-side ViewModel to use for istantiating the template. This can be done by applying a template binding to the html element that will receive the created content or by using the ClientBlockRepeater, if we are in a Client Block.

Below an example of use of the template we defined previously.

As firts step we define a Client-Side ViewModel, and the template binding:

 

var clientModel = Html.ClientViewModel("productsClientView", m => m);
var tBinding = clientModel.Template("productViewTemplate", m => m.Products).Get();
 

 

Then we apply it to a <div>:

 
<div data-bind="@tBinding" id="provaScript">

If we are in a Client Block our job is even easier. It is enough to call jus:

@Html.ClientBlockRepeater( "productViewTemplate" ExternalContainerType.div)

Finally, if we would like to add also server side errors it is enough to write:

 

@clientModel.HandleServerErrors()

 

Using the {fieldName} syntax.

Please refer to the jquery template engine documentation for more information on the {} sintax.

Using the _P, _F, and _D helpers

Attention: the _P and _F helpers have been marked as obsolete, and we just kept them for compatibilty with the previous releases. Please use just the _D helper. At moment the _P and _F helpers works just with the jQuery template engine, but in the future they will be removed completely.

The template helpers below can be used inside a template to define one-way bindings from a data item to the template. They produce the same result when the template is used on the server side or on the client side.

 

public static MvcHtmlString _P<VM, T>(this HtmlHelper<VM> htmlHelper, Expression<Func<VM, T>> expression)

 

 

It renders the field defined by expression as it is with no formatting.

 

public static MvcHtmlString _F<VM, T>(this HtmlHelper<VM> htmlHelper, Expression<Func<VM, T>> expression)

 

 

It renders the field defined by expression with the formatting information provided in its FormatAttribute. In particular, if it is a number or a date it is globalized and displayed with the chosen format, or if null, with the NullDisplayText.

 

public static MvcHtmlString _D<VM, T>(
            this HtmlHelper<VM> htmlHelper, 
            Expression<Func<VM, T>> expression,
            IDictionary<string, object> htmlAttributes = null, 
            string valuesArrayName=null,
            string urlsArrayName=null)

It renders the field defined by expression in the same way as the _F helper,  but it encloses the result within a <span> with the appropriate id generated according to the path of the field in the ViewModel.  The result is the same as the one of the DisplayField helper on the server-side. For more information about the _D helper see here.

Using the knockout bindings.

It is not possible to use always implicit bindings. If, for instance, we want to bind a button to a click handler we cannot do it with implicit bindings. In such cases we can either use normal jQuery functions, or more simply knockout style bindings. In such cases the simplest solution is to use implicit bindings for all input controls we can and then to use knockout bindings for all others.

The knockout bindings are applied as explained Client-Side ViewModel section. The only point is that when we are in the template no Client-Side ViewModel is defined, so we have no IBindingsBuilder interface for creating the bindings. In fact, the ViewModel of each item will be defined when the template is invoked on a collection of items of the overall Client-Side ViewModel.

To overcome this problem we define a kind of virtual item ViewModel with the Helper:

 

public static IBindingsBuilder<T> ItemClientViewModel<T>(
            this HtmlHelper<T> htmlHelper
            )
            where T : class, new()

 

 

where T is the Type of each item that will be bound to the template.

The example below uses the_D helper to define a one way binding, implicit bindings for all input controls, and a knockout style bindings to define the click handler of a button.:

 

 @{var templateChosen = Html.ClientTemplate<ProductView>(
                    "productsChosenTemplate",
                    _S.H<ProductView>(
                        @<tr>
                            <td>
                            @{var itemBindings = item.ItemClientViewModel();}
                            @item._D(m => m.Description)
                            </td>
                            <td>
                            @item.TypedTextBoxFor(m => m.Quantity)
                            @item.ValidationMessageFor(m => m.Quantity, "*")
                            </td>
                            <td>
                            @item.DateTimeFor(m => m.DeliveryDate, DateTime.Today,
                                        false).Date()
                            </td>
                            @{var totalPriceBinding = itemBindings.Text(
                                  m => m.Quantity, 
                                  "MvcControlsToolkit_FormatDisplay({0}*{1}, 'n', MvcControlsToolkit_DataType_Float, '', '$', '')",
                                  itemBindings.L<decimal>(m => m.Price)).Get();}
                            
                            <td><a href="#" data-bind="click: function() { confirmModel.Products.remove($data) }">Delete</a></td>
                     </tr>
                    ), true);}
                @templateChosen 

 

 


Last edited Jun 22, 2014 at 10:45 AM by frankabbruzzese, version 28

Comments

No comments yet.