RadzenTree questions

Good Evening,
currently porting a ASP.net app to blazor, i am missing some functionality in the RadzenTree component (or can't find it / find a way around it).

Missing methods:
i would like to, programmatically,

  • expand a certain node (like _tree.ExpandNode(TreeNode x))
  • expand all nodes at once after they are loaded (like _tree.ExpandAllNodes)
  • get the currently selected tree node object (like TreeNode s = _tree.GetSelectedNode())

is this already possible and i missed it somehow?

Node Selection:
the args parameter currently contains a textstring and the object of the node that was clicked.
void OnTreeSelectionChange(TreeEventArgs args)
but i need:

  • the node depth (root node =1, subnode = 2, next subnode = 3 and so on)
  • parent node
  • data object of parent node
  • traversal possibility of selected node up to root node (e.g. ParentNode.ParentNode.Name)

this is how it currently looks like:

for example i click an entity node at node depth 3. i want to find out which "category" this node is in - this information is in the node in depth 2. since my entity nodes can be linked to multiple categories, there is no 1:1 relationship in the data model, so i have to use the tree to find my category=parent.
can we somehow access the nodes of the tree programmatically?

tree construction in code
finally, when constructing the tree, i currently do this with the TreeNodeExpand handler, but i feel this is from the wrong direction:

void OnTreeNodeExpand(TreeExpandEventArgs args)
{
    if (args.Value is Group)
    {
        // TODO: how to make sure here that no expand icon appears if subnode has no children?
    }
    else if ((args.Value is Category))
    {
        var cat = args.Value as Category;

        switch (_type)
        {
            case NavTreeNodeType.Check:
                args.Children.Data = cat.CategoryCheckLinks;
                args.Children.Text = GetTextForCheckNode;
                args.Children.HasChildren = (o) => false;   // level 3 nodes can't expand
                args.Children.Template = TreeCheckTemplate;
                break;
            case NavTreeNodeType.Measure:
                args.Children.Data = cat.CategoryMeasureLinks;
                args.Children.Text = GetTextForMeasureNode;
                args.Children.HasChildren = (o) => false;   // level 3 nodes can't expand
                args.Children.Template = TreeMeasureTemplate;
                break;
        }
    }
    else
    {
        // level 3 nodes have no children
    }
}

RenderFragment<RadzenTreeItem> TreeCheckTemplate = (context) => builder =>
{
    var c = context.Value as CategoryCheckLink;
    MarkupString data = new MarkupString(Globals.quickHackTreeColorBox(c.Check.Name, c.Check.Published));
    builder.AddContent(0, data);
};
// template for measure node
RenderFragment<RadzenTreeItem> TreeMeasureTemplate = (context) => builder =>
{
    var m = context.Value as CategoryMeasureLink;
    MarkupString data = new MarkupString(Globals.quickHackTreeColorBox(m.Measure.Name, m.Measure.Published));
    builder.AddContent(0, data);
};

so for example, when i would like to make sure, that nodes at level 2 (categories) have no "expand" icon on the left side, i have to put code in the expand handler for level 1 (groups). i failed to cook up some code to do that.
it would be much easier for me to just precreate the entire tree structure after querying the database, instead of creating it step by step after user interaction? is this possible? did i miss this approach in the examples somewhere?

pseudo code:

foreach (var g in groups)
{
    TreeNode gNode = _tree.CreateNode(g.Name);
    gNode.Data = g;
    foreach (var c in g.categories)
    {
        TreeNode cNode = gNode.CreateNode(c.Name);
        cNode.Data = c;
        // find entities in this category
        var measures = context.CategoryMeasureLinks.Where(x=>x.categoryid == c.id).ToList();
        foreach(var m in measures)
        {
              TreeNode mNode = cNode.CreateNode(m.Name);
              mNode.Data = m;
        } 
    }
}

Wow that's a lot of questions :slight_smile:

I will try to answer as many as I can:

ASP.net app to blazor, i am missing some functionality in the RadzenTree component

The RadzenTree probably won't match your existing ASP.NET tree component API 1:1. Blazor is more of a declarative framework (uses data-binding instead of code-behind) while you seem to be looking for imperative API - adding nodes from code, accessing parent nodes etc.

expand a certain node

One can use the Expanded property to do that.

expand all nodes at once after they are loaded (like _tree.ExpandAllNodes)

The <RadzenTreeLevel> has Expanded property. The following will expand all nodes:

<RadzenTreeLevel TextProperty="CategoryName"
  ChildrenProperty="Products" Expanded="@(data => true)">
  • the node depth (root node =1, subnode = 2, next subnode = 3 and so on)
  • parent node
  • data object of parent node
  • traversal possibility of selected node up to root node (e.g. ParentNode.ParentNode.Name)

Those are indeed not available. We can easily add the node level. However maintaining the node hierarchy in memory is something we are not sure we should do. It has the potential to create memory leaks (a parent has children and the child refers to its parent which creates a loop). Also Blazor does not currently have any API that notifies a parent component that a child is removed or updated.

As a workaround I can suggest one of the following:

  • Create an object hierarchy that has Parent/Child relationship as data. You can use that hierarchy to data-bind the treeview (you are actually suggesting that later in your post). Here is an example:
<RadzenTree Data="@nodes">
   <RadzenTreeLevel Text="@(data => ((DataNode)data).Data.ToString())" 
         ChildrenProperty="Children" 
         HasChildren="@(data => ((DataNode)data).Children != null)" 
    />
</RadzenTree>
@code {
    public class DataNode
    {
        public object Data { get; set; }
        public IEnumerable<DataNode> Children { get; set; }
    }

    DataNode[] nodes = new DataNode[]
    {
        new DataNode
        {
            Data = "Root",
            Children = new [] {
                new DataNode
                {
                    Data = "Child"
                }
            }
        }
    };
}
  • оr use your API to find the parent of the current data item - if it is a file - get its parent directory, otherwise query the DB.

thanks a lot. i see i need to learn a different approach with blazor. but thats ok :slight_smile:
thanks to your suggestions i made good progress today. you're right, i totally didn't think of just creating the data structure myself and using that as the datasource. :man_facepalming:
for the moment i guess i am fine. cheers!

ok, i got most of the tree stuff implemented now. two things are still open.

#1 expand on click, expanding with property does not seem to execute the Expand= handler

    <RadzenTree @ref="_tree" Data="@_treeDataSource" Change="@OnTreeSelectionChange" Expand="OnTreeNodeExpand" Style="min-width: 300px; height: 100%;">
        <RadzenTreeLevel TextProperty="Name" ChildrenProperty="Categories" HasChildren="QueryGroupHasChildren" Expanded="@(data => (data as Group).expanded)">

on node click, the selection changed event is fired correctly. i modify the property as you suggested:

void OnTreeSelectionChange(TreeEventArgs args)
{
    Console.WriteLine($"TreeNode selected: {args.Text}");
    if (_lastClickedNode == args.Value)
    {
        if (args.Value is Group)
            (args.Value as Group).expanded = !(args.Value as Group).expanded;
        if (args.Value is Category)
            (args.Value as Category).expanded = !(args.Value as Category).expanded;
    }
    _lastClickedNode = args.Value;

and the node expands and collapses correctly (little icon on the left changes).
unfortunately, the Expand="OnTreeNodeExpand" handler is never called so my child nodes are not created....

image

could this be fixed?

#2 drag and drop
my nodes are sorted by a property in their data object. the user needs to be able to reorder the nodes somehow. in my old asp.net app i had two buttons next to each node, to move them up or down by modifying the property value. this is oldschool :slight_smile:

a far better approach would be drag and drop. could i leave this as a little feature request? e.g. if the treelevel has a property "DnDEnabled" set to true, a little ui element appears at each node that can be used to move up or down? once dropped, a callback event is triggered, where i modify the property again.
i understand this might be quite complicated to implement, so i'll go with the buttons for now. just as a possible enhancement for the future.

1 expand on click , expanding with property does not seem to execute the Expand= handler

I can't seem to reproduce that with the following setup. The Expand handler is ivoked.

 <RadzenTree Data="@nodes" Expand="@OnTreeExpand">
       <RadzenTreeLevel Text="@(data => ((DataNode)data).Data.ToString())" 
             ChildrenProperty="Children" 
             Expanded="@(data => ((DataNode)data).Expanded)"
             HasChildren="@(data => ((DataNode)data).Children != null)" 
       />
</RadzenTree>

@code {
    public class DataNode
    {
        public object Data { get; set; }
        public bool Expanded { get; set; }
        public IEnumerable<DataNode> Children { get; set; }
    }

    DataNode[] nodes = new DataNode[]
    {
        new DataNode
        {
            Data = "Root",
            Expanded = true,
            Children = new [] {
                new DataNode
                {
                    Data = "Child"
                }
            }
        }
    };

   void OnTreeExpand(TreeExpandEventArgs args)
   {
       Console.WriteLine(args.Value);
   }
}

#2 drag and drop

Yes we have this in the roadmap and will probably implement it at some point. It isn't easy indeed but not something we haven't done before :slight_smile:

1 Like

yes, it works with your code, because you set "expanded" already in the datanode at creation time.

i modified it a little bit, at creation time expanded = false, and added a click handler. there i set expanded to true. then it doesn't fire anymore:

<RadzenTree Data="@nodes" Expand="@OnTreeExpand" Change="@OnTreeSelectionChange">
       <RadzenTreeLevel Text="@(data => ((DataNode)data).Data.ToString())" 
             ChildrenProperty="Children" 
             Expanded="@(data => ((DataNode)data).Expanded)"
             HasChildren="@(data => ((DataNode)data).Children != null)" 
       />
</RadzenTree>

@code {

public class DataNode
{
    public object Data { get; set; }
    public bool Expanded { get; set; }
    public IEnumerable<DataNode> Children { get; set; }
}

DataNode[] nodes = new DataNode[]
{
    new DataNode
    {
        Data = "Root",
        Expanded = false,
        Children = new [] {
            new DataNode
            {
                Data = "Child"
            }
        }
    }
};

void OnTreeExpand(TreeExpandEventArgs args)
{
    Console.WriteLine($"TreeExpand: {args.Value}");
}

void OnTreeSelectionChange(TreeEventArgs args)
{
    Console.WriteLine($"TreeNode selected: {args.Text}");
    DataNode d = args.Value as DataNode;
    d.Expanded = true;
}

the node expands correctly (as seen in the picture), but the handler doesn't fire


in your example this is no problem, but i create nodes at runtime at the expanded event, so in that case it is a problem as they don't get created anymore.

Indeed setting the Expanded property does not trigger the Expand event for the time being. Could you populate the children in the OnTreeSelectionChange handler instead?

RE: Drag and Drop

Good, because you guys need it here:
image

This is very time consuming to move fields around on a form.
... because you have to chase the up/down arrow - because you just moved it.
Move buttons ABOVE the column would be more useful since you already allow selecting the Field.

Of course, DnD will be awesome too.

i fear not, because TreeEventArgs is not the same as TreeExpandedEventArgs, so there is no "args.Children", unless i miss something.

void OnTreeSelectionChange(TreeEventArgs args)
{
   args.Children.Data = cat.CategoryCheckLinks.OrderBy(x => x.Position);
   args.Children.Text = GetTextForCheckNode;
   args.Children.HasChildren = (o) => false;   // level 3 nodes can't expand
   args.Children.Template = TreeCheckTemplate;
}

but we can wait for a fix for the expanded issue, if you will implement one sooner or later.

but we can wait for a fix for the expanded issue, if you will implement one sooner or later.

I don't think we can implement it. One could put Expanded = true in the expand event handler and end with endless loop.

So what exactly are you trying to implement? I got confused. Are you trying to load on demand nodes when the user selects a node instead of expands it?

i want a node to expand when the user clicks the node (instead of having to click the expansion icon on the left). i use the Change= event handler for that, by setting expanded property to true, as you suggested.

the nodes in level 1 and 2 are created by template.

the nodes in level 3 are created dynamically in code, in the Expand= handler of a level 2 node. but the event does not fire, as explained, so they are not created.

I guess we can live with endless Expand events firing if someone does toggle the Expanded property. Will investigate.

just blame it on me if someone knocks up their call stack. :innocent:

just updated to 2.4.1 and now it works. are you really that fast?! many thanks.