Tree View

A tree view making use of a vertical menu

Purpose of Component

RfTreeView was added to RForge to support tree like data structures. It can be used as a folder traversal tool, simple side menu, basic accordian, etc.

NOTE: Eventually an accordian component will be added as a simplified / streamline way.

Basic Usage

RfTreeView is built upon Bulma's .menu component. With that the naming of sub components for RfTreeView should match the naming convention of the Bulma menu. For example: Use RfTreeViewLabel to add a .menu-label.

RfTreeView is currently a strongly typed component. The tree and all nodes are required to state the type via a generic parameter TTreeItemNodeData. This data is stored in RfTreeNode.NodeData. The node data is only transmitted via the following event callbacks: NodeClick, NodeExpandChange, and NodeSelectChange.

Below shows the hierarchical of sub components including when to use which.

RfTreeView                
↳ RfTreeLabel
  RfTreeList
  ↳ RfTreeNode
    ↳ RfTreeNode
      RfTreeNode
    RfTreeNode

Overall Usage

RfTreeView exposes a few parameters to change how all subsequent nodes work. The three are AllowSelection, AllowExpand, AllowClick. By default all three are on.

All three are live settings, not first-render snapshots. RfTreeView hands the current value to every descendant node each time its parameters change, so a value you flip after the tree is on screen takes effect on the next click, in both directions. Binding one to app state (a save in progress, a permission that got revoked and later restored) is the intended usage.

Allow Selection

When set to true all sub RfTreeNode when clicked will be marked as active. RfTreeView supports only one selection at a time. The event RfTreeNode.NodeSelectChange will fire for both selections and deselections.

NOTE: Setting this to false will still allow RfTreeNode.NodeClick to fire as well as any RfTreeNode.LinkUrl that are set.

Allow Expand

When set to true, all sub RfTreeNode elements will have the ability to expand and collapse their child nodes. The event RfTreeNode.NodeExpandChange will fire whenever a node is expanded or collapsed.

NOTE: Setting this to false will disable the expand/collapse functionality for all nodes. The nodes will still show the expand / collapse icon but mark them as disabled. The RfTreeNode.NodeClick event will still fire when clicking on the node.

Allow Click

When set to true, all sub RfTreeNode elements will respond to click events. This allows for custom actions to be performed when a node is clicked. The event RfTreeNode.NodeClick will fire whenever a node is clicked.

Setting it to false suppresses NodeClick and nothing else. The click is still a normal node click: selection and expansion still run under AllowSelection and AllowExpand, the expand toggle button still toggles, and the rendered node looks the same.

The flag is re-read every time the tree's parameters are set, so changing it after the first render takes effect on both edges. This is the behavior fixed in #98: the value used to be copied into the tree's cascaded context at initialization only, which meant a later AllowClick=false was silently ignored and the nodes stayed clickable.

NOTE: Any RfTreeNode.LinkUrl will still work.
Example: node actions off while a save is running
<RfTreeView TTreeItemData="Movie" AllowClick="@(!SaveInProgress)">
    <Nodes>
        <RfTreeNode TTreeItemData="Movie" NodeData="@movie" NodeClick="@OnNodeClick">
            <Node>@movie.Name</Node>
        </RfTreeNode>
    </Nodes>
</RfTreeView>

While SaveInProgress is true the tree still selects, expands and follows links, but OnNodeClick stops firing and resumes the moment the flag goes back to true. No rebuild of the tree and no re-render trick is involved.

Live version: Tree View Basic Example binds AllowClick to a checkbox.

Label / Heading

Use RfTreeLabel to create a heading within the tree view. These can be nested within a RfTreeList or RfTreeNode, but look best as a direct child of RfTreeView.

RfTreeLabel wraps the child content of it within a p.menu-label and supports CssClass parameter which is applied to the wrapping p tag.

RfTreeLabel will automatically render as a skeleton variant if added within RfTreeView.PrerenderNodes applying the same Css Classes. You can provide text for the label or leave it empty. If provided the text will be rendered and will grow the skeleton to fit the text.

Example:

