SPFx Reusable PnP React control: TreeView

Overview

After using the PnP reusable React controls, I am privileged to implement a TreeView control along with my colleague Siddharth Vaghasia, guided by Alex Terentiev and Elio Struyf.
This graphical control allows presenting a hierarchical view of information. Each tree item can have a number of subitems. This is often visualized by an indentation in a list. A tree item can be expanded to reveal subitems (if exist), and collapsed to hide subitems.
During this article, we will explore the TreeView control implementation, how to use it in your SPFx solutions.
 
 

The Implementation

The solution structure of this control is as follows:
 

treeView

|–– ITreeItem.ts

|–– TreeView.tsx

|–– TreeItem.tsx

|–– TreeItemActionsControl.tsx

|–– ButtonTreeItemAction.tsx

|–– DropdownTreeItemAction.tsx

 
Now, let us explore these important components of the solution:
ITreeItem.ts
This represents the public properties of the Tree Item, which can be set by an end-user.
 
TreeView.tsx
Main React component which renders the tree structure by calling the first tree item to render, which eventually renders the nested items. The events exposed from control (e.g. onSelect, onExpandCollapse, onRenderItem) are passed to the TreeItem component as a parent callback function.
 
TreeItem.tsx
Renders the controls for TreeItem component. This internally uses Checkbox and Label controls to render the layout of the tree item.
 
TreeItemActionsControl.tsx
Renders the controls for the TreeItem action component. We can set treeItemActionsDisplayMode on TreeView to render action (contextual menu) against tree item. The supported modes are Dropdown and Button.
 
ButtonTreeItemAction.tsx
Renders the context menu as Button action for TreeItem component, when treeItemActionsDisplayMode is set to Button.
 
DropdownTreeItemAction.tsx
Renders the context menu as Dropdown action for TreeItem component, when treeItemActionsDisplayMode is set to Dropdown.
 
 

Set up SPFx Solution

1. Open a command prompt. Create a directory for the SPFx solution.
  1. md spfx-react-treeview-control  
2. Navigate to the above created directory.
  1. cd spfx-react-treeview-control  
3. Run the Yeoman SharePoint Generator to create the solution.
  1. yo @microsoft/sharepoint  
4. Yeoman generator will present you with the wizard by asking questions about the solution to be created.
 
 
 
When prompted:
  • Accept the default spfx-react-treeview-control as your solution name, and then select Enter.
  • Select SharePoint Online only (latest), and then select Enter.
  • Select Use the current folder as the location for the files.
  • Select N to allow the solution to be deployed to all sites immediately.
  • Select N on the question if the solution contains unique permissions.
  • Select WebPart as the client-side component type to be created.
  • Name the web part as TreeViewDemo.
  • Provide the description as Using TreeView control in SPFx Solution.
  • Select the framework to use as React.
5. Yeoman generator will perform the scaffolding process to generate the solution. The scaffolding process will take a significant amount of time.
6. Once the scaffolding process is completed, on the command prompt type below command to open the solution in the code editor of your choice.
  1. code .  

NPM Package

On the command prompt, run below command to include the npm package.
  1. npm install @pnp/spfx-controls-react --save  

Using TreeView control in SPFx Solution

1. Open the React component file at “src\webparts\treeViewDemo\components\TreeViewDemo.tsx”
2. Add below imports.
  1. import { TreeView, ITreeItem, TreeViewSelectionMode, TreeItemActionsDisplayMode } from "@pnp/spfx-controls-react/lib/TreeView";  
