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.
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:
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:
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:
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:
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”:
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.
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:
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 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.
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:
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.