<RfTreeView>
    <PrerenderNodes>
        <RfTreeLabel CssClass="is-size-4">Root Label</RfTreeLabel>
        <RfTreeLabel />
        ....
    </PrerenderNodes>
    <Nodes>
        <RfTreeLabel CssClass="is-size-4">Root Label</RfTreeLabel>
        <RfTreeLabel>Some other Label</RfTreeLabel>
        ....
    </Nodes>
</RfTreeView>

RfTreeList

Use RfTreeList to wrap the root node. The purpose of this component is to create the initial ul.menu-list element.

RfTreeList supports one paramater CssClass. Use this parameter to add additioanl classes to the wrapping u.menu-list.

Example:

<RfTreeView>
    <Nodes>
        <RfTreeList>
            ... RfTreeNode(s) ...
        </RfTreeList>
    </Nodes>
</RfTreeView>

Tree Nodes

RfTreeNode is how to add an interative, selectable, and expandable element to RfTreeView. All interactions with the RfTreeView are made through RfTreeNode.

Parameters

TTreeItemData
generic Required

A generic type to use as part of event callbacks.

IsSelected
bool two-way binding default: true

If true the node will include .is-active to the node.

NodeCss
string default: null

Css that is applied to the node element.

IsExpanded
bool two-way binding default: false

Used in conjuction with ShowExpandIcon. If true the children render fragment will be rendered.

IsLoading
bool default: false

Used in conjuction with ShowExpandIcon. If true the expand button will be marked as .is-loading and the children will be replace with a set number of skeleton nodes based on LoadingNodeCount

LoadingNodeCount
bool default: 3

Used in conjuction with ShowExpandIcon and IsLoading.
If IsLoading == true && ShowExpandIcon == true && IsExpanded == true the component will render x number of skeleton nodes where x equals LoadingNodeCount.

ExpandedIconCss
string default: fa-solid fa-chevron-down

The css icons to show on the expand button while expanded.

IconCss
string default: null

The css icons to show on the expand button while collapsed or an icon to show while the node is a leaf node.

Node
RenderFragment {TTreeItemData}

The render fragment used to render the node text. It is optional: a node without it still renders its node markup (expand icon, link, children) with an empty label instead of failing.

Children
RenderFragment

The render fragment used to render the children nodes. This will convert the node icon into an expander toggle.

LinkUrl
string

The url to add to the node.

AllowSelection
bool? default: null

Overrides RfTreeView.AllowSelection for this node only. Left null the node follows the tree, including later changes to it.

AllowExpand
bool? default: null

Overrides RfTreeView.AllowExpand for this node only. Left null the node follows the tree, including later changes to it.

AllowClick
bool? default: null

Overrides RfTreeView.AllowClick for this node only. Left null the node follows the tree flag and picks up its changes. Set it to a value and the node stops consulting the tree, so the tree level re-sync no longer reaches this node: change the override by re-rendering the node with a new value.

NodeClick
EventCallback{TreeViewNodeOnClickEventArgs{TTreeItemData}} Event

An event that is called when the node is clicked. NOTE: This will not be called when toggling the expand.

NodeExpandChange
EventCallback{TreeViewNodeIsExpandEventArgs{TTreeItemData}} Event

An event that is called when the RfTreeNode.IsExpanded changes within the RfTreeView component.

NodeSelectChange
EventCallback{TreeViewNodeIsSelectedEventArgs{TTreeItemData}} Event

An event that is called when the RfTreeNode.IsSelected changes within the RfTreeView component.

Expansion, Lazy Loads and In Flight Toggles

NodeExpandChange is awaited by the node. It fires once the expansion state has been applied (including the two way IsExpandedChanged push) and the node has re-rendered, so a lazy child load started from the handler always runs against a node that is already expanded.

Because that handler is awaited, a slow load keeps the toggle open behind it: while your handler is still running the node treats the change as in flight and ignores every further toggle, whether it arrives on the toggle button or on the node itself (a node click expands too). An ignored toggle raises no second NodeExpandChange and never collapses a node in the middle of its own load, which is what a fast double click used to do: two misordered callbacks and children left rendered under a collapsed node. The node simply keeps the state of the load that is running, and aria-expanded on the toggle button reports exactly that state.

The in flight state belongs to the node, not the tree, and it is released as soon as your handler returns, including when the handler throws. The next click after a finished or failed load toggles normally, so a guard can never wedge a node.