3. Let us use some dummy data to render TreeView. In real world scenarios, this can come from SharePoint list or any other data source.
  1. private treeItems = [  
  2.   {  
  3.     key: "R1",  
  4.     label: "Root",  
  5.     subLabel: "This is a sub label for node",  
  6.     iconProps: {  
  7.       iconName: 'SkypeCheck'  
  8.     },  
  9.     actions: [{  
  10.       title: "Get item",  
  11.       iconProps: {  
  12.         iconName: 'Warning',  
  13.         style: {  
  14.           color: 'salmon',  
  15.         },  
  16.       },  
  17.       id: "GetItem",  
  18.       actionCallback: async (treeItem: ITreeItem) => {  
  19.         console.log(treeItem);  
  20.       }  
  21.     }],  
  22.     children: [  
  23.       {  
  24.         key: "1",  
  25.         label: "Parent 1",  
  26.         selectable: false,  
  27.         children: [  
  28.           {  
  29.             key: "3",  
  30.             label: "Child 1",  
  31.             subLabel: "This is a sub label for node",  
  32.             actions: [{  
  33.               title: "Share",  
  34.               iconProps: {  
  35.                 iconName: 'Share'  
  36.               },  
  37.               id: "GetItem",                  
  38.               actionCallback: async (treeItem: ITreeItem) => {  
  39.                 console.log(treeItem);  
  40.               }  
  41.             }],  
  42.             children: [  
  43.               {  
  44.                 key: "gc1",  
  45.                 label: "Grand Child 1",  
  46.                 actions: [{  
  47.                   title: "Get Grand Child item",  
  48.                   iconProps: {  
  49.                     iconName: 'Mail'  
  50.                   },  
  51.                   id: "GetItem",  
  52.                   actionCallback: async (treeItem: ITreeItem) => {  
  53.                     console.log(treeItem);  
  54.                   }  
  55.                 }]  
  56.               }  
  57.             ]  
  58.           },  
  59.           {  
  60.             key: "4",  
  61.             label: "Child 2",  
  62.             iconProps: {  
  63.               iconName: 'SkypeCheck'  
  64.             }  
  65.           }  
  66.         ]  
  67.       },  
  68.       {  
  69.         key: "2",  
  70.         label: "Parent 2"  
  71.       },  
  72.       {  
  73.         key: "5",  
  74.         label: "Parent 3",  
  75.         disabled: true  
  76.       },  
  77.       {  
  78.         key: "6",  
  79.         label: "Parent 4",  
  80.         selectable: true  
  81.       }  
  82.     ]  
  83.   },  
  84.   {  
  85.     key: "R2",  
  86.     label: "Root 2",  
  87.     children: [  
  88.       {  
  89.         key: "8",  
  90.         label: "Parent 5"  
  91.       }  
  92.     ]  
  93.   }  
  94. ];  
4. Use the TreeView control in the render method as follows.
 
  1. public render(): React.ReactElement<ITreeViewDemoProps> {  
  2.   return (  
  3.     <div className={styles.treeViewDemo}>  
  4.       <div className={styles.container}>  
  5.         <div className={styles.row}>  
  6.           <div className={styles.column}>  
  7.             <span className={styles.title}>Tree View PnP Control</span>  
  8.   
  9.             <TreeView  
  10.               items={this.treeItems}  
  11.               defaultExpanded={false}  
  12.               selectionMode={TreeViewSelectionMode.Multiple}  
  13.               selectChildrenIfParentSelected={true}  
  14.               showCheckboxes={true}  
  15.               treeItemActionsDisplayMode={TreeItemActionsDisplayMode.ContextualMenu}  
  16.               defaultSelectedKeys={['R2''6']}  
  17.               onExpandCollapse={this.onExpandCollapseTree}  
  18.               onSelect={this.onItemSelected} />  
  19.           </div>  
  20.         </div>  
  21.       </div>  
  22.     </div>  
  23.   );  
  24. }  
 
5. Implement onExpandCollapse to get the expanded and collapsed tree item.
  1. private onExpandCollapseTree(item: ITreeItem, isExpanded: boolean) {  
  2.   console.log((isExpanded ? "item expanded: " : "item collapsed: ") + item);  
  3. }  
 
6. Implement onSelect to get the selected tree item.
  1. private onItemSelected(items: ITreeItem[]) {  
  2.   console.log("items selected: " + items.length);  
  3. }  
 
 

Custom Rendering

You can fully customize how tree items are rendered by providing the onRenderItem callback function and returning whatever JSX.Element you want.
  1. <TreeView  
  2.                 items={this.treeItems}  
  3.                 defaultExpanded={false}  
  4.                 selectionMode={TreeViewSelectionMode.Multiple}  
  5.                 selectChildrenIfParentSelected={true}  
  6.                 showCheckboxes={true}  
  7.                 treeItemActionsDisplayMode={TreeItemActionsDisplayMode.ContextualMenu}  
  8.                 defaultSelectedKeys={['R2''6']}  
  9.                 onExpandCollapse={this.onExpandCollapseTree}  
  10.                 onSelect={this.onItemSelected}  
  11.                 onRenderItem={this.renderCustomTreeItem}  />  
Implement the onRenderItem as below:
  1. private renderCustomTreeItem(item: ITreeItem): JSX.Element {  
  2.     return (  
  3.       <span>  
  4.         {  
  5.           item.iconProps &&  
  6.           <i className={"ms-Icon ms-Icon--" + item.iconProps.iconName} style={{ paddingRight: '4px' }} />  
  7.         }  
  8.         {item.label}  
  9.       </span>  
  10.     );  
  11.   }  
 

Selection Modes

The selection mode can be set by using property TreeViewSelectionMode. The possible values are Single, Multiple, None.
 

Summary

In this article, we explored the implementation and practical use of TreeView control in the SPFx web part. Hope this reusable control will help you in your future solutions. Share your feedback!
 

Code Download