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 identifierName
: the organizational unit's nameOwner
: 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
: aBoolean
that must be set totrue
for any parent that has children in the collection passed to theTreeView
. If you don't set this property totrue
on parent nodes, users will be unable to expand a node to see its children.Expanded
: aBoolean
that you can use to control which nodes display their children. In my cases, I want to set this totrue
for the top node in theTreeView
that represents my company so that theTreeView
initially displays with the three (3) divisions showing.Icon
orImageURL
: astring
with the name of a custom font icon or the URL for an image to display with the node. Optionally, you can add anIconClass
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!