Creating TreeView For MVC 5

Here, in this article, we're going to extend JS Tree from JS to MVC and we'll render the HTML tags from the database using custom development in the model.

Basically, we'll create a sample DB that contains main/sub categories with self referencing; then we'll add support for Razor like (@HTML.TreeView); and after, we'll render the HTML tags (<ul> <li>) based on our relations in the database.

Let's get started.

We will proceed with the following sections.

  1. Getting Started
  2. Creating the (Code First) model
  3. Creating TreeView Component
  4. Representing Data
Getting Started

In this step, we're going to create the MVC application using Visual Studio 2017 with the following steps.

Step1

Open Visual Studio 2017 and click on “New Project”.


Figure 1 Start Page of VS 2017

Step 2

Select "Web" tab from the left panel and select the “ASP.NET Web Application”.


Figure 2 Creating Web Application in VS 2017

Step 3

Select “MVC project template” from the ASP.NET Template Wizard to create MVC app.


Figure 3 MVC Project Template

This will create an MVC application. After some changes in default layout page, run the application.


Figure 4 Runing the Project

Creating the (Code First) Model

Now, let's create our Model that handles many to many relationships with self referencing (Categories and unlimited sub categories).

Step 1

Right click on Models folder and select Add->Class and name it (Category).


Figure 5 Adding category model

Step 2  

Now, Category Model will contain the follwing peroperties.

Id                 // Category ID
Name          // Category name
Description //Category description
PID             // Parent id 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel.DataAnnotations.Schema;  
  4. using System.Linq;  
  5. using System.Web;  
  6.   
  7. namespace TreeViewMVC.Models  
  8. {  
  9.     public class Category  
  10.     {  
  11.         //Cat Id  
  12.         public int ID { getset; }  
  13.   
  14.         //Cat Name  
  15.         public string Name { getset; }  
  16.   
  17.         //Cat Description  
  18.         public string Description { getset; }  
  19.   
  20.         //represnts Parent ID and it's nullable  
  21.         public int? Pid { getset; }  
  22.         [ForeignKey("Pid")]  
  23.         public virtual Category Parent { getset; }  
  24.         public virtual ICollection<Category> Childs { getset; }  
  25.     }  
  26. }  
Figure 6 Adding model proprties

Step 3 Creating Database context

Right click on the project name from Solution Explorer window and choose Add-> class, and name it "context".


Figure 7 Creating Context

Step 4

Now, we have to install Entity Framework to generate our database.

From the top bar in Visual Studio, click on Tools->Nuget Package Manager -> Manage nuGet packges for sulotion. From the "Browse" tab at the top, select Entity Framwork, then from the right pane, select your project and click on "Install" button.


Figure 8 Installing EntityFramework
 
Step 5 

Now, we have to include the Model of (Category) that we created recently along with Entity Framework, and map our Category Model.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.Data.Entity;  
  6. using TreeViewMVC.Models;  
  7.   
  8. namespace TreeViewMVC  
  9. {  
  10.     public class context: DbContext  
  11.     {  
  12.         public context()  
  13.             //Your connection string in Web.Config File
  14.             :base("ConnectionString")  
  15.         {  
  16.   
  17.         }  
  18.         
  19.         DbSet<Category> Categories { getset; }  
  20.     }  
  21. }  
Step 6 

Now, let's create our Database and add migrations to it with some example data in seed. From the top bar in VS, click on Tools -> NuGet Package Manager -> Package Manager console.


Figure 9 Opening Nuget package manager console

Now, let's enable migrations for our Project using Enable-Migrations command.

