The RTM version of the Data Moving Plug-in comes with a sophisticated SPA view engine. Views are downloaded dynamically from the server when they are needed together with Javascript AMD modules containing all code they need to work properly.
Views are developed as Mvc Partial Views and are then compiled into client side templates. Accordingly, they may use all Data Moving Plug-in controls and features.
Also the AMD associated with each View may be developed as Partial Views and may take advantage of the Razor syntax to supply dynamic content to the client. Specifically, the developer may use Html extensions to serialize .Net data structures into their equivalent Javascript data structures, thus avoiding the duplication of the same data structures in both .Net and javascript. Moreover, the AMD content may depend on the language selected and on the user logged – in, and in general it may depend on contextual information, since it is created dynamically.
Both Views and their associated AMD are deployed by Asp.Net Mvc Controllers that take care of verifying user authorizations, of minifying the AMD code, and of handling the caching of both Views and AMD.
Views are organized into logical units, in the same way as several classes are organized into dlls (dynamic load libraries). In turn, each logical unit is composed of two deployment units: a template module containing all Views of the logical unit compiled into clients templates, and an overall AMD module containing the Javascript code associated with all Views of the template module in a minified format. Thus, Mvc Controllers, actually deploy template modules and overall AMD modules.
The organization of Views and of their associated AMD modules into deployment units increases the modularity of the application and reduces server round trips.
Typically all View related modules are deployed by an unique Controller that has a different action method for each module. In other words each action method is in charge for deploying both the overall AMD and the template module of a single logical unit.
Below a typical action method:
If the isJs parameter is true the controller deploys the overall AMD module, otherwise it deploys the template module. The overall AMD module is deployed by the MinifiedJavascripResult method that takes care of extracting and minifying all javascript contained in the partial View.
In the example above the template module, and the overall AMD module are contained respectively in the partial views Home/Main.cshtml, and Home/MainJs.cshtml located under the controller folder. Both files don’t contain the whole code but just the code needed to package all Views and all AMD modules into single deployment units. Typically, the code of each view and AMD is contained in a separate partial view that is called by the Main or MainJs partial view.
Below a typical organization of Views and AMD files handled by a controller called TemplatesController:
Each logical unit is contained in a folder with the same name of the unit. All partial views containing code have the same name of their associated Views with a Js postfix. The Main and MainJs partial views of each folder take care of packaging all partial views contained in the folder into the two deployment units called by the controller.
Below a typical MainJs module:
It is an AMD that returns a function of two parameters a data item and a view name. This function will be called with the name of the view to render and with the data item that will be bound to that view. This function just selects a function specific for each view that is defined in a separate partial view. The javascript code in the partial view must be included within <script> tags to get a valid Razor syntax. These tags will be removed by the MinifiedJavascriptResult helper method in the controller.
The partial views containing the view specific javascript code are included statically in the MainJs partial view with the call to @Html.IncludeStaticJsModules. After that , the javascript mvcc.core.withIncludedModules binds the values returned by all view specific modules to the parameters, menuF, registerF,…etc.The structure of a typical view specific module will be descibed later on in this post.
Below also a typical Main partial view:
It receives as input the module name. In fact the module name is not fixed, but may contain prefixes with the selected language, the user logged in, and other context-dependent information. Thus, in general, a module name has a structure of the type: en-US/john777/Home. The code contained in the view declares the module, adds a template for each view, and finally renders the whole module The rendered content is an Html node containing several client side template inside it. The first argument of each template declaration is the template, while the last argument is the view local name; the view full name is obtained as: <module name>+”_”+<view local name>. In the example above each template is specified with a partial view, so the first argument is just the partial view name. However, templates may be specified also as in-line razor templates. See here fore more details on templates.
The AddContentPagePartial helper is a simplified way to define Views that cover the role of virtual pages. In fact each virtual page has the following standard structure:
It contains the view local name, the module the view is in (without any contextual prefix), a boolean that is true if the page has associated javascript code. Then it contains a generic content. The AddContentPagePartial helper specifies a template for just the content of the page.
Below the two routing rules that pass all AMD and template modules requests to the action methods:
Below the action method that deploys the “People” template module and overall AMD only to logged users:
It has two more parameters: the user name, and the authentication ticket. In the template module request the authentication ticket is passed in the asp.net authentication cookie, while in the overall AMD request the ticket is inserted in the query string with the format: ?ticket=<auth ticket>, since Javascript file requests typically do not send cookies.
Modules are cached by the browser if we add adequate OutputCache attributes to the action methods that handle the modules. However modules are cached also inside the Html page with a least recently used strategy to improve performance. As a default the framework cache 10 modules, but this value may be changed by calling the javascript method:
Views are loaded in “placeholders” bound to model properties: each time the a new data item is inserted in the property, a template selection function selects a new view to use for rendering the new data item. Before rendering starts the data item is manipulated, and possibly filled with new content by the AMD associated with the selected view.
More specifically, the following procedure is triggered each time a new data item vm is associated to a “placeholder”:
Thus, the developer may modify vm before its rendering with func and immediately after its rendering with the afterRender function. Typically, func fills vm with javascript content while afterRender applies jquery plugins to the rendered content, and specifies options of updatesManagers and retrievalManagers possibly created during the view rendering.
Typically, data items, representing “virtual pages” are initially empty, and contain just the page complete name (module name + view name). The whole page content is put in place by func. The function func is the same for each module and it is the function returned by each MainJs partial view, we discussed before in this post. It just dispatches the call to view specific functions. Below a view specific javascript module that prepares a menu virtual page:
The data structure containing all menu information is built in .Net with the help of the virtualReference class that defines virtual links to virtual pages. Text content is taken from a resource file, so it may depend on the selected language. The VirtualPageMenuItem class is not part of the Data Moving Plug-in, but is specific of the example.
The .Net data structure is serialized in javascript by the JavascriptDefine helper that defines a javascript variable named mainMenu and fills it with the serialized data structure.
The mvcct.core.moduleResult javascript funtion returns the module result to the calling MainJs AMD. The returned value is the function that will be applied to the data item vm in case the view required is a “Menu” view. If vm has not been already initialized this function fills the Content property of the virtual page with the data structure describing the whole menu.
Below a javascript module that uses an afterRender function to apply a jQuery plugin to the submit button:
It is worth to point out how the button is located in the page. We can’t use a Css class to locate the button since this way we would risk to re-apply the jquery plug-in also to other buttons contained in other instances of the same view that have already been rendered in the Html page. A .find applied only to the newly created content might be too inefficient since it would not use CSS inedexes of the Html page. Thus, the best solution is using ids and Css classes that are specific to the instance of the view. In our case we prefix the button id with an unique identifiers, different for each view instance, with the PrefixedId helper:
The same prefix is added in the data-helper-prefix html5 attribute of the root node of the template:
This way the javascript module may read the prefix that uniquely identifies the page instance with:
and it may use it to locate all html content in the view instance. The root html node is computed by filtering out all not-Html nodes from the array of all rendered nodes with the helper function: mvcct.core.filterHtml.
The content of each AMD, may depend on parameters like the selected language, or the user logged in, but it can’t depend on the specific database state since, for performance reasons, AMD module must be cached by the browser. Therefore, data coming from the database can’t be added to vm by func, that is part of an AMD, but must be required to the server with a subsequent ajax call placed in the afterRender function or by instructing a retrievalManager to fetch data from the server as soon as it has been created.
Below a retrievalManager instructed to fetch data from the server immediately after it has been created:
It is enough to set to true the first parameter passed to the retrievalManager.
The javascript module associated to the view of the retrievalManager above, specifies some options for the retrievalManager and for an updatesManager rendered in the view in its afterRender:
The options passed specify custom error messages in case of unauthorized access to the server methods (status code 401), and apply screen blocking during the communication with the server.
Views are loaded into View placeholder, that are bound to model properties. Root placeholders are inserted in the host Html page and each view may, in turn, contain nested view placeholders. Views inserted in root placeholders play the role of virtual pages, while nested views play the role of “widgets”. A physical Html host page may contain several virtual pages, but most of SPA contain just a single root view placeholder that is connected with the back and forward browser buttons to simulate the behavior of a standard Html page.
Root placeholder are defined with the ClientIteratorFor helper extension. Below an example of usage:
The first parameter is the property that will be bound to the view placeholder. Each time a new data item is inserted in this property the view engine cycle is run to load a new view. In SPA applications the second parameter is always null, while the third and fourth parameters specify respectively a before render and an after render functions. They are used to implement transition animations. In the example above the before render function runs a fade-out animation, while the after render function specifies a fade-in animation. When the before render animation completes it MUST call the function f passed as fourth parameter to inform the view engine that it can remove the old view and can insert the new one.
Placeholders for nested views are specified with RecurIterationOnSingle and RecurIterationOn html extensions. The first one adds a single view instance and may be bound to a property containing a single data item, while the second one adds several views, one for each data item contained in the IEnumerable it is bound to. Below a RecurIterationOnSingle renders a widget containing a page header and a RecurIterationOn helper renders all menu item of a menu page:
Their first parameter is the property the placeholder will be bound to, while the wrapper and wrapperAttributes optional parameters specify respectively an html node that will enclose the dynamic content and its Html attributes. The data-spa-widget html5 attribute specifies witch view to render. More details on the use of this attribute are contained in the next section.
Also the RecurIterationOnSingle and RecurIterationOn html extension allow before render and after render animations useful to define transition.
The view to load is decided by a template selection function. The default template selection function is able to select the view to load just for virtual pages and for widgets. Normally, this is enough, but the developer has also the option to specify a custom template selection function to be used for all nested views of a module, as first parameter of the Render function of the module definition:
The $data bindings variable specifies that the function is contained in the current view model. We may use also $parent, etc. For more details see here.
Virtual pages are data items that works as empty containers and that specify the view they “need” through their view, module, and hasJs properties, so the default template selection needs just to read these properties. The selected AMD module fill the Content property of the empty page with content specific for the selected view and transforms the empty page into a complete working page.
Nested views (views that are not virtual pages) may be selected by the default template selection functions by means of the data-spa-widget html5 attribute that may be placed in the wrapper Html node that encloses the view placeholder. Its format is: <module>.<view>[.js]. The js suffix specifies that the module has an associated AMD module.
Pages are handled by mvcct.ko.dynamicTemplates.pageStore instances. When the application starts a default pageStore instance is created and inserted in the mvcct.ko.dynamicTemplates.defaultPageStore variable. The developer needs to create other pageStore instances only if the host page contains several page placeholders.
Pages are created by passing a virtualReference object to the get method of a pageStore instance. Once we receive the requested virtual page from the get method we may place it in a property associated with a virtual page placeholder. This trigger automatically the view loading procedure.
Below the first page loading that bootstraps an SPA:
If the stored property of the virtualReference is true (its default) the virtual page is stored in the page store after its creation, while if the stored property is true (its default) an already stored virtual page with the same full name will be returned, if available in the page store. The virtualReference may specify also a role string property. In this case a stored page is returned only if the role specified when it was created matches the role of the virtualReference passed to the get method.
After the SPA bootstrap a new page may be loaded passing a virtualReference directly to the goTo method of current page. If no second argument is passed the page is retrieved from the default page store, otherwise the second argument must be the page store to use. Most of the times, we don’t call directly the goTo method but use the VirtualLink and VirtualLinkFor html extensions that do this job for us:
Where the Link property must contain an instance of the virtualReference class.
The main purpose of the page store is to keep the state of each page, so that when the user returns on the same page he finds the page exactly in the same state he left it. This ensures a better user experience, and makes the SPA similar to a native windows application.
A call to the enablePushState method of a pageStore instance connects it to the browser history, so the user may navigate among the virtual pages with the back and forward browser buttons. If the browser doesn’t support pushState, the view engine reverts automatically to a software simulation.
Some modules might be loaded immediately when the SPA starts. For instance it is convenient that widgets used by all pages, such as page headers and footers be loaded as soon as the SPA starts and kept in the page for the whole lifetime of the page. The error page that is shown in case of module loading errors MUST be loaded statically when the SPA starts to ensure that it is always available in case of network problems.
Loading modules statically is straightforward. The AMD module is required with a standard javascript <script> tag:
However, in this case the define instruction contained in the module MUST specify explicitly the module name:
The template module, instead, is defined inside the host page in exactly the same way it is defined in a Main.cshtml file:
The host page must specify the following configuration information:
Typically all this information is inserted in the page header as shown in the example below:
Then the host page body must get a client viewmodel aware Html helper:
and must use it to render the root view placeholder:
Finally the page host should contain a javascript script tag with the code that bootstraps the SPA by creating its first virtual page, and if the application uses authorization, the code that bootstraps the SPA authorization system:
That’s all for now! In the next post we will see the SPA engine authorization framework, and how the SPA framework handles context-dependent information such as the user logged in and the selected culture. A video showing the Data Moving Plug-in SPA View Engine is available here.
Francesco