Menu and MenuFor

The Menu and MenuFor render a tree of objects as a tree of nested <ul> and <li> Html nodes. By applying adequate CSS styling to this Html structure several type of menus can be implemented, from left bar to drop-down menus. All nodes of the object tree must belong to the same class T, but the appearence of each <li> item can be customized by specifying a Template strongly typed with the type T.

The Html created is something like this:

 

    <ul  class="navigationMenu" >
        <li class='has-children selected-path'>
            <!-- Template -->
                <ul>
                    <li >
                        <!-- Template -->
                    </li>
                    <li class='item-selected'>
                        <!-- Template -->
                    </li>
                </ul>
        </li>
        <li class='has-children'>
            <!-- Template -->
            <ul>
                <li class='has-children'>
                    <!-- Template -->
                    <ul>
                        <li >
                            <!-- Template -->
                        </li>
                        <li >
                            <!-- Template -->
                        </li>
                    </ul>
                </li>
                <li >
                    <!-- Template -->
                </li>
            </ul>
        </li>
        <li >
            <!-- Template -->
        </li>
     </ul>

 

Where:

  • navigationMenu is the name of a CSS class that the developer can specify through the htmlAttributes parameter of the Menu, or MenuFor helpers. It is the base for stlyling all Html nodes contained in the menu
  • <!-- Template --> is the Html created by the Template specified by the developer.
  • The has-children CSS class is applied to all nodes having chidren, so that they can be styled differently, if needed.
  • The item-selected class is applied to the selected item. The selected element is computed with the help of of a function Func<HtmlHelper, T, bool> passed as argument to the Menu or MenuFor helpers. This function is evaluated in each object of the object tree, by passing it the HtmlHelper object used to call the Menu or MenuFor extension methods, and the current object. If the function returns true the item is considered selected. This way, the selected item can be styled differently.
  • The selected-path class is applied to all <li> nodes on the path to the selected node. This way they can be styled differently. For instance the menu items of a drop down menu on the path to the selected element can be kept opened.

The signature of the Menu extension method is:

 

public static MvcHtmlString Menu<T>(this HtmlHelper htmlHelper,
string name,
IEnumerable<T> collection,
IDictionary<string, object> htmlAttributes = null,
Expression<Func<T, IEnumerable<T>>> itemCollection = null,
object itemTemplate = null, Func<HtmlHelper, T, bool> selected=null,
Func<T, int> templateChoice = null,
string selectedClass = "item-selected",
string selectedPathClass = "selected-path",
string hasChildrenClass = "has-children",
Func<T, string> allItemsClass = null, string innerUlClass =null,
Func<T, string> liAttributesString = null)
where T: class, new()

 

Where:

  • name, is the name of the menu. It will be used as name prefix for possible input fields contained in the menu.
  • collection is tree of objects of type T specified by passing the first level of the tree.
  • htmlAttributes, as usual, contains Html attributes, that are applied to the <ul> at the root of the Menu.
  • itemColletion is a Lambda Expression that specifies the property that contains all children of a node of the object tree.
  • itemTemplate is the node Template. It can be also an array of templates (see templateChoice)
  • selected is the function to compute the selected item.
  • templateChoice, is used just when itemTemplate is an array of templates. It is a function that select the index of the template of itemTemplate to be used to render the current menu item given the current menu item.
  • selectedClass, is the css class that is added to the currently selected menu item (if any).
  • selectedPathClass, is the css class added to all menu items on the path to the currently selected item.
  • hasChildrenClass, is a css class that is applied to all menu items that have sub-items.
  • allItemsClass, when provided is  a function that returns a css class that is added to the <li> that renders the current menu item.
  • innerUlClass, is a css class that is applied to all <ul> but theroot of the menu.
  • liAttributeString, when provided, is a function that returns a string with all html attributes to be applied to the <li> that renders the current menu item.

If selected is null no selected item is computed and consequentely the item-selected and selected-path attributes are not applied to any node. 

itemCollection and itemTemplate can be null only if T is the pre-defined class SimpleMenuItem, in all other cases an ArgumentNullException will be thrown.

The SimpleMenuItem class is defined as below:

 

public class SimpleMenuItem
    {
        public string Text { get; set; }
        public string Link { get; set; }
        public string UniqueName { get; set; }
        public string Target { get; set; }
        public List<SimpleMenuItem> Children { get; set; }
    }

 

If one uses this class to build a menu there is no need to specify collection containing all children objects through the itemCollection parameter since it is just the children property. Moreover, if no template is provided a standard template consisting of an <a> node with href=Link, target=Target and content equal to Text is used.

