Building multi-level menu for Orchard
I was developing Orchard modules from nearly the very beginning. Design choice for the project I’m currently working on - to use Orchard as a base framework – pushed me into this direction and I have to admit that it was a very good choice. But… As with every project in it’s infancy there are lots of obstacles you have to jump over. One of the first I had to deal with was the menu…
By default, Orchard delivers only a single-level menu – too bad if you want to build a larger site:/ But… There’s nothing impossible if you are a developer at heart:) - if it’s not available out-of-the-box you have to build one yourself! And so the adventure started…
Orchard docs tell you straight how to build a module – everything you need to start is there. What I had to do was to grab the latest source, build it and scaffold the default module structure via command-line code generation tool. And then the documentation ends… So, how to replace the default menu? To answer this question I had to look into the framework code. And that’s what I found:
- To add your own menu items you have to create your own implementation of INavigationProvider interface. It looks like that:
public interface INavigationProvider : IDependency { string MenuName { get; } void GetNavigation(NavigationBuilder builder); } - I found out that MenuName has to return value “main” (by looking into Orchard.Core.Navigation.Services.MainMenuNavigationProvider class, which is the default menu provider) and the GetNavigation method has to call the provided NavigationBuilder builder.Add(…) method to create the appropriate menu.
- Orchard will automatically discover my implementation and pass it to the object which creates the menu (I’ll leave the details for next article).
Simple, isn’t it? Implement one interface and voila! You have your menu. At first sight it may look like this, but the toughest part had just begun…
The default menu provider creates a simple, flat menu, by fetching the existing MenuPart objects and iteratively calls builder.Add() method to create the menu. Ok, but I want a hierarchy! In Orchard, every menu item has a property Position which holds an information about its position. You can specify hierarchy by using dot-notation (1, 1.1, 1.1.1, 1.1.2 and so on), but the default menu provider simply ignores it although the NavigationBuilder object has the ability to create nested items. Just have to build a tree-like call: builder.Add(nb => nb.Add(nb => nb.Add(…))). to have my menu. You can pass a lambda expression (or any delegate that takes one argument of type NavigationItemBuilder) to the Add method, call Add() method of provided object inside the lambda, and so on. But how the hell I’m supposed to create nested lambda expressions from database-fetched data!?
I recalled my CS course and a well-known simple data structure – prefix tree (or trie). It fits perfectly! Half an hour of writing, testing and got it working.
Summarizing, I now had to fetch menu items from the database just like in the default, and add them one-by-one to the trie. Done. Next step was to use some recursion (menus are usually not deep, so I didn’t bother about the recursion drawbacks) to build the appropriate nested lambda expression to pass it to the builder.Add() method. Done. It was my intention not to get into much detail here, as the most important (for Orchard module developer) part hasn’t happened yet…
Ok, I have my menu provider, it creates the hierarchy, but why the hell the old default menu provider is still working!?!?!
This question and trying to find a workaround worried me for a couple of days… Why? There is a class called Orchard.UI.Navigation.NavigationManager. This is the one that is responsible for building menus and it takes as an input all of the INavigationProvider implementations found in IoC container (or more specific – IEnumerable of INavigationProvider)! So the default is taken into account too… That is bad:/ Basically, it takes all INavigationProviders implementations with a given MenuName property and merges their outputs. And that is why my menu had items duplicated (the top level of my menu was merged with the default).
I had to figure out how to get rid of the old main menu provider so that the NavigationManager doesn’t get it.
This part involves some Autofac knowledge. This is an IoC container used by Orchard under-the-hood. If you are not used to working with Autofac, I’d recommend reading the introduction and the part about modules first.
Underlying Autofac container allows developers to plug-in to the registration pipeline. How? Use Autofac modules! First of all you have to create your own module by extending the Autofac.Module class and override the AttachToComponentRegistration method. Orchard will automagically find your implementation and register your module in Autofac, so you don’t have to bother about that![]()
Inside AttachToComponentRegistration method you can modify any registration you want (eg. changing object’s lifetime, subscribe to events, provide custom object implementation…). Too bad you cannot unregister types previously registered in the Autofac container….:// But wait, didn’t I say that you can “provide custom object implementation”? Maybe if I cannot remove the old menu provider registration maybe I’d replace it with an empty stub? And that’s my final solution that made the menu work – the details below:
public class HierarchicalMenuModule : Module { protected override void Load(ContainerBuilder builder) { } protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) { if (typeof (MainMenuNavigationProvider).IsAssignableFrom(registration.Activator.LimitType)) { registration.Activating += (obj, e) => e.Instance = new EmptyMainMenuNavigationProvider(); } } [UsedImplicitly] protected class EmptyMainMenuNavigationProvider : INavigationProvider { public string MenuName { get { return "main"; } } public void GetNavigation(NavigationBuilder builder) { } } }
Hooray!:D
The menu is freely available for download from Orchard Gallery, where I happily contributed it:)
I’m in the process of creating series of articles about more advanced Orchard module development. If you are interested I’d encourage you to take a look at my blog in couple of days!
Cheers!
Comments have been disabled for this content.




10 Comments
akz said
Thank's useful post. But I'm still can't use your menu. It doesn't appear... Only base.
pszmyd said
Hi! This menu replaces the default one. In the basic scenario, when you have no sub-items set (the Position propert of each items is just 1, 2, 3 and so on) everything is displayed exactly like in default menu (flat). Could you please provide more info?
Cheers, Piotr
akz said
Yes, everything fine with simple menu, I just can't get any sub-items... Sorry for this problem, it feels like a dumb question...(And sorry for my English, my Russian is much better)
sid said
Hi, Piotr. I'm using your menu with the default theme. The submenu appears static below it's parent menu. It doesn't look nice. Can you provide an image of the menu you made the way you intend for it to appear?
I've just started getting into Orchard. please continue writing Orchard modules and writing about them in your blog. Thanks.
pszmyd said
Hi!
I'm currently working to deliver the next menu release + the corredsonding docs. I'll write the section about styling and give some examples. In short - you just need to add the appropriate CSS and/or JS (eg. some jQuery). There are lots of examples on the web of how to do it. In the incoming documentation I'll present some of the most popular approaches.
Unfortunately the default Orchard themes don't take into account the nested items, what makes them look bad at start:/
m][ke said
Hi Piotr,
Nice work!
A question: Have you given the localization any thought? I.e to the the actual menuItemsText in different languages?
pszmyd said
@M][ke: Thanks! Yes, the menu items are localizable just like the items in the default menu.
If the item text matches and entry in the corresponding *.po files for a given culture than it'll be translated when you set Orchard current culture to that one.
pszmyd said
Hi!
I've just released the new version of Hierarchical menu module. Apart from bugfixes it contains lot of new features!
http://www.szmyd.com.pl/blog/new-version-of-orchard-hierarchical-menu-just-released
Hope you enjoy it!
akz said
Hi, again.
You said: "If the item text matches and entry in the corresponding *.po files for a given culture than it'll be translated when you set Orchard current culture to that one."
What is "item, text"? - Title?
After I get your menu my localization falls... But widgets are great! Thanks!
pszmyd said
@akz: I meant the "Text" property of menu item (as you can see in Navigation pane).
Could you describe what exactly happens? I'll try to fix that.