Overview

jsTree plugin-based interactive tree component. For more info please visit the plugin's Home or Github Repo.

Reference

Instance Methods
Name Description
public constructor() Create a new instance of the Tree class.
Parameters:
  • element: string|HTMLDivElement|JQuery
    HTMLDivElement selector, element, or JQuery object.
  • options.ajax: Record<string, string|((node: any) => string)|TreeAjaxOptions>
    Ajax options for creating, deleting, and renaming nodes. This option is required.
    • children: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to retrieve child nodes (GET).
      The _PARENT_NODE_ID_ in the URL is replaced by the currently selected folder node ID.
      If you want to make the URL dynamic, use the callback function.
      Also, use the data option if you want to add or change the data to be sent.
                                      // Send to "GET /api/tree/:_PARENT_NODE_ID_"
      children: '/api/tree/_PARENT_NODE_ID_',
      
      // Dynamic URL generation.
      children: node => {
        // This is an example of simply retrieving tree data in JSON without using the server side.
        if (node.id === '#')
          // If the node ID is "#", the data of the root node itself is returned.
          return 'tree-root-itself.json';
        else if (node.id == 1)
          // If the node ID is "1", the data of the child node associated with the root node (ID=1) is returned.
          return 'tree-roots-children.json';
        else if (node.id == 2)
          // If the node ID is "2", the data of the child node (ID=2) associated with the child1 node is returned.
          return 'tree-child1s-children.json';
      }
      
      // Added data to be sent.
      children: {
        url: '/api/tree/_PARENT_NODE_ID_',
        data: (data, node) => {
          data.extra = 'Extra';
        }
      }
      
      // Dynamically generate URLs and add data to be sent.
      children: {
        url: node => {
          // This is an example of simply retrieving tree data in JSON without using the server side.
          if (node.id === '#')
            // If the node ID is "#", the data of the root node itself is returned.
            return 'tree-root-itself.json';
          else if (node.id == 1)
            // If the node ID is "1", the data of the child node associated with the root node (ID=1) is returned.
            return 'tree-roots-children.json';
          else if (node.id == 2)
            // If the node ID is "2", the data of the child node (ID=2) associated with the child1 node is returned.
            return 'tree-child1s-children.json';
        }
        data: (data, node) => {
          data.extra = 'Extra';
        }
      }
                                    
    • createFolder: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to create a folder node (POST).
      The _PARENT_NODE_ID_ in the URL is replaced by the currently selected folder node ID.
      The data to be sent is {"text": new node name} but can be added or changed with the data option.
      If you want to make the URL dynamic, use the callback function.
                                      // Send to "POST /api/tree/folder/:_PARENT_NODE_ID_".
      createFolder: '/api/tree/folder/_PARENT_NODE_ID_'
                                    
    • deleteFolder: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to delete a folder node (DELETE).
      URL to request. The _CURRENT_NODE_ID_ in the URL will be replaced by the current node ID.
      If you want to make the URL dynamic, use the callback function.
      Also, use the data option if you want to add or change the data to be sent.
                                      // Send to "DELETE /api/tree/folder/:_CURRENT_NODE_ID_".
      deleteFolder: '/api/tree/folder/_CURRENT_NODE_ID_'
                                    
    • renameFolder: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to rename a folder node (PUT).
      The _CURRENT_NODE_ID_ in the URL will be replaced by the current node ID.
      The data to be sent is {"text": changed node name}, but can be added or changed with the data option.
      If you want to make the URL dynamic, use the callback function.
                                      // Send to "PUT /api/tree/folder/:_CURRENT_NODE_ID_".
      renameFolder: '/api/tree/folder/_CURRENT_NODE_ID_'
                                    
    • createFile: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to create a file node (POST).
      The _PARENT_NODE_ID_ in the URL is replaced by the currently selected folder node ID.
      The data to be sent is {"text": new node name}, but can be added or changed with the data option.
      If you want to make the URL dynamic, use the callback function.
                                      // Send to "POST /api/tree/file/:_PARENT_NODE_ID_".
      createFile: '/api/tree/file/_PARENT_NODE_ID_'
                                    
    • deleteFile: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to delete a file node (DELETE).
      The _CURRENT_NODE_ID_ in the URL will be replaced by the current node ID.
      If you want to make the URL dynamic, use the callback function.
      Also, use the data option if you want to add or change the data to be sent.
                                      // Send to "DELETE /api/tree/file/:_CURRENT_NODE_ID_".
      deleteFile: '/api/tree/file/_CURRENT_NODE_ID_'
                                    
    • renameFile: string|((node: any) => string)|TreeAjaxOptions
      Ajax option to rename a file node (PUT).
      The _CURRENT_NODE_ID_ in the URL will be replaced by the current node ID.
      The data to be sent is {"text": changed node name}, but can be added or changed with the data option.
      If you want to make the URL dynamic, use the callback function.
                                      // Send to "PUT /api/tree/file/:_CURRENT_NODE_ID_".
      renameFile: '/api/tree/file/_CURRENT_NODE_ID_'
                                    
  • options.readonly?: boolean
    Read-only. If true, the context menu for changing nodes is not displayed. Default is false.
  • options.maxDepth?: number
    Maximum number of levels in the tree. Default is 2.
  • options.folderMaxlen?: number
    Maximum length of folder node name. Default is 20.
  • options.fileMaxlen?: number
    Maximum length of file node name. Default is 20.
  • options.nodeTypes?: Record<string, {type: string, icon: string}>
    Node type.
    • folder: {type: string, icon: string}
      Folder node options.
      • type: string
        Folder node identifier. Default is "folder".
      • icon: string
        Folder node icons. Default is "fa fa-folder text-warning".
    • file: {type: string, icon: string}
      File node options.
      • type: string
        File node identifier. Default is "file".
      • icon: string
        File node icons. Default is "fa fa-file text-white".
  • options.language?: Record<string, string>
    Strings used in the user interface.
    • createFolderMenu?: string
      Folder node creation menu. Default is "Create folder".
    • createFolderSuccessful?: string
      Folder node creation success message. The _FOLDER_ in the text is set to the name of the created folder. Default is "_FOLDER_ has been created.".
    • deleteFolderMenu?: string
      Folder node deletion menu. Default is "Delete folder".
    • deleteFolderConfirmation?: string
      Folder node deletion confirmation message. The _FOLDER_ in the text is set to the name of the folder to be deleted. Default is "Are you sure you want to delete _FOLDER_?".
    • deleteFolderButton?: string
      Folder node deletion confirmation OK button. Default is "Delete the folder".
    • deleteFolderCancelButton?: string
      Folder node deletion confirmation cancel button. Default is "Cancel".
    • deleteFolderSuccessful?: string
      Folder node deletion success message. The _FOLDER_ in the text is set to the name of the folder to be deleted. Default is "_FOLDER_ has been deleted.".
    • renameFolderManu?: string
      Folder node rename menu. Default is "Rename folder".
    • newFolderName?: string
      Default new folder node name. Default is "New Folder".
    • createFileMenu?: string
      File node creation menu. Default is "Create file".
    • createFileSuccessful?: string
      File node creation success message. The _FILE_ in the text is set to the name of the created file. Default is "_FILE_ has been created.".
    • deleteFileMenu?: string
      File node deletion menu. Default is "Delete file".
    • deleteFileConfirmation?: string
      File node deletion confirmation message. The _FILE_ in the text is set to the name of the file to be deleted. Default is "Are you sure you want to delete _FILE_?".
    • deleteFileButton?: string
      OK button for file node deletion confirmation. Default is "Delete file".
    • deleteFileCancelButton?: string
      Cancel button for confirming file node deletion. Default is "Cancel".
    • deleteFileSuccessful?: string
      Success message for deleting a file node. The _FILE_ in the text is set to the name of the file to be deleted. Default is "_FILE_ has been deleted.".
    • renameFileManu?: string
      File node rename menu. Default is "Rename file".
    • newFileName?: string
      Default new file node name. Default is "New File".
    • unknownErrorTitle?: string
      Unexpected error title. Default is "An unexpected error has occurred.".
    • unknownErrorMessage?: string
      Unexpected error message. Default is "The process was interrupted due to an error. Please try again.".
