Joseph Garza
Joseph Garza

Reputation: 21

ASP.NET MVC Recursion Procedure

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.

Upvotes: 2

Views: 1257

Answers (2)

Joseph Garza
Joseph Garza

Reputation: 21

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>
    }
  }
}

Upvotes: 0

Erik Philips
Erik Philips

Reputation: 54638

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>

    }
  }

Upvotes: 1

Related Questions