9

I have a partial view in an MVC 4 project, which is strongly typed. It takes an IEnumerable collection of a table of a database. In that table there are IDs, Names, and ParentIDs for storing hierarchical connection between records. The view that calls the partial view is also strongly typed, it takes the whole database as the model, and passes the Categories table to the partial view, as an enumerable collection:

@Html.Partial("_TreeCategories", @Model.Categories.ToList())

And in the partial view, I want to take the root nodes first, so I can extend the whole tree in a recursive way. In the database table, all records are considered as root nodes with a ParentID == null.
So generally, my way to do this would look like:

@model IEnumerable<TreeCollections.OpenAccess.Category>

@if (Model.ToList().Count >= 0)
    {
    @if (Model.ToList()[0].Parent_id == null)
    {
        <text><ul id="navigation"></text>
    }

    @foreach (var node in @Model)
    {
        <li><a href="?@node.Id">@node.Name</a>
            @foreach (var subNode in @Model.Where(s => s.Parent_id == node.Id))
            {
                @Html.Partial("_TreeCategories", subNode)
            }
        </li>
    }
    @if (Model.ToList()[0].Parent_id == null)
    {
        </ul>
    }
}

So I check if the first element's ParentID of the Model is null, and if it is, then it should create a < ul> tag with the id "navigation", so the jquery plugin can recognise that it is meant to be a treeview control. Then it creates a list tag with a recursive call within. The recursively called partial view takes the children of the node as the model. And lastly if we arrived to the end of the partial view's rendering, and we are at the "root level", it should write a closing < ul> tag
There are some problems, however. First, at the end, that closing unordered list tag is wrong, VS can't find the matching start tag for that. Second, I don't know why, but at the top, I can put the starter < ul> tag in between tags, and I can't do it at the closing tag below. But I'm not sure about these < ul > tags either, I feel those are wrong too.

Please, help me, I'm stuck with this for days now.

user2082422
  • 119
  • 1
  • 2
  • 8

2 Answers2

17

man, you got some wonk going on here. i feel your pain on getting stuck.

see if this floats your boat.

you need a seed value to keep track of what you are looking for in the listing when you do recursion on the same list. it's better to do a parent children mapping in the class, but meh this was fun to do given your structure and should do the trick.

Models

namespace trash.Models
{
    public class Category
    {
        public int ID { get; set; }
        public int? Parent_ID { get; set; }
        public string Name {get; set;}
    }

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

Controller (NOTE: you start the recursion chain by setting the Seed property to null which will pick up all the null parents)

namespace trash.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            IList<trash.Models.Category> categories = new List<trash.Models.Category>();
            categories.Add(new trash.Models.Category { ID = 1, Parent_ID = null, Name = "Top1" });
            categories.Add(new trash.Models.Category { ID = 2, Parent_ID = null, Name = "Top2" });
            categories.Add(new trash.Models.Category { ID = 3, Parent_ID = 1, Name = "Top1Ring1" });
            categories.Add(new trash.Models.Category { ID = 4, Parent_ID = 1, Name = "Top1Ring2" });

            trash.Models.SeededCategories model = new Models.SeededCategories { Seed = null, Categories = categories };
            return View(model);
        }
    }
}

Views Index

@model trash.Models.SeededCategories

Here's a list
@Html.Partial("_TreeCategories", Model)

Partial (your _TreeCategories. NOTE: set the Seed to the current node ID and volia recursion)

@model trash.Models.SeededCategories

@if (Model.Categories.Where(s => s.Parent_ID == Model.Seed).Any())
{
    <ul>
        @foreach (var node in Model.Categories)
        {
            if (node.Parent_ID == Model.Seed)
            {
                trash.Models.SeededCategories inner = new trash.Models.SeededCategories { Seed = node.ID, Categories = Model.Categories };
            <li><a href="?@node.ID">@node.Name</a>
                @Html.Partial("_TreeCategories", inner)
            </li>
            }
        }
    </ul>
}
hubson bropa
  • 2,632
  • 2
  • 28
  • 34
  • Thank you, it's so clear, and yet, I was confused about it for days... I've just reinstalled my whole system on my computer, it takes some time until I download and install Visual Studio again to try this out, but at least I see the pattern now! Thank you so much! – user2082422 Jul 09 '13 at 18:21
  • that's the way it is most of the time, glad to help – hubson bropa Jul 09 '13 at 18:24
  • By the way, what kind of parent-children mapping do you recommend? I chose this "ID - ParentID" method because this is one way to implement hierarchical data structure in a relational datamodel, such as a database. In the Categories table ID is the primary key, ParentID is a foreign key that refers to Categories.ID . – user2082422 Jul 09 '13 at 18:25
  • that works fine. it depends on if you want bidirectional navigation, if so then children need a parent reference. 'Normal' scenario is this: class Papa (inherits from a class called Person) has a property Children which is a list of Person. Person does not have a Parent (or Parents) property. This keeps you clear of circular references, but in Entity Framework you can do it because of lazy loading. So the other way (which is bidirectional) is Papa has Children property and Person has Parent(or Parents) property. – hubson bropa Jul 09 '13 at 18:30
1

You can try Shield UI's recursive TreeView for ASP.NET MVC.

It allows you to specify all the TreeView items using a RecursiveDataSource object, which can be setup to retrieve the data for a tree item from a remote endpoint or a local source "lazily", whenever the item is being expanded.

The RecursiveDataSource is a wrapper around a JavaScript DS widget, which introduces the need for some JS code, as well as updating your server code that will provide the data (either implementing a web service, or place the data in a JS variable in your view).

Vladimir Georgiev
  • 1,949
  • 22
  • 25
  • A link to a potential solution is always welcome, but please [add context around the link](//meta.stackoverflow.com/a/8259) so your fellow users will have some idea what it is and why it’s there. Always quote the most relevant part of an important link, in case the target site is unreachable or goes permanently offline. Take into account that being _barely more than a link to an external site_ is a possible reason as to [Why and how are some answers deleted?](//stackoverflow.com/help/deleted-answers). – Goodbye StackExchange Aug 11 '16 at 11:33
  • Updated the answer – Vladimir Georgiev Aug 11 '16 at 11:51