The UniqueName property is provided to give to each Action Method the opportunity to specify the selected node by putting the UniqueName of the selected item either in the ViewModel or in the ViewData. Often, the selected element can be computed automatically through the request url and the Link property of the object as shown in the example included with the binary distribution.

 The MenuFor extension can be used if the tree  of object is contained in a ViewModel (for sure it cannot be used if the menu is defined directly in a Master page). Its signature is:

 

public static MvcHtmlString MenuFor<M, T>(
this HtmlHelper<M> htmlHelper, 
Expression<Func<M, IEnumerable<T>>> rootCollection,
IDictionary<string, object> htmlAttributes = null,
Expression<Func<T, IEnumerable<T>>> itemCollection = null,
object itemTemplate = null,
Func<HtmlHelper, T, bool> selected = null,
Func<T, int> templateChoice = null, 
string selectedClass = "item-selected",
string selectedPathClass = "selected-path",
string hasChildrenClass = "has-children",
Func<T, string> allItemsClass = null, 
string innerUlClass = null, 
Func<T, string> liAttributesString = null)
where T : class, new()

 

The tree of object needed to define the Menu can be stored in a DB Table having a one to many relation with itself that specifies the children of each item.

To read the whole tree it is enough something like:

 

var allNodes= (from x in context.MyTable select x).ToList();

 

 

The above statement loads all nodes from the DB. Then, allNodes.Where(x => x.Parent == null) returns all top level tree nodes to be passed to the Menu extension method.

Otherwise, the tree of objects can be bult with the TreeBuilder<T> object. The constructor of the  object is passed the Lambda Expression Expression<Func<T, List<T>>>  that defines the collection of children of each object.

Then, the whole tree of objects can be built with with an unique sequence of chained calls to the following methods:

 

public TreeBuilder<T> Add(T item)

 

or

 

public TreeBuilder<T> Add(bool enabled, T item)

 

That adds one after the other all children of the current node. If enabled is false, item, and all its children added with further instructions is not added.

 

public TreeBuilder<T> SubItems()

 

 

That turns the last children added into the current node, thus going down in the tree.

 

public TreeBuilder<T> FatherItem()

 

That go back to the previous current node, thus going up in the tree.

Finally:

 

public List<T> Get()

 

returns the tree we have built.

 

If  the  SimpleMenuItem class is enough for us we can build the tree with the more easier to use SimpleMenuBuilder class that inherits from the TreeBuilder<SimpleMenuItem> class:

  • Add(string Text, string link = null, string uniqueName=null) or Add(enabled, string Text, string link = null, string uniqueName=null): adds a new child.
  • Down(): go down in the tree.
  • Up(): go up in the tree.
  • Get(): returns the tree we have built.

if enabled is false the current node and possible children defined with Down() are not added.

Below an example of use of the SimpleMenuBuilder class:

 

var menuTree = new SimpleMenuBuilder()
                    .Add("This Site")
                    .Down()
                        .Add("Home", Url.Action("Index", "Home"))
                        .Add("Registration", Url.Action("Register", "Account"))
                    .Up()
                    .Add(false, "Usefull Links")
                    .Down()
                        .Add("javascript")
                        .Down()
                            .Add("jQuery", "http://jquery.com/")
                            .Add("jQuery UI", "http://jqueryui.com/")
                        .Up()
                        .Add("Asp.Net MVC", "http://www.asp.net/mvc")
                    .Up()
                    .Add("About", Url.Action("About", "Home"))
                    .Get();

 

Since before "Usefull Links" we put enabled false, it and all items below it ("javascript", "jQUery", "jQUeryUI", etc.) are not included in the tree. 

Once built the object tree we can call the Menu extension method:

 

Html.Menu("MainMenu", menuTree, new {@class="navigationMenu"}, 
                    selected: (h, i) =>  i.Link != null && HttpContext.Current.Request.Url.AbsolutePath == i.Link)

 

Please note how we compute the selected item. In more complexes cases we might need to consider also the values of the parameters string of the request url. This technique solves quite easily the problem in most of the cases, but there are cases where it is simpler to use the UniqueName property to let the Action Mthod declare explicitely the selected element.

 

The Menu is a display only control. We can also enable the user to modify the menu in an administrative page, by using a TreeView to edit or build  interactively the object tree that defines the Menu. Then, the object tree can be stored in a Database Table having a one to many relation with itself that specifies the children of each item or in an Xml document.

 

Last edited Jun 22, 2014 at 10:49 AM by frankabbruzzese, version 23

Comments

No comments yet.