4
votes

Has anyone written any HTMLHelper classes for MVC that help with Yahoo's User Interface Library?

For instance I have written a helper method to convert a 'menu model' into the HTML markup needed to support the Yahoo Menu Control. The MVC pattern works well here because obviously if I chose to switch to a different menu implementation I can just write a new helper and not touch the model.

This code works for me but isn't fully tested and you're welcome to use it.

First we need a simple data structure for the menu model itself. You would add this to your page model with the normal MVC conventions. For instance I access a list of menu items from my view via ViewData.Model.MainMenu.MenuOptions.

public class MenuItem

{
    public string Text { get; set; }
    public string Description { get; set; }
    public string RouteURL { get; set; }
    public bool SeparatorBefore { get; set; }

    public List<MenuItem> MenuItems { get; set; }
}

Extension method. Put in a namespace that is accessible to your view.

public static class YUIExtensions
    {
        public static string RenderMenu(this HtmlHelper html, string id, List<MenuItem> menuItems)
        {
            // <div id="mnuTopNav" class="yuimenubar yuimenubarnav">
            //    <div class="bd">
            //        <ul class="first-of-type">

            //            <li class="yuimenubaritem first-of-type"><a class="yuimenubaritemlabel" href="#store">Store</a></li>

            //            <li class="yuimenubaritem"><a class="yuimenubaritemlabel" href="#products">Products</a>

            //                <div id="communication" class="yuimenu">
            //                    <div class="bd">
            //                        <ul>
            //                            <li class="yuimenuitem"><a class="yuimenuitemlabel" href="http://360.yahoo.com">360&#176;</a></li>
            //                            <li class="yuimenuitem"><a class="yuimenuitemlabel" href="http://mobile.yahoo.com">Mobile</a></li>
            //                            <li class="yuimenuitem"><a class="yuimenuitemlabel" href="http://www.flickr.com">Flickr Photo Sharing</a></li>
            //                        </ul>
            //                    </div>
            //                </div>     
            //            </li>

            //        </ul>            
            //    </div>
            //</div>   

            int menuId = 0;
            HtmlGenericControl menuControl = CreateControl(html, id, 0, ref menuId, menuItems);

            // render to string
            StringWriter sw = new StringWriter();
            HtmlTextWriter tw = new HtmlTextWriter(sw);
            tw.Indent = 1;
            menuControl.RenderControl(tw);
            return sw.ToString();
        }

        private static HtmlGenericControl CreateControl(HtmlHelper html, string id, int level, ref int menuId, List<MenuItem> currentItems)
        {
            var menu = new HtmlGenericControl("div");
            menu.Attributes["class"] = (level == 0) ? "yuimenubar yuimenubarnav" : "yuimenu";
            menu.Attributes["id"] = id;

            var div_bd = new HtmlGenericControl("div");
            menu.Controls.Add(div_bd);
            div_bd.Attributes["class"] = "bd";

            HtmlGenericControl ul = null;

            int i = 0;
            foreach (var menuItem in currentItems)
            {
                if (ul == null || menuItem.SeparatorBefore)
                {
                    ul = new HtmlGenericControl("ul");
                    div_bd.Controls.Add(ul);

                    if (i == 0)
                    {
                        ul.Attributes["class"] = "first-of-type";
                    }
                }

                var menuItem_li = new HtmlGenericControl("li");
                menuItem_li.Attributes["class"] = (level == 0) ? "yuimenubaritem" : "yuimenuitem";
                if (i == 0)
                {
                    menuItem_li.Attributes["class"] += " first-of-type";
                }
                ul.Controls.Add(menuItem_li);

                var href = new HtmlGenericControl("a");
                href.Attributes["class"] = (level == 0) ? "yuimenubaritemlabel" : "yuimenuitemlabel";
                href.Attributes["href"] = menuItem.RouteURL;
                href.InnerHtml = menuItem.Text;
                menuItem_li.Controls.Add(href);

                if (menuItem.MenuItems != null && menuItem.MenuItems.Count > 0)
                {
                    menuItem_li.Controls.Add(CreateControl(html, id + "_" + (menuId++), level + 1, ref menuId, menuItem.MenuItems));
                }
                i++;
            }

            return menu;
        }
    }

Stick this code where you want to generate the menu in your view (I have this in a master page):

<%= Html.RenderMenu("mnuTopNav", ViewData.Model.MainMenu.MenuOptions) %>

If you're lazy, or don't know about YUI you'll need this too in your <HEAD>

<!-- Combo-handled YUI CSS files: -->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.6.0/build/menu/assets/skins/sam/menu.css">
<!-- Combo-handled YUI JS files: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/container/container_core-min.js&2.6.0/build/menu/menu-min.js"></script>

This currently generates markup for top nav style navigation bar - but it could be easily modified.

I was hoping somebody else was doing the same for some of the other controls.

Seems like a good candidate for an open source project - but I dont have time to start that.

Implementation advice welcomed!

2

2 Answers

3
votes

Last night I did some thinking about this and am wondering if there's even more opportunity here to make general purpose HTMLHelpers using YUI or whatever other Javascript/HTML widgets you want.

For instance, if there was an interface for IMenu and one for ITextBox, ICheckBox, IRichTextEditor, ICarousel, etc. much like your class for a MenuItem, then you could have a YUI implementation of each of those interfaces, one for JQuery, one for MooTools or one for just straight HTML/CSS.

Part of what sparked this is the generalization that articles like this: http://designingwebinterfaces.com/essential_controls are taking to UI controls on the web for rich web apps.

Those interfaces would contain all of the basic stuff that is obvious at first glance: Id, Name, Value, List, Style, OnChange, OnClick, etc. as well as less obvious stuff like ValidationRegex, HelpText, etc.

That would let you have a layer that converts a model object or model property into an ITextBox and not worry about which one of the implementations of the interface will actually be handling it. You could also easily switch to a new implementation if one came along that was better/faster/cooler.

You'd have to deal with what should happen if you give something like ValidationRegex to a barebones HTML implementation and it has no way to deal with it, but I think it's a path worth thinking about. I also think it might make more sense to implement this as separate from the existing HTMLHelper namespace by inheriting it or just reimplementing it, but I am often wrong at this kind of early idea stage of coming up with a solution.

The YUIAsp.NET stuff is interesting, but is more oriented to WebForms and user controls than the direction that ASP.NET MVC and even moreso with Fubu MVC recently are going.

I tinkered with this idea a little bit and am really intrigued with the possibilities.

2
votes

Simon,

I'm not sure this is helpful with respect to the MVC question, but there is a good open-source project that aims to simplify working with YUI within .NET:

http://www.yuiasp.net/

Menu is one of the controls that they include.

At the very least, this may be a project you can contribute back to if your work adds a new dimension to what's already there.

-Eric