jsTree
plugin-based interactive tree component. For more info please visit the plugin's Home or Github Repo.
Name | Description |
---|---|
public constructor()
|
Create a new instance of the Tree class.
Parameters:
|
public refresh()
|
Refresh the tree.
Parameters:
Return:
|
public refreshNode()
|
Refresh a node in the tree (reload child nodes).
Parameters:
Return:
|
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:
Return:
|
public onError()
|
Sets the callback function to be called on error. The callback function receives an error object.
Parameters:
Return:
|
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:
Return:
|
public onReady()
|
Sets the callback function that will be called when the tree initialization is complete. The callback function receives an event object.
Parameters:
Return:
|
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:
Return:
|
public getSelectedNodes()
|
Get an array of all selected nodes.
Parameters:
Return:
|
public getSelectedNode()
|
Get the first node among the selected.
Parameters:
Return:
|
public getPath()
|
Get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array).
Parameters:
Return:
|
public getParentNode()
|
Get parent node.
Parameters:
Return:
|
public renameNode()
|
Rename node.
Parameters:
Return:
|
<!--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);
});
[
{
"parent": "#",
"type": "folder",
"text": "Root",
"id": 1,
"children": 1
}
]
[
{
"parent": 1,
"type": "folder",
"text": "Child1",
"id": 2,
"children": 1
},
{
"parent": 1,
"type": "folder",
"text": "Child2",
"id": 3,
"children": 0
}
]
[
{
"parent": 2,
"type": "file",
"text": "GrandChild1",
"id": 1,
"children": null
}
]
<!--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.
},
},
});
// 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}]
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;
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}});
}
}
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();
}
}