Hide submenu if no childitems

Topics: Core, Customizing Orchard, Nederlands (Dutch)
May 17, 2013 at 2:39 PM
Hi all,

I'd like to know if it is possible to NOT render a Menu Widget if the Menu doesn't have items. This is a submenu that only shows childitems.

Inside my Layout.cshtml I'd like to render a different layout when there are submenu items and another layout if there aren't any submenu items.

Is this possible from the Layout.cshtml?

Thanks,
Daniel
May 17, 2013 at 6:06 PM
Edited May 17, 2013 at 6:06 PM
plompd wrote:
I'd like to know if it is possible to NOT render a Menu Widget if the Menu doesn't have items. This is a submenu that only shows childitems.
Im not sure its possible from Layout.cshtml but to stop the widget section from rendering I would make a new module and add a ContentHandler and some settings for the menu widget part.
public class NavWidgetPartHandler : ContentHandler
    {
        public NavWidgetPartHandler(IMenuService menuService, INavigationManager navigationManager)
        {
            OnGetDisplayShape<MenuWidgetPart>((context, part) => {

                var menu = menuService.GetMenu(part.Menu.Id);
                var menuItems = navigationManager.BuildMenu(menu).ToList();

                if (menuItems.Count == 0) 
                {
                    var widgetPart = part.As<WidgetPart>();
                    //place the widget somewhere else 
                    widgetPart.Zone = "None";
                }
            }); 
        }
    }
I tested this quickly with the default "theme machine" theme and added the widget to the left aside. When there is more than one item the menu will show as normal.
When there are no items it will place the shape to render in "None" which doesnt exist. This made the content section cover the entire width as normal again.

To better customize it I would create some part settings for MenuWidgetPart to check if this behavior was wanted.

Is this the kind of thing you wanted? It probably could be written to be a bit more efficient ... but as a proof of concept ;-)
Coordinator
May 17, 2013 at 6:16 PM
Or override the Menu.cshtml in your theme to do the switch.
May 17, 2013 at 7:42 PM
Thanks for the responses.
I'm quite new to building my own modules, but I'm going to look into that.

@Sebastian: I did override the Menu in my theme. The problem was I couldn't find a way to also override the MenuItem and MenuItemLink for my second menu, because it would change the toplevel menu and the main level menu. So I took another approach. I

I think what Matt explains is almost what I want: don't render the menu if there are no childitems, but also change the div classes in the layout to have another span (since I'm building on a responsive design).

-Daniel
May 17, 2013 at 8:12 PM
sebastienros wrote:
Or override the Menu.cshtml in your theme to do the switch.
that is easier, and may suit plompd needs but....
Sebastien - If you need the widget wrapping (and potentially a bit around the zone) to drop which is created due to the Menu Widget Content Type being placed there then how would you go about doing that? As above or some other clever way? As overriding the Menu.cshtml will only be one of the shapes.

-- zone
---- widget
------ menu

An alternate for Widget-MenuWidget would be getting closer but getting the menu item count is quite a bit trickier. Still the zone will decorate the layout when this is called:
@if (Model.AsideFirst != null) {
    <aside id="aside-first" class="aside-first group">
        @Zone(Model.AsideFirst)
    </aside>
}
Some good css rules may solve things too but i feel i may be missing some more obvious.
May 17, 2013 at 8:17 PM

I now have it setup like this inside my Layout, which works great when using e.g. a Html Widget:

// Check if null
var displayContent = (Model.Content != null);
var displayAside = (Model.Aside != null);

// How to check if the current menu option holds childitems here?
======
@if (displayContent && displayAside) {
            <div class="container">
                <div class="row">
                    @if (displayAside != null) {
                        <div class="span3">
                            @Display(Model.Aside)
                        </div>
                    }
                    <div class="span9">
                        @Display(Model.Content)
                    </div>
                </div>
            </div>
        } else {
            <div class="container">
                <div class="row">
                    <div class="span12">
                        @if (Model.Content != null) {
                            @Zone(Model.Content)
                        }
                    </div>
                </div>
            </div>
        }
So, not sure about it.

-Daniel
May 17, 2013 at 8:25 PM
Edited May 17, 2013 at 8:35 PM
plompd wrote:
The problem was I couldn't find a way to also override the MenuItem and MenuItemLink for my second menu, because it would change the toplevel menu and >the main level menu.
Well there is a way, not sure how good of a way ... Taken from a similar example in bootstrap, and modified a bit to suit me:

Menu.cshtml:
@using Orchard.ContentManagement;
@using Orchard.Widgets.Models;

@{
    // Model is Model.Menu from the layout (Layout.Menu)
    var tag = Tag(Model, "ul");

    var items = (IList<dynamic>)Enumerable.Cast<dynamic>(Model.Items);

    if (items.Any()) {
        items[0].Classes.Add("first");
        items[items.Count - 1].Classes.Add("last");
    }
}
@if (Model.Items.Count == 0) { 
    <p>No items.</p>
}else{
<nav>
    @tag.StartElement
        @* see MenuItem shape template *@
        @foreach (var menuItem in Model) 
        {
            @* but change the shape type that is loaded *@
            menuItem.Metadata.Type = "SecondMenuItem"; //any name for your shape here. 
            @Display(menuItem)
        }

    @tag.EndElement
</nav>
}
The MenuItem will expect the template based on the type "SecondMenuItem". Creating a new branch of possibilities.

SecondMenuItem.cshtml (which will need to be in the theme views folder)
@{
    // odd formatting in this file is to cause more attractive results in the output.
    var items = Enumerable.Cast<dynamic>((System.Collections.IEnumerable)Model);

    string requestUrl = Request.Path.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
    string modelUrl = Model.Href.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
    
}
@{
if (!HasText(Model.Text)) {
    @DisplayChildren(Model)
} else {
    if (requestUrl == modelUrl || (!string.IsNullOrEmpty(modelUrl) && requestUrl.StartsWith(modelUrl + "/"))) { 
        Model.Classes.Add("active");
    }
    if(items.Any()) {
        Model.Classes.Add("dropdown");
    }
    var tag = Tag(Model, "li");
    @tag.StartElement
            
    @* morphing the shape to keep Model untouched*@
    Model.Metadata.Alternates.Clear(); 
    if (items.Any()) {
        Model.Metadata.Type = "MenuItemLinkdd"; //change again for new template - currently representing a drop down
    }
    else {
        Model.Metadata.Type = "MenuItemLink"; //change for template 
    }
    @Display(Model)
    
    if (items.Any()) {
        <ul class="dropdown-menu">
            <li>
                @{ 
                    Model.Metadata.Type = "MenuItemLink";
                    Model.Text = Model.Text + " - " + T("Show All").Text; //for the taxonomy menu. I want to show all items by term and the term may have children. 
                }
                @Display(Model)
            </li>
            @DisplayChildren(Model)
        </ul>
    }
    @tag.EndElement
    }
}
MenuItemLinkdd.cshml:
<a class="dropdown-toggle" data-toggle="dropdown" href="#">@Model.Text<b class="caret"></b></a>
MenuItemLink
<a href="@Model.Href">@Model.Text</a>