2

I'm trying to extrapolate and transfer some answered questions on recursion into one of my projects.

Answered question on recursion that I'm referencing:
asp-net-mvc-4-generating-a-treeview-with-recursive-partial-view

However, I'm having trouble grasping the concept and applying it in my project.

Here is my attempt.

First, I created a class called Models:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MVCMnetWebsite.Models
{
  public class Category
  {
    public int TAB_ITEM_ID { get; set; }
    public string TAB_ITEM_NAME { get; set; }
    public int? TAB_ITEM_PARENT_ID { get; set; }
    public string URL { get; set; }
    public string WINDOW_NAME { get; set; }
    public string TOOL_TIP { get; set; }
    public string ACCESS { get; set; }
    public DateTime? START_DATE { get; set; }
    public DateTime? END_DATE { get; set; }
    public int? LASTUPDATE_BY { get; set; }
    public int? SNAC_TYPES_OBJID { get; set; }
    public byte? ACTIVE { get; set; }
    public string ICON { get; set; }
}

public class SeededCategories
{
    public int? Seed { get; set; }
    public IList<Category> Categories { get; set; }
}

}

Then, I created a controller called TreeController:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCMnetWebsite.Models;

namespace MVCMnetWebsite.Controllers
{
  public class TreeController : Controller
  {
    public ActionResult Index()
    {
        var categories = Session["_menu"] as IList<Category>;
        SeededCategories model = new SeededCategories { Seed = null, Categories = categories };
        return View(model);
    }    
  }
}

Then, I created a partial view called _TreeCategories:

@model MVCMnetWebsite.Models.SeededCategories
@using MVCMnetWebsite.Models
@{var menuList = Session["_menu"] as List<Category>;}
@{int appID = 72;} @*PKEY OF APP - LOCATED: [INTRAWEB].[dbo][DATA_SN_ACCESS_CONTROL_TYPES]*@

@foreach (var topMenuList in menuList.Where(x => x.TAB_ITEM_PARENT_ID == null && x.SNAC_TYPES_OBJID == appID).Select(x => x).ToList())
{
<li>
    <a href="#"><span><i class="@topMenuList.ICON"></i>  @topMenuList.TAB_ITEM_NAME</span></a>
    <ul>
        @foreach (var subMenuList in menuList.OrderBy(x => x.TAB_ITEM_NAME).Where(x => x.TAB_ITEM_PARENT_ID == topMenuList.TAB_ITEM_ID).Select(x => x).ToList())
        {
            if (menuList.Any(x => x.TAB_ITEM_PARENT_ID == subMenuList.TAB_ITEM_ID))
            {
                <li>
                    <a href="#"><span><i class="fa fa-toggle-right"></i> @subMenuList.TAB_ITEM_NAME</span></a>
                    <ul>
                        @foreach (var subChildMenuList in menuList.Where(x => x.TAB_ITEM_PARENT_ID == subMenuList.TAB_ITEM_ID).Select(x => x).ToList())
                        {
                            <li class="urlLink">
                                <a name="@subChildMenuList.URL" href="">@subChildMenuList.TAB_ITEM_NAME</a>
                            </li>
                        }
                    </ul>
                </li>
            }
            else
            {
                <li class="urlLink"><a name="@subMenuList.URL" href="">@subMenuList.TAB_ITEM_NAME </a></li>
            }
        }
    </ul>
</li>
}

and in my _layout.cshtml, I have:

@Html.Partial("_TreeCategories", Model)

From the code, shown above, I can only show items up to the 2nd child. How do I use recursion to show items up to the Nth child?

EDITED:

@if (Model.Categories.Where(s => s.TAB_ITEM_PARENT_ID == Model.Seed).Any())
{
  <ul>
    @foreach (var node in Model.Categories)
    {
        if (node.TAB_ITEM_PARENT_ID == Model.Seed)
        {
            SeededCategories inner = new SeededCategories { Seed = node.TAB_ITEM_ID, Categories = Model.Categories };
            <li>
                <a href="[email protected]_ITEM_ID">@node.TAB_ITEM_NAME</a>
                @Html.Partial("_TreeCategories", inner)
            </li>
        }
    }
</ul>
}

The above code works but it needs to be tweaked in order to render the proper style. Additionally, the code above was taken from the link mentioned at the top. I just dont know where to add my HTML structure - meaning: The above code starts with an unordered list where mine starts with a list item The HTML is different and I don't see the pattern and how to transfer this to my situation.

3
  • I don't see where you creating your Tree in the Model? Commented May 7, 2015 at 20:53
  • A recursive method is one which calls itself (no where in your code are you doing that) and in any case your model does not have a hierarchical structure - you should start with a view model (say) CategoryVM which contains a property List<CategoryVM> ChildCategories Commented May 7, 2015 at 23:30
  • @StephenMuecke, Correct - I am not invoking any recursive methods which is why I'm seeking some help. Where/how do I implement recursion? I'm going to see if I can hash it out today using Erik Philips answer. Thank you. Commented May 8, 2015 at 15:26

2 Answers 2

1

I'm personally not a fan of using recursion for partials simple because the stack grows and you can get a Stack Overflow.

I'll show you an easy way to create a tree structure from a non-tree structure, and how I create a tree structure in a view without recursion (sounds CraAAzzzyYY!?).

At some point you should have an object that somehow looks like:

public class NodeVM
{
  public int Id { get; set; }
  public int PartentId { get; set; }
  public string Name { get; set; }

