views:

47

answers:

2

I have been asked to update the menu on a website we maintain. The website uses Castle Windors Monorail and NVelocity as the template. The menu is currently rendered using custom made subclasses of ViewComponent, which render li elements. At the moment there is only one (horizontal) level, so the current mechanism is fine.

I have been asked to add drop down menus to some of the existing menus. As this is the first time I have seen Monorail and NVelocity, I'm a little lost.

What currently exists:

<ul>
    #component(MenuComponent with "title=Home" "hover=autoselect" "link=/")
    #component(MenuComponent with "title=Videos" "hover=autoselect")
    #component(MenuComponent with "title=VPS" "hover=autoselect" "link=/vps")                                   
    #component(MenuComponent with "title=Add-Ons" "hover=autoselect" "link=/addons")                    
    #component(MenuComponent with "title=Hosting" "hover=autoselect" "link=/hosting")                           
    #component(MenuComponent with "title=Support" "hover=autoselect" "link=/support")                           
    #component(MenuComponent with "title=News" "hover=autoselect" "link=/news")
    #component(MenuComponent with "title=Contact Us" "hover=autoselect" "link=/contact-us") 
</ul>

Is it possible to have nested MenuComponents (or a new SubMenuComponent) something like:

<ul>
    #component(MenuComponent with "title=Home" "hover=autoselect" "link=/")
    #component(MenuComponent with "title=Videos" "hover=autoselect")
    #blockcomponent(MenuComponent with "title=VPS" "hover=autoselect" "link=/vps")                                  
        #component(SubMenuComponent with "title="Plans" "hover=autoselect" "link=/vps/plans")
        #component(SubMenuComponent with "title="Operating Systems" "hover=autoselect" "link=/vps/os")
        #component(SubMenuComponent with "title="Supported Applications" "hover=autoselect" "link=/vps/apps")
    #end
    #component(MenuComponent with "title=Add-Ons" "hover=autoselect" "link=/addons")                    
    #component(MenuComponent with "title=Hosting" "hover=autoselect" "link=/hosting")                           
    #component(MenuComponent with "title=Support" "hover=autoselect" "link=/support")                           
    #component(MenuComponent with "title=News" "hover=autoselect" "link=/news")
    #component(MenuComponent with "title=Contact Us" "hover=autoselect" "link=/contact-us") 
</ul>

I need to draw the sub menu (ul and li elements) inside the overridden Render method on MenuComponent, so using nested ViewComponent derivatives may not work. I would like a method keep the basically declarative method for creating menus, if at all possible.

edit: I can use Context.RenderBody() to render the nested ViewComponent derivitives, but they're being rendered before the parent. I guess the rending of the sub menus need to somehow hook into the same output as the parent?

A: 

My original render method looked like

public override void Render()
{
    var buffer = new StringBuilder();           
    var extraClass = _hoverState == MenuHoverState.Selected ? "class=\"Selected\"" : "";

    // Menu Item Start
    buffer.Append("<li><a href=\"" + ComponentParams["link"] + "\"" + extraClass + ">");

    // Menu Text
    buffer.Append(ComponentParams["title"]);

    // Menu Item End
    buffer.Append("</a></li>");                     
    RenderText(buffer.ToString());
}

I needed to hook into the Context.Writer:

public override void Render()
{
    var buffer = new StringBuilder();           
    var extraClass = _hoverState == MenuHoverState.Selected ? "class=\"Selected\"" : "";

    // Menu Item Start
    buffer.Append("<li><a href=\"" + ComponentParams["link"] + "\"" + extraClass + ">");

    // Menu Text
    buffer.Append(ComponentParams["title"]);

    // Menu Item End
    buffer.Append("</a><ul class=\"subMenu\" style=\"display:none;\">");                        
    Context.Writer(buffer.ToString());
    Context.RenderBody(Context.Writer);
    Contet.Writer("</ul></li>");    
}
rob_g
A: 

I can use Context.RenderBody() to render the nested ViewComponent derivitives

in your Render method override, you could use something like

RenderView("header");
RenderBody();
RenderView("footer");

and maybe use RenderSection could be useful to be able to override some parts from the template you use the component

if(HasSection("header")){
    RenderSection("header");
} else {
    RenderView("header");
}

it is also possible to itterate and alter the context:

for(var item in this.SubItems){
    PropertyBag["item"] = item;
    if(HasSection("item")){
        RenderSection("item");
    } else {
        RenderView("item");
    }
}

all these solution are fancy, but I generaly prefer having a viewcomponent which takes a purpose specific viewmodel (say HierarchicalMenuViewModel) as a parameter and keep the templating logic simple, it's more easy to use, and output customization happen

at least for simple controls (that would sometime only deserve a macro or partial depending on the viewengine).

In the end, viewcomponent concepts illustrated above are still nice to have when doing control that need more customization. An advice is to take care of documenting the rendering logic or keeping it simple (<= 10 lines in render method)

smoothdeveloper