Usual links are substituted by virtual links containing a virtual references instead of a standard Urls. This solution avoid the burden of encoding all information needed to instantiate a view into a single Url. In fact, the main disadvantages of Url encoding are:
The above circumstances makes SPA attractive also for standard Web Sites that needs search engines indexing. For this kind of SPAs Urls play the same role they have in standard Web Applications. Namely, they furnish a way to reach all pages that compose the application and, together with all semantic tags, they help search engines to classify properly all pages.
The Url based ko.routing router was conceived as an attempt to keep the SEO advantages of standard Urls while overcoming all disadvantages of keeping information encoded into strings (Urls).
ko.routing may compute dynamically all Urls starting from all parameters that must be encoded in the Urls themselves. This result is obtained by “inverting” the routing rules. The router action method, and the related knockout.js action binding accept the parameters to be extracted from an Url and build the Url that would yield exactly those parameters. They are very similar to the Asp.net Mvc Url.Action method, and have the same purpose of keeping the code completely independent from the routing rules.
The 1.2 release of the Data Moving Controls suite added the support for ko.routing, and offers also SPA project templates based on ko.routing, namely: SPA_Mvc5_ko.routing.vsix and SPA_Mvc4_ko.routing.vsix.
In this post I’ll revisit the Data Moving SPA with a Context-Dependent Menu tutorial with the new ko.routing based visual studio templates. The main difference is that now all menu items will contain actual links with actual Urls.
You may repeat exactly the same steps of the Data Moving SPA with a Context-Dependent Menu tutorial till the “Defining a Menu to Navigate our SPAViews” section(not included), with the following two differences:
We will add the “Menu” SPAView to the Basic module that is loaded as soon as the application starts, since our menu is needed also in the first loaded page. Right click on the Views/Templates/Basic folder and add a new SPAView called: “Menu”:
Each menu item stores both the computed Url and the original parameters used to compute it, since the parameters are needed to verify if a menu item Url refers to a SPAView the current user has the right to visit or not. Moreover, the original parameters help in verifying if the current SPAView is an instance of the SPAView referenced by an item menu, with some possible input parameter more(simple string comparison between two Urls is able to verify only if both Urls reference the same SPAView with exactly the same input).
The client template used to render each menu item is selected by calling a template selection function that receives the menu data item as argument. In order to ensure the maximum flexibility each menu item has its own template selection function. However if no item specific function is passed, the default template selection function passed as an argument to the menuBuilder constructor is taken.
The down method moves the fluent interface to the definition of the sub-items of the current item, and the up method returns to the definition of the father item.
Finally the get method yields the final object tree.
Now we are ready to build the Menu ViewModel:
The viewModelContent is the put in place by the standard scaffolded code below:
that creates all needed knockout observables an put the new modified model in the ViewModel Content property.
The default template selection function selects 3 different templates: Basic_MenuItem2 for father items with children items, Basic_MenuItem1 for all items that do not contain an Url, and Basic_MenuItem0 for all items containing an Url. Each template name is obtained by chaining a base name declared with the .TemplatesBaseName method of the Menu fluent interface with the the sequence number of the template defined with the Menu fluent interface. Below the content of the Menu.cshtml file:
The ExtendedClientMenuFor Html<T> extension declares the root menu items collection, the property that contains the level 0 menu items, and the menu data item property that contains the “action” to execute when the menu item is selected.Then, ExtendedClientMenuFor returns a fluent interface to continue the menu configuration. The root Menu ul tag contains the mainmenu Css class that defines some <ul> and <li> structural properties, and that removes underlining from all menu links.
The above definitions must be appended to the Content/Site.css file. You may experiment by changing some of them.
As a default sub-menus appear when the mouse hover their father menu item, but If the client device is a mobile device the call to ActiveOnlyOnClick() change this behavior, and sub-menus are open on “click” or “tap”.
Then we declare which item property contains the target where to open links, and the various menu items radiuses (see the picture that shows the three radiuses here ).
The way each menu item is rendered is defined by 3 templates, that are configured inside 3 AddRowType()-EndRowType() blocks. All templates have an unique column based on a custom template. The addition of the GenericClasses.NoWrap Data Moving predefined Css class prevents menu item titles from wrapping. The ChildCollection call in the last template causes the recursive rendering of all children sub items contained in the Children property. All AddRowType blocks define knockout client templates. Their names are obtained by adding the postfixes ‘0’ and ‘1’ and ‘2’ to the template base name “Basic_MenuItem” declared with TemplateBaseName. It is good practice to give names of the type <module name>_<a name> to all client templates used by controls declared inside SPAViews.
The selection of the right template is performed by the template selection function declared with TemplateSelector. In our case a function declared on line recalls the default template selection method previously defined in each menu data item.
That’s enough to see the menu working! Now you may run the application and enjoy your Menu!
Now, our last objective is to adapt the menu to the logged user and to the actual SPAView that is in the main host. More specifically:
Both requirements may be achieved by letting the content ViewModel of our menu implements an interface, say,IContextDependent with two methods:
In general we may handle several SPAViews that implements the IContextDependent interface in a modular way as follows:
We may define the class that implements the IContextDependent resource handler in the Views/Templates/Application/MainJs.cshtml file next to the definition of the uiBlockInterface class:
Then, we add an instance of this class to the applicationModel.interfaces property, so that it is available within the applicationModel itself and to all SPAViews:
The IContextDependent.select method must be called each time the router selects a new SPAView is placed in the application main host. Accordingly, let open the Views/Templates/Application/RoutingJs.cshtml file that contain the definition of all routing rules and let add the .select call immediately after the the start of a new SPAView loading:
Since all default routing rules use the default action function our call must be placed only there. The default action uses the view, module and role parameters to create a virtual reference and then passes all parameters extracted by the Url as input to the SPAView (Input property of the virtual reference). Then the virtual link is used to fetch the SPAView from the default page store. If an instance of the required SPAView with the specified role(if any) is already stored it is returned, otherwise a new fresh copy is created (and stored) by the page store possibly downloading the required module from the server. Finally, the newly fetched SPAView is inserted in the main page host (applicationModel.MainContent).
The .select call is wrapped inside a setTimeOut to ensure it be executed only after that all IContextDependent implementations (in our case just the menu) have been loaded when the SPA starts.
The right place where to call IContextDependent.register is the processInput method that is called each time a SPAView is loaded, while the right place to call IContextDependent.unregister is the beforeRemove method that is called before the SPAView is unloaded:
Now our vm.Content ViewModel must implement the IContextDependent interface. You may place the interface definition immediately before the vm.processInput method definition:
Both methods call two private recursive functions that traverse the menu data items hierarchy to do their job. They are exactly the same of the ones defined in the Data Moving SPA with a Context-Dependent Menu tutorial.
The currentSelected private variable contains the menu data item that is currently selected, if any, otherwise, null. If a null action is passed, currentSelected is set to null and any previously selected menu item is unselected by calling mvcct.html.menu.selected(currentSelected, false).
Otherwise the reclusive selectMenu function tries to locate a menu data item matching the action string. If such a data item is found the menu item it is bound to is set in the selected state by calling: mvcct.html.menu.selected(item, true)and the data item itself is returned in the res variable, so it may substitute the previous currentSelected. If the selectMenu recursive search fails, null is returned in res, and any previously selected item is unselected by calling mvcct.html.menu.selected(currentSelected, false).
The authorize method immediately calls the recursive private function authorizeMenu passing it the current authorization manager, and the level 0 menu data items. The authorizeMenu function traverses the menu data items hierarchy and checks possible links to SPAView against the authorization manager to verify if the SPAView may be accessed by the current user. In case the SPAView referred by a menu data item cannot be accessed by the user, the data item rendering is prevented by setting its _destroy property to true. If a menu item x has the only purpose of showing its children sub-items, and if all its children have _destroy set to true also the _destroy property of x is set to true, since it is completely un-useful.
The private functions selectMenu and authorizeMenu may be placed in the read-only private members area immediately before the mvcct.core.moduleResult call. They differ from the analogous function of the Data Moving SPA with a Context-Dependent Menu tutorial only in the way references to SPAViews are compared:
It is worth to point out that each time we make invisible a menu item we call the mvcct.ko.unfreeze to release any possible cached template, since most of Data Moving controls cache templates instances to improve performance.
The selectMenu function compares the set of parameters of each menu item with the set of parameters that identifies the current SPAView. If the current SPAView contains some parameters more, the comparison is considered successful since those parameters are further specifications of the input contained in the menu item.
Now the context dependent menu is ready. Whenever you navigate to a SPAView that is referenced in a menu item that menu item will become visible and will appear in a selected state:
If a permanently opened sub-menu is not acceptable in your application, you may customize the Css class that is added to all menu items on the path to the selected item by calling the ItemSelection method of the menu fluent interface. In the example below, a yellow border is added to all items on the path to the selected item instead of leaving them permanently in the opened state:
In this case the “selected color” on the menu item linking to “Page 4” becomes visible only if you hover the yellow-border “private area”:
You may also add a breadcrumb and modify the menu select method to update the breadcrumb, too.
That’s all for now! The full code is available in the “ContextDependentMenu_ko_routing” file that may be found here and in the Data Moving Plugin Examples Codeplex site download area. The Visual studio solution must be activated by installing the DataMovingPlugin5Examples.x.x.x.nupkg file you get with the product.