NOTE: The dropped toggle is silent on purpose: the component keeps no queue and replays nothing. Drive RfTreeNode.IsLoading from your handler so the toggle button shows its spinner and the user can see why the second click did nothing.

Example: lazy loaded children

<RfTreeNode TTreeItemData="Genre" NodeData="@genre" IsLoading="@IsLoadingGenre"
            NodeExpandChange="@LoadMovies">
    <Node>@genre.Name</Node>
    <Children>
        ... built from MoviesByGenre once the load below has completed ...
    </Children>
</RfTreeNode>
private async Task LoadMovies(TreeViewNodeIsExpandEventArgs<Genre> args)
{
    if (args.IsExpanded == false || MoviesByGenre.ContainsKey(args.NodeData))
    {
        return;
    }

    IsLoadingGenre = true;
    MoviesByGenre[args.NodeData] = await MovieService.GetByGenre(args.NodeData.Name);
    IsLoadingGenre = false;
}

Clicking the chevron twice while GetByGenre is still in the air produces exactly one NodeExpandChange, one load, and a node that ends expanded with its children under it.

Prerendering Entire Tree

RfTreeView supports two forms of skeleton. First you can make use of RfTreeView.PrerenderNodes render fragment to render an example of the tree view. To use the prerender render fragment the RfTreeView.ShowAsPrerender must be set to true.

Use the same set of sub components when building out the PrerenderNodes.

Example

Below shows a simple example of setting up RfTreeView.ShowAsPrerender making use of a label and four nodes.

<RfTreeView ShowAsPrerender="true" TTreeItemData="object">
    <PrerenderNodes>
        <RfTreeLabel />
        <RfTreeNode TTreeItemData="object" />
        <RfTreeNode TTreeItemData="object" />
        <RfTreeNode TTreeItemData="object">
            <Children>
                <RfTreeNode TTreeItemData="object" />
            </Children>
        </RfTreeNode>
    </PrerenderNodes>
</RfTreeView>

CSS Variables

RfTreeView adds additional CSS variables applied at the root .menu.rf-treeview.

CSS Variable Value
--bulma-menu-nested-list-padding-left .25em
--rf-menu-list-link-label-padding-left .25em
--rf-menu-list-link-icon-padding-horizontal .5em
--rf-menu-list-link-icon-width 1.25em
--rf-menu0list-link-icon-font-size .75em

How to Setup

Setting up RForge is simple. Follow the below steps to add RForge Blazor components to your project.

  1. Install the Nuget package
  2. Import the namespaces (optional)
  3. Register RForge services
  4. Include Bulma CSS

Install the Nuget Package

RForge Blazor components are installed via Nuget. There are two packages. One for the blazor app and one for the library if any.

Blazor Project Installation

Command Line
dotnet add package RForge.Blazor
Package Manager Console
Install-Package RForge.Blazor

Library Project Installation

This is optional but may be useful if you want to use common enums across the backend and frontend.

Command Line
dotnet add package RForge.Abstractions
Package Manager Console
Install-Package RForge.Abstractions

Import the namespaces

To simplify namespacing while using RForge, the package is designed with most all components sitting within the root RForge namespace.

You may want to include the following namespaces in your _import.razor for ease of use.

@using RForgeBlazor
@using RForgeBlazor.Services
@using RForge.Abstractions
Namespace Purpose
RForgeBlaozr Houses all of the blazor components.
RForgeBlaozr.Services Houses interfaces you may need to communicate with certain components.
RForgeBlaozr.Abstractions Common enums used through out the components.

Register RForge Services

In order for some components to work they must first have their services registered.

Open up Program.cs and add the following line before the var app = builder.Build();.

Heads Up!
If your project is using the multi project solution where one project is server and the other is client. You will need to register the services in both projects.

using RForgeBlazor;
//...
builder.Services.AddRfForgeBlazorServices();
//...
var app = builder.Build();
    

Include Bulma CSS

RForge makes use of Bulma CSS to stylize the components. Add / install Bulma and add the CSS file to the head. Link to download: Bulma releases.

An unhandled error has occurred. Reload 🗙