The Data Moving Menu Control may be used to navigate among the virtual pages of a Single Page Application, too. In this step by step tutorial I will show you how to configure the menu control, and how to adapt it to the authorizations of the currently logged user .
The full working code is available in the “ContextDependentMenu” file on this site and on 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.
The result that you’ll obtain is shown in this Video:
As a first step let select the SPA Mvc 5/WebApi 2 SPA project template in visual studio. All SPA visual studio templates may be downloaded from the Data Moving Examples Codeplex Site.
Select File->New->Project in VS 2013:
Then name the new project:, SPAMenuTutorial:
The result should be this one:
Now you need to install the Data Moving Plug-in for Mvc 5. Right click on the “References” and select: “Manage Nuget Packages…”:
In the window that appears, select the local Nuget source on your hard disk where you copied all Data Moving Plug-in Nuget packages:
Now select the “Data Moving Plug-in 5”, and install it.
Before running the initial SPA that the visual studio template created for you, your Product Key must be placed in the application root Web.Config, in the AppSettings section:
Now you may run the project:
An essential SPA with just an home page, and Login, Logout, and Unauthorized page is created.
All these SpaViews are placed in the “Basic” SPA Module that is loaded as soon as the application starts:
The Application folder contains just two dynamic JavaScript files:
All SPA templates use bootstrap for styling, and install a bootstrap Nuget package that provides just the “core” CSS without any specific theme. In order to improve, the appearance of our demo we may download the default bootstrap theme from the bootstrap web site: “bootstrap-theme.min.css “and “bootstrap-theme.css”. Just add these two files to the web site “Content” folder, and then add "~/Content/bootstrap-theme.css", to the "~/Content/css" style bundle definition in the App_Start/BundleConfig.cs file that contains all bundle definitions:
Without the addition of a theme the appearance of the menu we are going to build would be quite “bad”.
Our plan is:
The Models/ApplicationDbContext.cs file contains a Database seeding function that defines the “admins” role and two users: an administrator and a normal user. In an actual application we should modify this code to adapt it to our needs. In this simple tutorial we may use the default users and roles.
The seeding function is “activated “ by a few lines of code in the Global.asax that are initially commented-out:
Let uncomment them, and run the application. Now we may login as “admins” with the credentials:
username: admin, password: padmin, as shown in the previous video.
Now, in visual studio, right click on the Views/Templates folder and select “new folder”. Name the new folder: “MenuExample”.This folder will contain all files of the new MenuExample SPA Module that we are going to define.
Now, in visual studio right click on the “MenuExample” folder and select: “New Item”:
Select “SPAModule”, and then give to the new SPA module the same name of the previous folder: “MenuExample”:
We should get the following files structure:
The two Main and MainJs files contain the logic to assemble all SPAViews we are going to define into an unique SPAModule. More specifically, Main.cshtml contains the logic to assemble all templates, and to route any incoming View request to the right SPAView template, while MainJS.cshtml contains the logic to assemble the AMD code associated to all SPAViews into the unique JavaScript file associated to the whole “MenuExample” module, and the logic to route any SPAView request to the right chunck of JavaScript code that was previously aggregated to the “MenuExample” module.
Since ,as in most of the cases, we don’t need to modify the default routing rules you don’t need to modify Main, and MainJs.
Now we need to define the Action method that will deploy the “MenuExample” module to the client. Go to the “Controllers/TemplatesController.cs” file, copy the example “MyTemplate” action method…
…paste it, and rename any occurrence of “MyModule” into “MenuExample”:
Now we may define all SPAViews.
Right click on the “MenuExample” folder, and select again “New Item”. However this time, choose “SPAView”. Repeat the same operation five times, and give to the SPAViews the names:
“Page1”, “Page2”, “Page3”, “Page4”, and “Page5”:
Now open each Page1.cshtml…Page5.cshtml file:
Then add the “main-page” Css class and a different header to each page:
You don’t need to define a page ViewModel, since we will use all these pages just to test our menu.
Since pages have no ViewModel wee need to comment-out the instruction that prepare the SPAView JavaScript model in each PagexJs.cshtml file. In each PagexJs.cshtml file locate the instruction:
and comment it out:
Done! Now you may navigate to each of the newly created SPAViews, by adding view and module parameters to the root application url that Visual Studio automatically set in you browser:
http://localhost:3291/?module=MenuExample&view=Page1
As already announced pages 3, 4, 5 must be available only for users with the role admins. Open the Views/Templates/Application/AuthorizationJs.cshtml file and add the three rules below:
In this simple example we specified access rules for each single SPAView, but in more complex applications you may write also something like: AddTo(“MenuExample”, “*”), in which case the requirements apply to all SpaViews of the “MenuExample” module. Then, exceptions to this general rule may be applied to specific SPAViews.
Now when you navigate, to Page3-5, the login form appears. Test it with Page4:
http://localhost:3291/?module=MenuExample&view=Page4
After you successfully login, Page4 appears.
Now, try to login with username: normalUser. and password: pnormalUser; you will see the following complaint page:
Wow! We need only a “smart menu” to navigate our application.
All SPAViews are loaded in the Views/Home/Index.cshtml page. The code in the above page do the following:
Declares the application ViewModel, and get a client ViewModel aware HtmlHelper<ApplicationModel>, that is then used to declare the SPAViews hosts where all the SPAViews may be loaded:
Each SPAView host is bound to an applicationModel Property. The initial application ViewModel contains just a single property, called MainContent, used by the main content SPAView host, shown above. mainAfterRender and mainbeforeRemove are applicationModel methods defined in Views/Templates/Application.MainJs.cshtml, that provide respectively content fade-in and content fade-out.
Since we need a further SPAView host for our menu we must add a new property to the application ViewModel:
Now we may define our Main Menu host. We place it on top of the Main Content:
We enclose the our main menu in a nav tag to improve the application accessibility. The Main Menu host doesn’t need fade-in, and fade-out capability, since it will never be removed, but only modified to adapt it to the context.
We need some Css to align the menu with the main content. Open the Content/Site.css file and append:
We will add the “Menu” SpaView to Basic module that is loaded as soon as the application starts Right click on the Views/Templates/Basic folder and add a new SPAView called: “Menu”:
The Data Moving control suite urnish the default SimpleMenuItem class to be used as ViewModel for each menu item. We may decide to use it, inherit from it or to use a custom class. The definition of the SimpleMenuItemClass is:
When the Menu works in a “standard” Mvc application the Link property contains either an Url or the name of a JavaScript function, preceeded either by @, or # to be executed when the menu item is clicked. Analogously the Target property contains the target where to open the Url.
In the case of a SPA we must supply a function that provides a custom interpretation of both Link and Target. In the simplest case a SPAView may be referenced with a string of the type <module name>.<view name> in the Link, while the host where to load the SPAView may be denotated by its index into an array of possible hosts.
Thus, for Instance, “MenuExample.Page4” denotes the SPAView “Page4” contained in the “MenuExample” module. If needed, the notation may be extended to include also possible roles of the SPAView, and also an input object to pass to the SPAView:
<module>.<view>.<optional role>#<optional json representation of the SPAView input>
In our simple example the custom Link+Target processing function will accept just the simple notation <module>.<view> plus standard Urls:
Standard Urls are processed first, otherwise we retrieve the SPAView specified in the action parameter from the default page store. The host where to place the SPAView is decided by interpreting the target argument as an index in the array of all application root hosts that is inserted in the _interfaces property of the Menu ViewModel.
The _interfaces property is copied in each SPAView from the interfaces property of the application ViewModel by default context rules that are run each time a SPAView is loaded. All context rules are defined in the file: Script/Modules/contextRules.js. Below the default context rule that provide to each SPAView all interfaces needed to interact with the application:
The interfaces property of the application ViewModel is defined in the Views/Templates/Application.MainJs.cshtml file. Below the default definition:
As a default the interfaces property contains just an implementation of the interface used to “block” the content of a DOM during a communication with the server. We must add also the array of all hosts (in our case there are just 2 hosts):
In the same Views/Templates/Application.MainJs.cshtml file we must modify the code that loads the initial SPAViews:
…because we must load also our menu:
The Menu SPAView is not handled by any page store, since it will never be removed from its host.
The vm.Content.virtualNavigation method definition may be placed immediately after the ViewModel initialization in the MenuJS.cshtml file:
Now we must define viewModelContent to bethe object hierachy that contains all Menu information.
Usually, the ViewModel content is initialized by serializing in JavaScript a .Net data structure, at the beginning of any …Js file:
In our case the .Net data structure is a hierarchy of SimpleMenuItem objects that we may build easily with the helper SimpleMenuBuilder class:
We added also two sub-items that navigate to external pages.
Now it’s finally time to use the Data Moving Menu control. Open the Menu.cshtml file, and substitute its content with:
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:
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 or virtual SPAView references, 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 two templates, that are configured inside two AddRowType()-EndRowType() blocks. Both templates are based on the default menu row template, since no custom row template is defined, and both of them have an unique column that shows the menu item title. Each menu item title is contained in the Text property of the underlying data item. The addition of the GenericClasses.NoWrap Data Moving predefined Css class prevents menu item titles from wrapping. The only difference between the two templates is the ChildCollection call in the second declaration, that causes the recursive rendering o all children sub items contained in the children property. The two AddRowType blocks define two knockout client templates. Their names are obtained by adding the postfixes ‘0’ and ‘1’ to the template base name “Basic_MenuItem” declared with TemplateBaseName. It is good practice to give name of the type <module name>_<a name> to all client templates used by controls declared inside SPAViews.
The first template is used for the menu items that can’t have children (their Children property is a null observable) while the second template is used for the menu items that may have children (their Children property is a possibly empty observable array). The selection of the right template is performed by the template selection function declared with TemplateSelector. In our case the function is declared on line, but it may be also the name of a SPAView ViewModel method.
CustomNavigate declares the custom navigation function we have already defined in the MenuJs file.
That’s enough to see the menu working! Run the application and enjoy your first Data Moving SPA Menu!
Now, our last objective is adapting 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 classes 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 a new SPAView is placed in the application main host. Accordingly, it may be called in the main host mainAfterRender that is defined as an applicationModel method in the Views/Templates/Application/MainJs.cshtml file:
The if checks that the module and view properties are actually defined, otherwise a null value passed to the select method simply de-selects any previously selected SPAView.
The iContextDependent.authorize method must be called each time the logged user changes. The instruction that initializes the authorization system in the Views/Templates/Application/MainJs.cshtml file accepts an array of observables, and functions that are automatically notified whenever the user changes. This is the right place for calling our authorize method:
iContextDependent.register and iContextDependent.unregister must be called in the Views/Templates/Basic/MenuJs.cshtml JavaScript file associated with our Menu SPAView.
The right place 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 after the vm.Content.virtualNavigation method we defined before:
Both methods call two private recursive functions that traverse the menu data items hierarchy to do their job.
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 recusive 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:
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 to improve performance.
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:
When a menu item get the focus the focus style overrides its “selected color” (this is due to the way bootstrap handles the focus style), if you don’t like this behavior you may change it by appending the following Css rule to the Site.css file:
Where background-color must match “selected color” that in the case of bootstrap is the “alert-info” Css class background-color.
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” file of 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.
Francesco