public refresh() Refresh the tree.
Parameters:
  • skipLoading: boolean
    An option to skip showing the loading indicator. Default is false.
  • forgetState: boolean
    If set to true state will not be reapplied, if set to a function (receiving the current state as argument) the result of that function will be used as state. Default is false.
Return:
  • Tree
public refreshNode() Refresh a node in the tree (reload child nodes).
Parameters:
  • nodeObject: any
    The node.
Return:
  • Tree
public onSelected() Sets the callback function to be called when a node is selected. The callback function receives an event object and a node object.
Parameters:
  • handler: (event: any, node: any) => void
    Callback function.
Return:
  • Tree
public onError() Sets the callback function to be called on error. The callback function receives an error object.
Parameters:
  • handler: (error: any) => void
    Callback function.
Return:
  • Tree
public onFetch() Sets the callback function that will be called when a child node of the selected node is retrieved from the server side.
The callback function receives the data retrieved from the server side and can modify or add data.
Parameters:
  • handler: (nodeData: any) => void
    Callback function.
Return:
  • Tree
public onReady() Sets the callback function that will be called when the tree initialization is complete. The callback function receives an event object.
Parameters:
  • handler: (event: any) => void
    Callback function.
Return:
  • Tree
public onCreateFileHook() Sets the function that hooks the operation to create a new node.
If the hook is not set, the new node will be added to the tree immediately.
The hook function you set must return the ID and text of the newly created node. Also, return false, null, or undefined if you want to cancel the creation.
Parameters:
  • hook: (parent: any) => Promise<{id: string|number, text: string, [key: string]: any}|null|undefined|false>
    Hook function.
