7/10/2021 Admin
Radzen Blazor Tree Creator with Checkboxes
You can build a Radzen Blazor tree creator with checkboxes.
A list of checkboxes allows you to assign multiple tags to an entity, such as help desk tickets. Putting the tags in a hierarchical tree allows the tags to contain additional context, for example a customer-service tag under the outside-sales node has a different meaning than a customer-service tag under the inside-sales node.
In addition, hierarchical tree nodes can be moved, (by simply assigning, or removing a parent to the tree nodes). This effectively associates the entity with additional context.
Note - also see: Blazor Tree Creator with Checkboxes
The Application
The sample application (available on the Downloads page of this site), displays a hierarchical tree with checkboxes next to each tree node.
Clicking the Get Selected button will display a list of the tree nodes that have their checkboxes checked.
New nodes can be created by clicking the New button, entering a Node Name and setting the Node Parent, and clicking the Save button.
Existing nodes can be edited by clicking on them.
This will display the node in the Edit Node section.
The Node Name and Node Parent can be altered and the changes persisted by clicking the Save button.
(Note: The currently selected node will not display in the Node Parent dropdown, so it cannot accidently be selected as a parent of itself)
While in Edit Node mode, the node can be deleted by clicking the Delete button.
If the node has any children, they will be come the child of the parent of the deleted node.
Creating The Application
The project is created as a Blazor Server App.
The following NuGet package is installed:
The complete solution contains files added to the Data and Models directories.
The rest of the application is primarily contained in the Index.razor page.
The Data
While the Tree must be bound to a collection of a class (TreeNode), that contains a nested collection of children, if a database is used to persist the data, it would need to be stored in a collection that resembles the DataNode class.
Therefore, even though this sample stores data in-memory, a DataNode collection is used to store the data. It is programmatically transformed to and from the TreeNode collection.
Loading The Tree (and Node Parent dropdown)
When the page first loads, the OnInitializedAsync() fires and loads the sample data, binds it to the collation that is bound to the Tree, and the Dropdown (that is used to select the Node Parent):
protected override async Task OnInitializedAsync(){// Create initial data (as DataNodes)
CreateDefaultTree();// Pass DataNodes to GetTree to get a list of TreeNodes
colAllTags =await TreeUtility.GetTree(colDataNode);// Get colTreelist
colTreelist =await TreeUtility.GetTreeList(colAllTags, SelectedTreeNode);// Set Checked values
CheckedValues = await TreeUtility.GetSelectedTreeNodes(colAllTags);}
The code to display the tree is as follows:
<RadzenTree AllowCheckBoxes="true"Data=@colAllTagsExpand=@LoadTreeChange=@OnChange@bind-CheckedValues="@CheckedValues"AllowCheckChildren="AllowCheckChildren"AllowCheckParents="AllowCheckParents"Style="width: 100%; height: 100%"><RadzenTreeLevel Text=@GetTextForNodeTemplate=@FolderTemplateExpanded="@(data => true)" /></RadzenTree>
This uses the Tree Templates below:
// Tree Templates and Events
RenderFragment<RadzenTreeItem> FolderTemplate = (context) => builder =>{TreeNode objTreeNode = context.Value as TreeNode;
builder.OpenComponent<RadzenIcon>(0);builder.AddAttribute(1, "Icon", "folder");builder.CloseComponent();builder.AddContent(2, context.Text);};void LoadTree(TreeExpandEventArgs args)
{var objTreeNode = args.Value as TreeNode;
args.Children.Data = objTreeNode.Children;args.Children.Text = GetTextForNode;args.Children.HasChildren = e => (e as TreeNode).Children.Any();
args.Children.Template = FolderTemplate;}string GetTextForNode(object data){return ((TreeNode)data).NodeName;
}void OnChange(TreeEventArgs args)
{SelectNode((args.Value as TreeNode));
}
The CreateDefaultTree() method simply creates a collection of tree nodes, including which ones are selected:
private void CreateDefaultTree(){colDataNode.Add(new DataNode
{ Id = 1, IsSelected = false, NodeName = "One", ParentId = null });colDataNode.Add(new DataNode
{Id = 2, IsSelected = false, NodeName = "One-One", ParentId = 1});colDataNode.Add(new DataNode
{Id = 3, IsSelected = true, NodeName = "One-Two", ParentId = 1});colDataNode.Add(new DataNode
{Id = 4, IsSelected = true, NodeName = "Two", ParentId = null});colDataNode.Add(new DataNode
{Id = 5, IsSelected = false, NodeName = "Two-One", ParentId = 4});colDataNode.Add(new DataNode
{Id = 6, IsSelected = false, NodeName = "Two-One-One", ParentId = 5});colDataNode.Add(new DataNode
{Id = 7, IsSelected = true, NodeName = "Two-Two", ParentId = 4});}
The TreeUtility.GetTree(colDataNode) method takes that DataNode collection and transforms it into a TreeNode collection using the following code:
public static async Task<List<TreeNode>> GetTree(List<DataNode> DataNodes){List<TreeNode> ColTreeNodes = new List<TreeNode>();
// Get all the top level nodes
foreach (var node in DataNodes.Where(x => x.ParentId == null)){TreeNode objTreeNode = new TreeNode();
objTreeNode.Id = node.Id;objTreeNode.NodeName = node.NodeName;objTreeNode.IsSelected = node.IsSelected;objTreeNode.Children = new List<TreeNode>();
ColTreeNodes.Add(objTreeNode);//Recursively call the AddChildren method adding all children
await Task.Run(() => AddChildren(DataNodes, ColTreeNodes, objTreeNode));}return ColTreeNodes;
}
private static void AddChildren(List<DataNode> colNodeItemCollection,List<TreeNode> colTreeNodeCollection,TreeNode paramTreeNode){// Get the children of the current item
// This method may be called from the top level
// or recursively by one of the child items
var ChildResults = from objNode in colNodeItemCollection
where objNode.ParentId == paramTreeNode.Idselect objNode;// Loop thru each Child of the current Node
foreach (var objChild in ChildResults){// Create a new Node
var objNewNode = new TreeNode();
objNewNode.Id = objChild.Id;objNewNode.NodeName = objChild.NodeName;objNewNode.IsSelected = objChild.IsSelected;objNewNode.Children = new List<TreeNode>();
paramTreeNode.Children.Add(objNewNode);//Recursively call the AddChildren method adding all children
AddChildren(colNodeItemCollection, colTreeNodeCollection, objNewNode);}}
Finally, the TreeUtility.GetTreeList(colAllTags, SelectedTreeNode) method takes that collection, and the currently selected node, and creates a collection of TreeListNode that is bound to the Dropdown that is used to select the Node Parent, using the following code:
public static async Task<List<TreeListNode>> GetTreeList(List<TreeNode> TreeNodes, TreeNode CurrentSelectedNode){List<TreeListNode> ColTreeNodes = new List<TreeListNode>();
// Create default
ColTreeNodes.Add(new TreeListNode()
{Id = 0,ParentId = 0,NodeName = "[None]"
});// Get all the top level nodes
foreach (var node in TreeNodes){ColTreeNodes.Add(new TreeListNode()
{Id = node.Id,ParentId = 0,NodeName = node.NodeName});// Recursively call the AddChildren method adding all children
await Task.Run(() =>AddTreeListChildren(TreeNodes, ColTreeNodes, node));}// Prepare final collection
List<TreeListNode> ColFinalTreeNodes = new List<TreeListNode>();
foreach (var item in ColTreeNodes){// Do not add the currently selected node to the list
if (item.Id != CurrentSelectedNode.Id)
{ColFinalTreeNodes.Add(item);}}return ColFinalTreeNodes;
}
private static void AddTreeListChildren(List<TreeNode> colNodeItemCollection,List<TreeListNode> colTreeNodeCollection,TreeNode paramTreeNode){// Get the children of the current item
// This method may be called from the top level
// or recursively by one of the child items
// Loop thru each Child of the current Node
foreach (var objChild in paramTreeNode.Children){// Get the Parent node
var ParentNode =colTreeNodeCollection.Where(x => x.Id == paramTreeNode.Id).FirstOrDefault();// See how many dots the Parent has
int CountOfParentDots = ParentNode.NodeName.Count(x => x == '.');
colTreeNodeCollection.Add(new TreeListNode()
{Id = objChild.Id,ParentId = ParentNode.Id,NodeName = $"{AddDots(CountOfParentDots + 1)}{objChild.NodeName}"
});// Recursively call the AddChildren method adding all children
AddTreeListChildren(colNodeItemCollection, colTreeNodeCollection, objChild);}}
private static string AddDots(int intDots){String strDots = "";for (int i = 0; i < intDots; i++){strDots += ". ";
}return strDots;
}
Selecting a Node
When a node is selected, and loaded into the Edit Node box, the following code is used:
async void SelectNode(TreeNode selectedNode)
{EditLabel = "Edit Node";
SelectedTreeNode = selectedNode;SelectedTreeNodeParentId = 0;var SelectedNode =colDataNode.Where(x => x.Id == selectedNode.Id).FirstOrDefault();if (SelectedNode != null){SelectedTreeNodeParentId =colDataNode.Where(x => x.Id == selectedNode.Id).FirstOrDefault().ParentId ?? 0;}// Refresh parent dropdown list
colTreelist = await TreeUtility.GetTreeList(colAllTags, SelectedTreeNode);StateHasChanged();}
The New Button
When the New button is pressed, the following code is used:
private async void SetNewNode(){EditLabel = "New Node";
SelectedTreeNode =new TreeNode()
{Id = -1,NodeName = "",IsSelected = false,
Children = new List<TreeNode>()
};// Get colTreelist
colTreelist = await TreeUtility.GetTreeList(colAllTags, SelectedTreeNode);StateHasChanged();}
The Save Button
When the Save button is pressed, the following code is used:
private async void SaveNode(){if (SelectedTreeNode != null){if (SelectedTreeNode.Id > 0) // Existing Node{// Find the node in the colDataNode list
var EditedNode =colDataNode.Where(x => x.Id == SelectedTreeNode.Id).FirstOrDefault();// Edit the Node Name
EditedNode.NodeName = SelectedTreeNode.NodeName;// Make node a child of the selected parent
if (SelectedTreeNodeParentId == 0)
{EditedNode.ParentId = null;
}else
{EditedNode.ParentId = SelectedTreeNodeParentId;}// Pass DataNodes to GetTree to get a list of TreeNodes
colAllTags = await TreeUtility.GetTree(colDataNode);// Get colTreelist
colTreelist = await TreeUtility.GetTreeList(colAllTags, SelectedTreeNode);}else // New Node{if (SelectedTreeNode.NodeName.Trim().Length > 0)
{intCurrentId = intCurrentId + 1;// Add the node
var NewNode = new DataNode()
{Id = intCurrentId,NodeName = SelectedTreeNode.NodeName,IsSelected = false
};// Set ParentId
if (SelectedTreeNodeParentId == 0)
{NewNode.ParentId = null;
}else
{NewNode.ParentId = SelectedTreeNodeParentId;}// Add the new node to colDataNode
colDataNode.Add(NewNode);// Pass DataNodes to GetTree to get a list of TreeNodes
colAllTags = await TreeUtility.GetTree(colDataNode);// Get colTreelist
colTreelist = await TreeUtility.GetTreeList(colAllTags, SelectedTreeNode);// Clear the SelectedTreeNode
SelectedTreeNode =new TreeNode()
{Id = -1,NodeName = "",IsSelected = false,
Children = new List<TreeNode>()
};}}}// Set Checked values
CheckedValues = await TreeUtility.GetSelectedTreeNodes(colAllTags);StateHasChanged();}
The Delete Button
When the Delete button is pressed, the following code is used:
private async void DeleteNode(){if (SelectedTreeNode.Id > -1)
{// Find the node in the colDataNode list
var DeletedNode =colDataNode.Where(x => x.Id == SelectedTreeNode.Id).FirstOrDefault();// Get the ParentId (if any)
var DeletedParentId = DeletedNode.ParentId;// Update all child nodes (if any) to he new ParentId
List<DataNode> ChildNodes =colDataNode.Where(x => x.ParentId == DeletedNode.Id).ToList();foreach (var item in ChildNodes){// Get the node
var ChildNode =colDataNode.Where(x => x.Id == item.Id).FirstOrDefault();// Update the ParentId
ChildNode.ParentId = DeletedParentId;}// ** Remove the node from the colDataNode collection **
colDataNode.Remove(DeletedNode);// Pass DataNodes to GetTree to get a list of TreeNodes
colAllTags = await TreeUtility.GetTree(colDataNode);// Get colTreelist
colTreelist = await TreeUtility.GetTreeList(colAllTags, SelectedTreeNode);// Set mode to New Node
SetNewNode();StateHasChanged();}}
The Get Selected Button
When the Get Selected button is pressed, the following method takes the TreeNode collection and returns a list of the tree nodes that were selected:
public static async Task<List<TreeNode>> GetSelectedTreeNodes(List<TreeNode> TreeNodes){List<TreeNode> ColSelectedTreeNodes = new List<TreeNode>();
// Get all the top level nodes
foreach (var node in TreeNodes){if (node.IsSelected)
{ColSelectedTreeNodes.Add(node);}// Recursively call the AddChildren method adding all children
await Task.Run(() =>AddTreeNodeChildren(TreeNodes, ColSelectedTreeNodes, node));}return ColSelectedTreeNodes;
}private static void AddTreeNodeChildren(List<TreeNode> colNodeCollection,List<TreeNode> colTreeNodeCollection,TreeNode paramTreeNode){// Get the children of the current item
// This method may be called from the top level
// or recursively by one of the child items
// Loop thru each Child of the current Node
foreach (var objChild in paramTreeNode.Children){if (objChild.IsSelected)
{colTreeNodeCollection.Add(objChild);}// Recursively call the AddChildren method adding all children
AddTreeNodeChildren(colNodeCollection, colTreeNodeCollection, objChild);}}
Download
The project is available on the Downloads page on this site.
You must have Visual Studio 2019 (or higher) installed to run the code.