blazort_870x220

Setting required properties of the TreeView to ensure that you get the display you want requires some clever LINQ queries. Here's how to load a TreeView from a table in a database in five (5) statements.

Tree views are an immensely powerful UI concept for organizing hierarchical data while giving the user control over how much of the hierarchy they want exposed. Its configuration, on the other hand, can be a nightmare, especially if you're pulling data from a non-hierarchical source such as a relational database. You end up having to write a lot of code to either ensure that you retrieve your data in the right order for the hierarchy or you have to repetitively navigate through the nodes on the tree view to add the right nodes in the right place.

The TreeView component in Telerik UI for Blazor simplifies all of that configuration; you simply pass a collection of objects that you've defined. Based on the data in the objects, the TreeView sorts out the relationships and builds the hierarchy for you. This post is about how to pass a set of entity objects retrieved through LINQ and Entity Framework.

Version caveats: For this column, I'm using Telerik UI for Blazor 1.4.1, Visual Studio 2019 Preview (version 16.3), and ASP.NET Core v3.0.0-preview7. I had to install the NuGet package for Microsoft.CodeAnalysis (and update some related packages) before I could install the NuGet package containing Telerik UI for Blazor. I also had to upgrade to the latest version of the JavaScript file that supports its components:

<script src="https://kendo.cdn.telerik.com/blazor/1.4.1/telerik-blazor.min.js" defer></script>

If Everything Worked Out

In the TreeView, each node represents an object in a collection that's bound to the Data property. If this collection is constructed perfectly, the code to bind it is simple:

<TelerikTreeView Data="@orgChart"></TelerikTreeView>
@code {
  public IEnumerable<OrgUnit> orgChart { get; set; }
}

In real life, entity objects you retrieve from your database probably aren't following conventions (in fact, your entity objects are probably missing several properties that the TreeView requires). Fortunately, it's very easy to both to override these conventions and to incorporate the additional properties required by the TreeView.

The Relational Perspective

For this article, let's assume the following hierarchical problem: a TreeView that shows the hierarchical relationship in a company's organizational units (divisions and departments). Here, divisions belong to the company and departments belong to divisions.

In this fictional company, there is a database with a table that lists all those organizational units. The relationship between these organizational units is an example of the classical Bill of Materials problem in relational database theory. A table implements the typical solution for this problem: it has a foreign/primary key relationship with itself that allows each department/division to link to the department/division it belongs to.

For that solution, each row has these three columns (plus, probably, many others):

  • OrdId: a primary key that holds the organizational unit's identifier
  • Name: the organizational unit's name
  • Owner: a foreign key that points to another row in the table (the department or division this unit belongs to)

For my example, one row in this table represents the organization. The company row is easily identifiable because the company has no parent department, so its foreign key is set to null (in fact, the TreeView requires that you have one object with no parent to use as the topmost node). This table also has rows for each of the organization's three divisions: East, West, and Central (their foreign keys point back to the company row). The table also has rows for each division's departments (each department's foreign key points back to the row for the division it's part of).

The code to retrieve this data and load it into the collection that drives a TreeView consists of these two lines of code:

CompanyEntities db = new CompanyEntities();
List<OrgUnit> OrgUnits = db.OrganizationalUnits.ToList();

Designing the Classes

To hold that data, there is an OrgUnit entity class:

public class OrgUnit
{
  public int OrgId { get; set; }
  public string Name { get; set; }
  public int? Owner { get; set; }
  // other properties...
}

The TreeView will use the foreign/primary key relationship between the Owner and OrgId properties to nest child objects under their parents. In fact, it's that ability to resolve foreign/primary key relationships and figure out which children belong to which parents that lets the TreeView work with any set of related tables.

But, for any object to work with the TreeView, I must add three (3) additional properties to my OrgUnit class:

  • HasChildren: a Boolean that must be set to true for any parent that has children in the collection passed to the TreeView. If you don't set this property to true on parent nodes, users will be unable to expand a node to see its children.
  • Expanded: a Boolean that you can use to control which nodes display their children. In my cases, I want to set this to true for the top node in the TreeView that represents my company so that the TreeView initially displays with the three (3) divisions showing.
  • Icon or ImageURL: a string with the name of a custom font icon or the URL for an image to display with the node. Optionally, you can add an IconClass property to hold a CSS class name to use with the image.

You're not obligated to use these names as your property names – the TreeView allows you to give your properties whatever names you want and configure the component to use them. In fact, I've already taken advantage of that with my initial three properties of OrgId, Name, and Owner.

By default, the TreeView looks for properties called Id, Text, and ParentId rather than the names I used in my class (OrgId, Name, and Owner). To configure the TreeView to use the property names driven by my table's columns, I use the IdField, ParentIdField, and TextField attributes on the <TelerikTreeViewBinding> element to tie the TreeView to my entity's properties.

That means that my <TelerikTreeView> element ends up looking like this to bind the default names to the property names on my entity class:

<TelerikTreeView Data="@OrgChart">
  <TelerikTreeViewBindings>
    <TelerikTreeViewBinding IdField="OrgId"
                            ParentIdField="Owner"
                            TextField="Name">
    </TelerikTreeViewBinding>
  </TelerikTreeViewBindings>
</TelerikTreeView>

I could do the same thing for the three (3) “required” properties that aren't represented in the database (HasChildren, Expanded, and Icon). However, it's easier just to use the names that the TreeView is looking for.

Here's the full version of my entity class, using the [Notmapped] attribute to tell Entity Framework not to try to bind the required properties to my table:

public class OrgUnit
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int? Owner { get; set; }

  [NotMapped]
  public bool HasChildren { get; set; }
  [NotMapped]
  public string Icon { get; set; }
  [NotMapped]
  public bool Expanded { get; set; }
}

Setting the Required Properties

I'm not quite ready to pass my collection to the TreeView: I still have to set those “required” properties (HasChildren, Icon, and Expanded) so that the TreeView display will do the right thing.

Setting the Expanded property is easy because, for my initial display, I only want the top node (the company node) to be expanded. I can do that with this line of code that finds the entity that has no parent and sets its Expanded property to true:

orgUnits.Where(o => o.Owner == null).First().Expanded = true;

Setting the HasChildren property is slightly more complicated: I want to set it to true for any object that has a child. Or, to put it another way, any object whose OrgId appears in some other object's Owner property. That's what this statement does (if you're keeping count, this is the fourth statement):

orgUnits.Where(p => orgUnits.Any(c => c.Owner == p.Id))
        .ToList()
        .Select(p => { p.HasChildren = true; return p; });

I could just leave the Icon field empty, but that makes for a boring display. Alternatively, if I wanted to put different icons on different OrgUnit instances, I could use a variation on my last statement to selectively set the Icon property based on properties in each OrgUnit. But, for this demo, I'll just set the Icon property to "folder" for all the org units in my fifth, and last, statement:

orgUnits.ToList()
        .Select(p => { p.Icon = "folder"; return p; });

OK, I lied: I have one more statement. I need to set the collection used by the TreeView to the collection I've carefully built. Here's that last line:

orgChart = orgUnits;

And there you have it: Very little fuss or muss and I've loaded a treeview-ready collection from my database in just five LINQ statements. Hard to do better than that (but I bet a comment will show up, eventually, that does it in fewer lines).

To learn more about these Telerik UI for Blazor components and what they can do, check out this Blazor demo page or download a trial to start developing right away!


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.

Related Posts

Comments

Comments are disabled in preview mode.