Return:
  • Tree
public getSelectedNodes() Get an array of all selected nodes.
Parameters:
  • full: boolean
    if set to true the returned array will consist of the full node objects, otherwise - only IDs will be returned.
  • index?: number
    Index of the node to be acquired. Default is none and all nodes are retrieved.
Return:
  • any|null Selected node.
public getSelectedNode() Get the first node among the selected.
Parameters:
  • full: boolean
    if set to true the returned array will consist of the full node objects, otherwise - only IDs will be returned.
Return:
  • any|null Selected node.
public getPath() Get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array).
Parameters:
  • nodeObject: any
    The node.
  • glue: string|undefined
    If you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned.
  • ids: boolean
    If set to true build the path using ID, otherwise node text is used.
Return:
  • string[]|string Path of the node.
                    tree.onSelected((event, node) => {
  // Get the path of the node.
  tree.getPath(node);// ['Root', 'Folder#1', 'Folder#1_1']
  tree.getPath(node, '/');// 'Root/Folder#1/Folder#1_1'
  tree.getPath(node, '/', true);// '1/2/3'
});
                  
public getParentNode() Get parent node.
Parameters:
  • nodeObject: any
    The node.
Return:
  • any Parent node.
public renameNode() Rename node.
Parameters:
  • nodeObject: any
    The node, you can pass an array to rename multiple nodes to the same name.
  • text: string
    new text value.
Return:
  • Tree

Basic

Here is an example of a read-only tree that does not use server-side processing and contains data via JSON.
Node ID: -
Path: -
Text: -
Type: -
              <!--begin::Tree-->
<div id="basicTree"></div>
<!--end::Tree-->
<!--begin::Selected node information-->
<div class="pt-5">
  <div class="mb-2">Node ID: <span id="nodeId" class="text-dark">-</span></div>
  <div class="mb-2">Path: <span id="nodePath" class="text-dark">-</span></div>
  <div class="mb-2">Text: <span id="nodeText" class="text-dark">-</span></div>
  <div class="mb-2">Type: <span id="nodeType" class="text-dark">-</span></div>
</div>
<!--end::Selected node information-->
            
              import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
const basicTree = new components.Tree(document.getElementById('basicTree'), {
  // Ajax Option.
  ajax: {
    children: node => {
      // This is an example of simply retrieving tree data in JSON without using the server side.
      if (node.id === '#')
        // If the node ID is "#", the data of the root node itself is returned.
        return 'json/tree-root-itself.json';
      else if (node.id == 1)
        // If the node ID is "1", the data of the child node associated with the root node (ID=1) is returned.
        return 'json/tree-roots-children.json';
      else if (node.id == 2)
        // If the node ID is "2", the data of the child node (ID=2) associated with the child1 node is returned.
        return 'json/tree-child1s-children.json';
    },
  },
  readonly: true,
  nodeTypes: {
    file: {
      icon: 'fa-solid fa-computer text-white',// Changed file node icons.
    },
  },
});

basicTree
  .onSelected((event, node) => {
    // Display selected node information.
    document.getElementById('nodeId').textContent = node.id;
    document.getElementById('nodePath').textContent = basicTree.getPath(node, '/');
    document.getElementById('nodeText').textContent = node.text;
    document.getElementById('nodeType').textContent = node.type;
  })
  .onError(error => {
    // Displays errors encountered in tree operations.
    alert(error);
  });
            
This root node of the tree loads data by Ajax (tree-root-itself.json). The latest data that has been loaded is shown below. This data will update automatically as any additional data is loaded.
              [
  {
    "parent": "#",
    "type": "folder",
    "text": "Root",
    "id": 1,
    "children": 1
  }
]
            
Child nodes associated with the route of the tree loads data by Ajax (tree-roots-children.json). The latest data that has been loaded is shown below. This data will update automatically as any additional
              [
  {
    "parent": 1,
    "type": "folder",
    "text": "Child1",
    "id": 2,
    "children": 1
  },
  {
    "parent": 1,
    "type": "folder",
    "text": "Child2",
    "id": 3,
    "children": 0
  }
]
            
Child nodes associated with Child1 of the tree loads data by Ajax (tree-child1s-children.json). The latest data that has been loaded is shown below. This data will update automatically as any additional
              [
  {
    "parent": 2,
    "type": "file",
    "text": "GrandChild1",
    "id": 1,
    "children": null
  }
]
            

Server-side processing

In this example, the tree data is retrieved from the server side.
It also activates the context menu for creating, modifying, and deleting nodes, and requests the server-side to create, modify, and delete them.
Server-side processing is required to make this demo work. Click here to learn how to build server-side processing.
              <!--begin::Tree-->
<div id="serverSideProcessingTree"></div>
<!--end::Tree-->
            
              import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
const serverSideProcessingTree = new components.Tree(document.getElementById('serverSideProcessingTree'), {
  ajax: {
    children: '/api/tree/_PARENT_NODE_ID_',
    createFolder: '/api/tree/folder/_PARENT_NODE_ID_',
    deleteFolder: '/api/tree/folder/_CURRENT_NODE_ID_',
    renameFolder: '/api/tree/folder/_CURRENT_NODE_ID_',
    createFile: '/api/tree/file/_PARENT_NODE_ID_',
    deleteFile: '/api/tree/file/_CURRENT_NODE_ID_',
    renameFile: '/api/tree/file/_CURRENT_NODE_ID_',
  },
  nodeTypes: {
    file: {
      icon: 'fa-solid fa-computer text-white',// Changed file node icons.
    },
  },
});
            
This tree loads data by Ajax. The latest data that has been loaded is shown below. This data will update automatically as any additional data is loaded.
              // Send to "GET /api/tree/%23" and load the root's own data.
[{"parent": "#", "type": "folder", "text": "Root", "id": 1, "children": 1}]

// Send to "GET /api/tree/1" and load the data of the root's child nodes.
[
  {"parent": 1, "type": "folder", "text": "Child1", "id": 2, "children": 1},
  {"parent": 1, "type": "folder", "text": "Child2", "id": 3, "children": 0}
]

// Send to "GET /api/tree/2" and load the data of Child1 node's child nodes.
[{"parent": 2, "type": "file", "text": "GrandChild1", "id":1, "children": null}]
            
This is a route script used in server-side processing created with express-sweet, which extends Node.js express.
              const router = require('express').Router();
const {body, validationResult} = require('express-validator');
const FolderModel = require('../../models/FolderModel');
const FileModel = require('../../models/FileModel');

// Check validation results.
const checkValidationResult = (req, res, next) => {
  const result = validationResult(req);
  if (result.isEmpty())
    // If there are no errors, go to the next process.
    next();
  else
    // If there is an error, return a 400 error.
    res.status(400).send();
}

