7

Blazor Tree Creator with Checkboxes

 3 years ago
source link: https://blazorhelpwebsite.com/ViewBlogPost/51
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Blazor Tree Creator with Checkboxes


You can build a 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.

The Application

image

The sample application (available on the Downloads page of this site), displays a hierarchical tree with checkboxes next to each tree node.

image

Clicking the Get Selected button will display a list of the tree nodes that have their checkboxes checked.

image

New nodes can be created by clicking the New button, entering a Node Name and setting the Node Parent, and clicking the Save button.

image

Existing nodes can be edited by clicking on them.

image

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)

image

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

image

The project is created as a Blazor Server App.

image

The following NuGet packages are installed:

image

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 markup for the Index.razor page is as follows:

image

(Note: preventDefault and stopPropagation are required to prevent events tied to selecting a tree node from bubbling up to the underlying tree control and causing nodes with children to collapse)

The Data

image

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);
								
									}

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.Id
								
									select 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

image

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

image

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

image

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();
								
									// 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>()
								
									};
								
									}
								
									}
								
									}
								
									StateHasChanged();
								
									}

The Delete Button

image

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

image

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>> GetSelected(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(() => AddSelectedChildren(TreeNodes, ColSelectedTreeNodes, node));
								
									}
								
									return
									ColSelectedTreeNodes;
								
									}
									private
									static
									void
									AddSelectedChildren(
								
									List<TreeNode> 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
								
									// 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
								
									AddSelectedChildren(colNodeItemCollection, 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.

Links

excubo-ag/Blazor.TreeViews

Radzen


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK