ASP.NET MVC Recursive TreeView Helper

3 minutes read

UPDATE 11/27/2011

Please see the updated version of this helper. It now includes a fluent API and can be added to your project very easily with NuGet.

Writing a Fluent ASP.NET MVC Recursive TreeView Helper

The following helper will make it easy to create a tree view from a recursive self-referencing table. Below you are seeing a tree of “Locations” where each Location can contain X number of child locations.

Dependencies

 jQuery TreeView Plugin

### Rendered Tree

Table Definition

The table itself is extremely simple, each Location has a ParentLocationId which is a relationship to the same table. If the ParentLocationId is null then it is a root location.

 

### Usage

Simple

<%= Html.TreeView("locations", 
    Model.Locations, 
    l => l.ChildrenLocations, 
    l => l.Name) %>

Wrapping a div around each list item

<%= Html.TreeView("dropTree", 
    Model.Locations, 
    l => l.ChildrenLocations, 
    l => "<div class='dropZone'>" + l.Name + "<div>")

Making each list item an ActionLink

<%= Html.TreeView("dropTree", 
    Model.Locations, 
    l => l.ChildrenLocations, 
    l => Html.ActionLink("MyController", "MyAction", l.Name, new { id = l.Name })

The Code

public static class TreeViewHtmlHelper
{
    /// <summary>
    /// Create a TreeView of nodes starting from a root element
    /// </summary>
    /// <param name="treeId">The ID that will be used when the ul is created</param>
    /// <param name="rootItems">The root nodes to create</param>
    /// <param name="childrenProperty">A lambda expression that returns the children nodes</param>
    /// <param name="itemContent">A lambda expression defining the content in each tree node</param>
    public static string TreeView<T>(this HtmlHelper html, string treeId, IEnumerable<T> rootItems, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent)
    {
        return html.TreeView(treeId, rootItems, childrenProperty, itemContent, true, null);
    }

    /// <summary>
    /// Create a TreeView of nodes starting from a root element
    /// </summary>
    /// <param name="treeId">The ID that will be used when the ul is created</param>
    /// <param name="rootItems">The root nodes to create</param>
    /// <param name="childrenProperty">A lambda expression that returns the children nodes</param>
    /// <param name="itemContent">A lambda expression defining the content in each tree node</param>
    /// <param name="includeJavaScript">If true, output will automatically render the JavaScript to turn the ul into the treeview</param>    
    public static string TreeView<T>(this HtmlHelper html, string treeId, IEnumerable<T> rootItems, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent, bool includeJavaScript)
    {
        return html.TreeView(treeId, rootItems, childrenProperty, itemContent, includeJavaScript, null);
    }

    /// <summary>
    /// Create a TreeView of nodes starting from a root element
    /// </summary>
    /// <param name="treeId">The ID that will be used when the ul is created</param>
    /// <param name="rootItems">The root nodes to create</param>
    /// <param name="childrenProperty">A lambda expression that returns the children nodes</param>
    /// <param name="itemContent">A lambda expression defining the content in each tree node</param>
    /// <param name="includeJavaScript">If true, output will automatically render the JavaScript to turn the ul into the treeview</param>
    /// <param name="emptyContent">Content to be rendered when the tree is empty</param>
    /// <param name="includeJavaScript">If true, output will automatically into the JavaScript to turn the ul into the treeview</param>    
    public static string TreeView<T>(this HtmlHelper html, string treeId, IEnumerable<T> rootItems, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent, bool includeJavaScript, string emptyContent)
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendFormat("<ul id='{0}'>\r\n", treeId);

        if(rootItems.Count() == 0)
        {
            sb.AppendFormat("<li>{0}</li>", emptyContent);
        }

        foreach (T item in rootItems)
        {
            RenderLi(sb, item, itemContent);
            AppendChildren(sb, item, childrenProperty, itemContent);
        }

        sb.AppendLine("</ul>");

        if (includeJavaScript)
        {
            sb.AppendFormat(
                @"<script type='text/javascript'>
                    $(document).ready(function() {{
                        $('#{0}').treeview({{ animated: 'fast' }});
                    }});
                </script>", treeId);
        }

        return sb.ToString();
    }

    private static void AppendChildren<T>(StringBuilder sb, T root, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent)
    {
        var children = childrenProperty(root);
        if(children.Count() == 0)
        {
            sb.AppendLine("</li>");
            return;
        }

        sb.AppendLine("\r\n<ul>");
        foreach (T item in children)
        {
            RenderLi(sb, item, itemContent);
            AppendChildren(sb, item, childrenProperty, itemContent);
        }

        sb.AppendLine("</ul></li>");
    }

    private static void RenderLi<T>(StringBuilder sb, T item, Func<T, string> itemContent)
    {
        sb.AppendFormat("<li>{0}", itemContent(item));            
    }
}

Updated:

Leave a Comment