// Get node data. If the node ID of the parameter is "#", the root node's own data is acquired; otherwise, the child nodes of the node corresponding to the ID are acquired.
router.get('/:folderId(\\d+|%23)', async (req, res) => {
  const children = await FolderModel.findChildren(req.params.folderId);
  res.json(children);
});

// Create a node.
router.post('/:type(folder|file)/:folderId(\\d+)', [
  body('text').trim().not().isEmpty(),
  checkValidationResult
], async (req, res) => {
  let newNode;
  if (req.params.type === 'folder')
    newNode = await FolderModel.createNode(req.params.folderId, req.body.text);
  else
    newNode = await FileModel.createNode(req.params.folderId, req.body.text);
  res.json({id: newNode.id});
});

// Rename node.
router.put('/:type(folder|file)/:nodeId(\\d+)', [
  body('text').trim().not().isEmpty(),
  checkValidationResult
], async (req, res) => {
  if (req.params.type === 'folder')
    await FolderModel.renameNode(req.params.nodeId, req.body.text);
  else
    await FileModel.renameNode(req.params.nodeId, req.body.text);
  res.json({});
});

// Delete node.
router.delete('/:type(folder|file)/:nodeId(\\d+)', async (req, res) => {
  if (req.params.type === 'folder')
    await FolderModel.deleteNode(req.params.nodeId);
  else
    await FileModel.deleteNode(req.params.nodeId);
  res.json({});
});

module.exports = router;
            
This is a Model script used in server-side processing created with express-sweet, which extends Node.js express.
              const Model = require('express-sweet').database.Model;

module.exports = class extends Model {
  static get table() {
    return 'folder';
  }

  static get attributes() {
    return {
      id: {
        type: this.DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      parent: this.DataTypes.INTEGER,
      text: this.DataTypes.STRING,
    };
  }

  static async createNode(parent, text) {
    return super.create({parent, text});
  }

  static async deleteNode(id) {
    const node = await super.findOne({where: {id}});
    if (!node)
      throw new Error('Data not found');
    return node.destroy();
  }

  static async renameNode(id, text) {
    const node = await super.findOne({where: {id}});
    if (!node)
      throw new Error('Data not found');
    node.text = text;
    await node.save();
  }

  static async findChildren(parent) {
    return parent === '#' ?
        super.query(
          `SELECT
            '#' parent, 'folder' type, \`text\`, id, if (counter.folderId IS NOT NULL, true, false) children
          FROM
            folder LEFT JOIN (
              SELECT
                folderId
              FROM
                (SELECT parent folderId FROM folder WHERE parent IS NOT NULL
                  UNION ALL SELECT folderId FROM file) t
              GROUP BY
                folderId
            ) counter ON folder.id = counter.folderId
          WHERE
            folder.parent is null`, {type: this.QueryTypes.SELECT}) :
        super.query(
          `SELECT
            parent, 'folder' type, \`text\`, id, if (counter.folderId IS NOT NULL, true, false) children
          FROM
            folder LEFT JOIN (
              SELECT
                folderId
              FROM
                (SELECT parent folderId FROM folder WHERE parent IS NOT NULL
                  UNION ALL SELECT folderId FROM file) t
              GROUP BY
                folderId
            ) counter ON folder.id = counter.folderId
          WHERE
            folder.parent = :parent
          UNION ALL
          SELECT
            folderId parent, 'file' type, \`text\`, id, NULl children
          FROM
            file
          WHERE
            folderId = :parent`, {type: this.QueryTypes.SELECT, replacements: {parent}});
  }
}
            
This is a Model script used in server-side processing created with express-sweet, which extends Node.js express.
              const Model = require('express-sweet').database.Model;

module.exports = class extends Model {
  static get table() {
    return 'file';
  }

  static get attributes() {
    return {
      id: {
        type: this.DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      folderId: this.DataTypes.INTEGER,
      text: this.DataTypes.STRING,
    };
  }

  static async createNode(folderId, text) {
    return super.create({folderId, text});
  }

  static async deleteNode(id) {
    const node = await super.findOne({where: {id}});
    if (!node)
      throw new Error('Data not found');
    return node.destroy();
  }

  static async renameNode(id, text) {
    const node = await super.findOne({where: {id}});
    if (!node)
      throw new Error('Data not found');
    node.text = text;
    await node.save();
  }
}