After excuting this, EF will create a migration in the root of our project, now, we have to add the migration ( let's call it "init").

Figure 10 Adding init migration
 
Then, let's add some dummy data into seed. We'll be creating categories and sub categories as the follwing,
  • Main Cat1 -> Sub Main Cat1 -> Sub Sub
  • Main Cat2
  • Main Cat3 -> Sub Main Cat3
From Solution Explorer pane, open up the configuration.cs class in the Migrations folder.
  1. namespace TreeViewMVC.Migrations  
  2. {  
  3.     using System;  
  4.     using System.Data.Entity;  
  5.     using System.Data.Entity.Migrations;  
  6.     using System.Linq;  
  7.     using TreeViewMVC.Models;  
  8.   
  9.     internal sealed class Configuration : DbMigrationsConfiguration<TreeViewMVC.context>  
  10.     {  
  11.         public Configuration()  
  12.         {  
  13.             AutomaticMigrationsEnabled = false;  
  14.         }  
  15.   
  16.   
  17.         //Creating an object from our  context class to make the DB operations using EF  
  18.   
  19.   
  20.         protected override void Seed(TreeViewMVC.context context)  
  21.         {  
  22.             //Creating Dummy categories and sub categories  
  23.             context.Categories.AddOrUpdate(c=>c.Name,  
  24.               new Category  { ID =1 , Name="Main Cat1"     , Pid = null , Description  = "Main Cat1"    },  
  25.               new Category  { ID =2 , Name="Sub Main Cat1" , Pid = 1    , Description  ="Sub Main Cat1" },  
  26.               new Category  { ID =3 , Name="Sub Sub"       , Pid = 2    , Description  ="Sub Sub"       },  
  27.               new Category  { ID =4 , Name="Main Cat2"     , Pid = null , Description  ="Main Cat2"     },  
  28.               new Category  { ID =5 , Name="Main Cat3"     , Pid = null , Description  ="Main Cat3"     },  
  29.               new Category  { ID =6 , Name="Sub Main Cat3" , Pid = null , Description  ="Sub Main Cat3" }  
  30.               );  
  31.         }  
  32.     }  
  33. }  
Now, let's create the Database along with dummy data that we added in seed.
 
Go to Tools -> NuGet Package Manager -> Package Manager console and type "Update-Database".

Figure 11 Creating the database from Code First using EF

Let's see what we got in the database:

Figure 12 Our Database after executing Update-Database

Creating TreeView class

Step 1

Now, we have to develop the TreeView class "component"

From soulotion explorer window, right click on Models folder ->add ->class and let's name it "Tree"

Figure 13 Adding class "Tree" for creating TreeView component
 
Step 2

Here we go with our component,

The Tree class will create an HTML tree from a recursive collection of items and handle  the (X) model in our case "Category" model and generate <ul> <li> items based on relations we added in the seed.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Reflection;  
  5. using System.Web;  
  6. using System.Web.Mvc;  
  7. using System.Web.UI;  
  8. using System.Web.WebPages;  
  9.   
  10. namespace TreeViewMVC.Models  
  11. {  
  12.     public static class TreeViewHelper  
  13.     {  
  14.         /// <summary>  
  15.         /// Create an HTML tree from a recursive collection of items  
  16.         /// </summary>  
  17.         public static TreeView<T> TreeView<T>(this HtmlHelper html, IEnumerable<T> items)  
  18.         {  
  19.             return new TreeView<T>(html, items);  
  20.         }  
  21.     }  
  22.   
  23.     /// <summary>  
  24.     /// Create an HTML tree from a resursive collection of items  
  25.     /// </summary>  
  26.     public class TreeView<T> : IHtmlString  
  27.     {  
  28.         private readonly HtmlHelper _html;  
  29.         private readonly IEnumerable<T> _items = Enumerable.Empty<T>();  
  30.         private Func<T, string> _displayProperty = item => item.ToString();  
  31.         private Func<T, IEnumerable<T>> _childrenProperty;  
  32.         private string _emptyContent = "No children";  
  33.         private IDictionary<stringobject> _htmlAttributes = new Dictionary<stringobject>();  
  34.         private IDictionary<stringobject> _childHtmlAttributes = new Dictionary<stringobject>();  
  35.         private Func<T, HelperResult> _itemTemplate;  
  36.   
  37.         public TreeView(HtmlHelper html, IEnumerable<T> items)  
  38.         {  
  39.             if (html == nullthrow new ArgumentNullException("html");  
  40.             _html = html;  
  41.             _items = items;  
  42.             // The ItemTemplate will default to rendering the DisplayProperty  
  43.             _itemTemplate = item => new HelperResult(writer => writer.Write(_displayProperty(item)));  
  44.         }  
  45.   
  46.         /// <summary>  
  47.         /// The property which will display the text rendered for each item  
  48.         /// </summary>  
  49.         public TreeView<T> ItemText(Func<T, string> selector)  
  50.         {  
  51.             if (selector == nullthrow new ArgumentNullException("selector");  
  52.             _displayProperty = selector;  
  53.             return this;  
  54.         }  
  55.   
  56.   
  57.         /// <summary>  
  58.         /// The template used to render each item in the tree view  
  59.         /// </summary>  
  60.         public TreeView<T> ItemTemplate(Func<T, HelperResult> itemTemplate)  
  61.         {  
  62.             if (itemTemplate == nullthrow new ArgumentNullException("itemTemplate");  
  63.             _itemTemplate = itemTemplate;  
  64.             return this;  
  65.         }  
  66.   
  67.   
  68.         /// <summary>  
  69.         /// The property which returns the children items  
  70.         /// </summary>  
  71.         public TreeView<T> Children(Func<T, IEnumerable<T>> selector)  
  72.         {  
  73.             //  if (selector == null) //throw new ArgumentNullException("selector");  
  74.             _childrenProperty = selector;  
  75.             return this;  
  76.         }  
  77.   
  78.         /// <summary>  
  79.         /// Content displayed if the list is empty  
  80.         /// </summary>  
  81.         public TreeView<T> EmptyContent(string emptyContent)  
  82.         {  
  83.             if (emptyContent == nullthrow new ArgumentNullException("emptyContent");  
  84.             _emptyContent = emptyContent;  
  85.             return this;  
  86.         }  
  87.   
  88.         /// <summary>  
  89.         /// HTML attributes appended to the root ul node  
  90.         /// </summary>  
  91.         public TreeView<T> HtmlAttributes(object htmlAttributes)  
  92.         {  
  93.             HtmlAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));  
  94.             return this;  
  95.         }  
  96.   
  97.         /// <summary>  
  98.         /// HTML attributes appended to the root ul node  
  99.         /// </summary>  
  100.         public TreeView<T> HtmlAttributes(IDictionary<stringobject> htmlAttributes)  
  101.         {  
  102.             if (htmlAttributes == nullthrow new ArgumentNullException("htmlAttributes");  
  103.             _htmlAttributes = htmlAttributes;  
  104.             return this;  
  105.         }  
  106.   
  107.         /// <summary>  
  108.         /// HTML attributes appended to the children items  
  109.         /// </summary>  
  110.         public TreeView<T> ChildrenHtmlAttributes(object htmlAttributes)  
  111.         {  
  112.             ChildrenHtmlAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));  
  113.             return this;  
  114.         }  
  115.   
  116.         /// <summary>  
  117.         /// HTML attributes appended to the children items  
  118.         /// </summary>  
  119.         public TreeView<T> ChildrenHtmlAttributes(IDictionary<stringobject> htmlAttributes)  
  120.         {  
  121.             if (htmlAttributes == nullthrow new ArgumentNullException("htmlAttributes");  
  122.             _childHtmlAttributes = htmlAttributes;  
  123.             return this;  
  124.         }  
  125.   
  126.         public string ToHtmlString()  
  127.         {  
  128.             return ToString();  
  129.         }  
  130.   
  131.         public void Render()  
  132.         {  
  133.             var writer = _html.ViewContext.Writer;  
  134.             using (var textWriter = new HtmlTextWriter(writer))  
  135.             {  
  136.                 textWriter.Write(ToString());  
  137.             }  
  138.         }  
  139.   
  140.         private void ValidateSettings()  
  141.         {  
  142.             if (_childrenProperty == null)  
  143.             {  
  144.                 return;  
  145.             }  
  146.         }  
  147.   
  148.   
  149.         public override string ToString()  
  150.         {  
  151.             ValidateSettings();  
  152.             var listItems = new List<T>();  
  153.             if (_items != null)  
  154.             {  
  155.                 listItems = _items.ToList();  
  156.             }  
  157.   
  158.   
  159.             var ul = new TagBuilder("ul");  
  160.             ul.MergeAttributes(_htmlAttributes);  
  161.             var li = new TagBuilder("li")  
  162.             {  
  163.                 InnerHtml = _emptyContent  
  164.             };  
  165.             li.MergeAttribute("id""-1");  
  166.   
  167.             if (listItems.Count > 0)  
  168.             {  
  169.                 var innerUl = new TagBuilder("ul");  
  170.                 innerUl.MergeAttributes(_childHtmlAttributes);  
  171.   
  172.                 foreach (var item in listItems)  
  173.                 {  
  174.                     BuildNestedTag(innerUl, item, _childrenProperty);  
  175.                 }  
  176.                 li.InnerHtml += innerUl.ToString();  
  177.             }  
  178.             ul.InnerHtml += li.ToString();  
  179.   
  180.             return ul.ToString();  
  181.         }  
  182.   
  183.         private void AppendChildren(TagBuilder parentTag, T parentItem, Func<T, IEnumerable<T>> childrenProperty)  
  184.         {  
  185.             //  
  186.             if (childrenProperty == null)  
  187.             {  
  188.                 return;  
  189.             }  
  190.             var children = childrenProperty(parentItem).ToList();  
  191.             if (!children.Any())  
  192.             {  
  193.                 return;  
  194.             }  
  195.   
  196.             var innerUl = new TagBuilder("ul");  
  197.             innerUl.MergeAttributes(_childHtmlAttributes);  
  198.   
  199.             foreach (var item in children)  
  200.             {  
  201.                 BuildNestedTag(innerUl, item, childrenProperty);  
  202.             }  
  203.   
  204.             parentTag.InnerHtml += innerUl.ToString();  
  205.         }  
  206.   
  207.         private void BuildNestedTag(TagBuilder parentTag, T parentItem, Func<T, IEnumerable<T>> childrenProperty)  
  208.         {  
  209.             var li = GetLi(parentItem);  
  210.             parentTag.InnerHtml += li.ToString(TagRenderMode.StartTag);  
  211.             AppendChildren(li, parentItem, childrenProperty);  
  212.             parentTag.InnerHtml += li.InnerHtml + li.ToString(TagRenderMode.EndTag);  
  213.         }  
  214.   
  215.         private TagBuilder GetLi(T item)  
  216.         {  
  217.             var li = new TagBuilder("li")  
  218.             {  
  219.                 InnerHtml = _itemTemplate(item).ToHtmlString()  
  220.             };  
  221.             Type myType = item.GetType();  
  222.             IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());  
  223.             foreach (PropertyInfo prop in props)  
  224.             {  
  225.                 if (prop.Name.ToLower() == "id")  
  226.                     li.MergeAttribute("id", prop.GetValue(item, null).ToString());
  227.                 // Do something with propValue  
  228.                 if (prop.Name.ToLower() == "sortorder")  
  229.                     li.MergeAttribute("priority", prop.GetValue(item, null).ToString());  
  230.             }  
  231.             return li;  
  232.         }  
  233.     }  
  234. }   
Represent Data

Now, we're done with TreeView class and our Model, let's start on View.

Step 1

We have to include jstree, which is jquery plugin, that provides interactive trees.
 
From the top menu, click on Tools -> NuGet Package Manager -> Manage NuGet packages for solution.
Choose "Browse" tab, then type jsTree,

Then, hit on Install button to download JSTree and include it in your project.

 
 
Step 2

From Solution Explorer window, expand the Views folder and right click on Home folder to add an Empty View, and name it "TreeView".

Figure 14 Adding an empty view to our project
 
Now, let's add some code to the our View to display the categories.
  1. @model IEnumerable<TreeViewMVC.Models.Category>  
  2. @using System.Web.UI.WebControls  
  3. @using TreeViewMVC.Models;  
  4.   
  5. <h2>TreeView</h2>  
  6. <link href="~/Content/jsTree/themes/default/style.min.css" rel="stylesheet" />  
  7. <div class="form-body">  
  8.     <div id="jstree">  
  9.         @(Html.TreeView(Model)  
  10.                           .EmptyContent("root")  
  11.                           .Children(m => m.Childs)  
  12.                           .HtmlAttributes(new { id = "tree" })  
  13.                       .ChildrenHtmlAttributes(new { @class = "subItem" })  
  14.                           .ItemText(m => m.Name)  
  15.                           .ItemTemplate(  
  16.         @<text>  
  17.             <a href="@item.Description" desc="@item.Description">@item.Name</a>  
  18.         </text>)  
  19.         )  
  20.     </div>  
  21. </div>  
  22. @section scripts  
  23. {  
  24.     <script src="~/Scripts/jsTree3/jstree.min.js"></script>  
  25.     <script>  
  26.         $(function () {  
  27.             var selectedData;  
  28.             $('#jstree').jstree({  
  29.                 "core": {  
  30.                     "multiple"true,  
  31.                     "check_callback"false,  
  32.                     'themes': {  
  33.                         "responsive"true,  
  34.                         'variant''larg',  
  35.                         'stripes'false,  
  36.                         'dots'false  
  37.                     }  
  38.                 },  
  39.                 "types": {  
  40.                     "default": {  
  41.                         "icon""fa fa-folder icon-state-warning icon-lg"  
  42.                     },  
  43.                     "file": {  
  44.                         "icon""fa fa-file icon-state-warning icon-lg"  
  45.                     }  
  46.                 },  
  47.                 "plugins": ["dnd""state""types""sort""checkbox"]  
  48.             });  
  49.         });  
  50.     </script>  
  51. }   
Now, we have to define the View "TreeView" in our Controller "Home controller".
  1. public ActionResult TreeView()  
  2. {  
  3.     var db = new context();  
  4.     return View(db.Categories.Where(x => !x.Pid.HasValue).ToList());  
  5. }   
 Result