  public IEnumerable<NodeVM> Nodes { get; set; }
  public MvcHtmlString EndTag { get; set; }
{

public class TreeVM
{
  public Stack<NodeVM> Nodes { get; set; }
}

If you start out with just a list of nodes without the Nodes property being populated, this is the easy way to populates nodes:

// get our nodes from the database:
var nodes = db.Nodes
  .Select(n => new NodeVM()
  {
    // map fields or use AutoMapper... whatever
  })
  .ToList(); 

nodes = nodes.Select(node =>
  {
    node.Nodes = nodes.Where(subnode => subnode.ParentID == node.Id).Tolist();
    return node;
  })
  .ToList();

  var model = new TreeVM();
  model.Nodes = new Stack(nodes
    // Find only Parent Elements
    .Where(node => !nodes.Any(subnode => subnode.ParentiD = node.Id));
    // Reverse list, we are using a stack (LILO)
    .Reverse()
    .ToList());

Now lets say we want our html for the tree built like:

<ul>
  <li> A
    <ul>
      <li> A 1 </li>
      <li> A 2 </li>
    </ul>
  </li>
  <li> B
    <ul>
      <li> B 1 </li>
      <li> B 2 </li>
    </ul>
  </li>
<ul>

That is fairly straight forward HTML..., now onto the razor:

@model TreeVM

<ul>
  @while(Model.Nodes.Count > 0)
  {
    var currentNode = Model.Nodes.Pop();

    @* we reached a node with an EndTag Value
       must render closing tag 
    *@
    if (!currentNode.EndTag.IsNullOrEmpty())
    {
      @: @currentNode.EndTag 
    }
    @* we reached a node we haven't seen before
    *@
    else
    {
      // Create our node <LI>
      var li = new TagBuilder("li");
      li.AddCssClass("my css classes");
      li.MergeAttributes("id", currentNode.Id.ToString();

      var endTag = li.ToString(TagRenderMode.EndTag);

      // Render out our LI start tag
      @: @(new MvcHtmlString(li.ToString(TagRenderMode.StartTag)))

      // Do we have subnodes?
      var hasSubNodes = currentNode.Nodes.Count > 0;

      if (hasSubNodes)
      {
        // Need a new UL
        var ul = new TagBuilder("ul");
        li.AddCssClass("my css classes");
        li.MergeAttributes("id", currentNode.Id.ToString();

        // Render out our UL start Tag
        @: @(new MvcHtmlString(ul.ToString(TagRenderMode.StartTag)))

        // End tag should be opposite order </ul></li>
        endTag = ul.ToString(TagRenderMode.EngTag) + endTag;
      }

      // Put the node back in the stack
      currentNode.EndTag = new MvcHtmlString(endTag);
      Model.Nodes.Push(currentNode);

      // Push all sub nodes in the stack
      if (hasSubNodes)
      {
        foreach(var subNode in currentNode.Nodes)
        {
          Model.Nodes.Push(subNode);
        }
      }

      <text>
      Whatever you want in the LI
      </text>

    }
  }
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the answer Erik, can you explain why exactly using stack manually like this doesn't result in stack overflow exception? I mean we push the nodes that have children back on top of stack, what does here prevent the stack from growing to the point of stack overflow?
I'm not using a stack and my code never calls itself nor other nested calls. Based on your comment, I don't think you understand what a StackOverflowException is.
Aren't you using Stack in your TreeVM model? and then Model.Nodes.Push(subNode); maybe I'm confusing the use of Stack data structure (class) to the call stack that overflow? I've read this article from Phil Haack's blog: haacked.com/archive/2007/03/04/…, I think it's because you don't call anything recursively and you don't using the call stack like a normal recursive function, or if it's not the case, I guess it's a little out of my depth.
Ah.. don't confuse the Stack class with The Stack.
0

I managed to solve my own question. So, @ErikPhilips pointed out I was not created a tree model. After some fiddling, I solved my problem. Hopes this helps anyone with a similar situation.

@if (Model.CATEGORIES.Where(s => s.TAB_ITEM_PARENT_ID ==     Model.TAB_ITEM_CHILD_ID).Any())
{
foreach (var node in Model.CATEGORIES)
{
    if (node.TAB_ITEM_PARENT_ID == Model.TAB_ITEM_CHILD_ID)
    {
        SeededCategories inner = new SeededCategories { TAB_ITEM_CHILD_ID = node.TAB_ITEM_ID, CATEGORIES = Model.CATEGORIES };

        bool hasChildren = Model.CATEGORIES.Any(x => x.TAB_ITEM_PARENT_ID == node.TAB_ITEM_ID);
        bool hasParents = Model.CATEGORIES.Any(x => x.TAB_ITEM_ID == node.TAB_ITEM_PARENT_ID);

        <li>
            @if (hasChildren && !hasParents)
            {
                <a href="#"><span><i class="@node.ICON"></i> @node.TAB_ITEM_NAME</span></a>
            }
            else if (hasChildren && hasParents)
            {
                <a href="@node.URL"><span><i class="fa fa-toggle-right"></i> @node.TAB_ITEM_NAME</span></a>
            }
            else
            {
                <a href="@node.URL"><span><i class="#"></i> @node.TAB_ITEM_NAME</span></a>
            }
            @if (hasChildren)
            {
                <ul>
                    @Html.Partial("_TreeCategories", inner)
                </ul>
            }
        </li>
    }
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.