jQuery tree plugin for creating interactive tree components with drag & drop, inline editing, checkboxes, search, and customizable node types
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Customizable right-click context menus with support for custom items, submenus, and conditional visibility. The context menu plugin provides an intuitive way to access tree operations and custom actions through right-click interactions.
Configuration options for the context menu plugin behavior.
/**
* Context menu plugin configuration
*/
interface ContextMenuConfig {
/** Select node when showing context menu (default: true) */
select_node?: boolean;
/** Show menu at node position vs cursor position (default: true) */
show_at_node?: boolean;
/** Menu items configuration */
items?: object|function;
}
interface ContextMenuItem {
/** Display label */
label?: string;
/** Icon class */
icon?: string;
/** Action function */
action?: function;
/** Submenu items */
submenu?: {[key: string]: ContextMenuItem};
/** Separator before item */
separator_before?: boolean;
/** Separator after item */
separator_after?: boolean;
/** Conditional visibility */
visible?: boolean|function;
/** Conditional enablement */
disabled?: boolean|function;
/** Custom CSS class name */
_class?: string;
/** Shortcut key display */
shortcut?: string;
/** Shortcut key combination */
shortcut_label?: string;
}
// Usage in tree initialization
const config = {
"plugins": ["contextmenu"],
"contextmenu": {
"select_node": true,
"show_at_node": true,
"items": contextMenuBuilder
}
};Usage Examples:
// Initialize tree with context menu
$("#tree").jstree({
"core": {
"data": ["Item 1", "Item 2", "Folder"],
"check_callback": true
},
"plugins": ["contextmenu"],
"contextmenu": {
"items": function(node) {
return {
"create": {
"label": "Create",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
const obj = inst.get_node(data.reference);
inst.create_node(obj, {}, "last", function(new_node) {
setTimeout(function() { inst.edit(new_node); }, 0);
});
}
}
};
}
}
});
// Get instance for context menu operations
const tree = $("#tree").jstree(true);Methods for programmatically controlling context menus.
/**
* Show context menu programmatically
* @param obj - Node to show menu for
* @param x - X coordinate (optional)
* @param y - Y coordinate (optional)
* @param e - Original event (optional)
*/
show_contextmenu(obj: string|object, x?: number, y?: number, e?: Event): void;Usage Examples:
// Show context menu programmatically
tree.show_contextmenu("node_1");
// Show at specific coordinates
tree.show_contextmenu("node_1", 100, 200);
// Show context menu on custom trigger
$("#custom-button").click(function() {
const selectedNodes = tree.get_selected();
if (selectedNodes.length > 0) {
tree.show_contextmenu(selectedNodes[0]);
}
});Configuration for static menu items that don't change based on context.
/**
* Static menu items configuration
*/
interface StaticMenuItems {
[key: string]: ContextMenuItem;
}Usage Examples:
// Static menu items
$("#tree").jstree({
"plugins": ["contextmenu"],
"contextmenu": {
"items": {
"create": {
"label": "Create",
"icon": "fa fa-plus",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
const obj = inst.get_node(data.reference);
inst.create_node(obj, {}, "last", function(new_node) {
inst.edit(new_node);
});
}
},
"rename": {
"label": "Rename",
"icon": "fa fa-edit",
"shortcut": "F2",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
const obj = inst.get_node(data.reference);
inst.edit(obj);
}
},
"delete": {
"label": "Delete",
"icon": "fa fa-trash",
"shortcut": "Del",
"separator_before": true,
"action": function(data) {
const inst = $.jstree.reference(data.reference);
const obj = inst.get_node(data.reference);
if (confirm("Delete " + obj.text + "?")) {
inst.delete_node(obj);
}
}
}
}
}
});Configuration for menu items that change based on the selected node or context.
/**
* Dynamic menu items function
* @param node - Node that was right-clicked
* @returns Menu items object
*/
type DynamicMenuFunction = (node: object) => {[key: string]: ContextMenuItem};Usage Examples:
// Dynamic menu based on node type
$("#tree").jstree({
"plugins": ["contextmenu", "types"],
"contextmenu": {
"items": function(node) {
const items = {};
// Common items for all nodes
items.rename = {
"label": "Rename",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.edit(data.reference);
}
};
// Type-specific items
if (node.type === "folder") {
items.create_folder = {
"label": "New Folder",
"icon": "fa fa-folder",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.create_node(data.reference, {
"text": "New Folder",
"type": "folder"
}, "last", function(new_node) {
inst.edit(new_node);
});
}
};
items.create_file = {
"label": "New File",
"icon": "fa fa-file",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.create_node(data.reference, {
"text": "New File",
"type": "file"
}, "last", function(new_node) {
inst.edit(new_node);
});
}
};
}
// Permission-based items
if (hasDeletePermission(node)) {
items.delete = {
"label": "Delete",
"icon": "fa fa-trash",
"separator_before": true,
"action": function(data) {
if (confirm("Delete this item?")) {
const inst = $.jstree.reference(data.reference);
inst.delete_node(data.reference);
}
}
};
}
return items;
}
}
});Configuration for nested submenu items.
/**
* Submenu configuration
*/
interface SubmenuConfig {
submenu: {[key: string]: ContextMenuItem};
}Usage Examples:
// Context menu with submenus
$("#tree").jstree({
"plugins": ["contextmenu"],
"contextmenu": {
"items": {
"create": {
"label": "Create",
"icon": "fa fa-plus",
"submenu": {
"folder": {
"label": "Folder",
"icon": "fa fa-folder",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.create_node(data.reference, {
"text": "New Folder",
"type": "folder"
});
}
},
"file": {
"label": "File",
"icon": "fa fa-file",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.create_node(data.reference, {
"text": "New File",
"type": "file"
});
}
},
"separator": {
"separator_before": true
},
"template": {
"label": "From Template",
"icon": "fa fa-copy",
"submenu": {
"html": {
"label": "HTML File",
"action": function(data) {
createFromTemplate(data.reference, "html");
}
},
"css": {
"label": "CSS File",
"action": function(data) {
createFromTemplate(data.reference, "css");
}
}
}
}
}
},
"edit": {
"label": "Edit",
"submenu": {
"rename": {
"label": "Rename",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.edit(data.reference);
}
},
"properties": {
"label": "Properties",
"action": function(data) {
showProperties(data.reference);
}
}
}
}
}
}
});Configuration for menu items with conditional visibility and enablement.
/**
* Conditional visibility function
* @param key - Menu item key
* @param opt - Menu options
* @returns True if item should be visible
*/
type VisibilityFunction = (key: string, opt: object) => boolean;
/**
* Conditional enablement function
* @param key - Menu item key
* @param opt - Menu options
* @returns True if item should be enabled
*/
type DisabledFunction = (key: string, opt: object) => boolean;Usage Examples:
// Conditional menu items
$("#tree").jstree({
"plugins": ["contextmenu"],
"contextmenu": {
"items": function(node) {
return {
"cut": {
"label": "Cut",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.cut(data.reference);
},
"visible": function(key, opt) {
// Only show if node is not root
return node.parent !== "#";
}
},
"copy": {
"label": "Copy",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.copy(data.reference);
}
},
"paste": {
"label": "Paste",
"action": function(data) {
const inst = $.jstree.reference(data.reference);
inst.paste(data.reference);
},
"visible": function(key, opt) {
const inst = $.jstree.reference(opt.$trigger);
return inst.can_paste();
},
"disabled": function(key, opt) {
const inst = $.jstree.reference(opt.$trigger);
return !inst.can_paste();
}
},
"delete": {
"label": "Delete",
"action": function(data) {
if (confirm("Delete this item?")) {
const inst = $.jstree.reference(data.reference);
inst.delete_node(data.reference);
}
},
"visible": function(key, opt) {
// Only show if user has delete permission
return hasPermission("delete", node);
}
}
};
}
}
});Events triggered during context menu operations.
// Context menu specific events
"show_contextmenu.jstree": ContextMenuEvent;
"hide_contextmenu.jstree": ContextMenuEvent;
interface ContextMenuEvent {
node: object;
x: number;
y: number;
instance: jsTree;
}Usage Examples:
// Listen for context menu events
$("#tree").on("show_contextmenu.jstree", function(e, data) {
console.log("Context menu shown for:", data.node.text);
console.log("Position:", data.x, data.y);
});
$("#tree").on("hide_contextmenu.jstree", function(e, data) {
console.log("Context menu hidden");
});
// Custom context menu styling based on node
$("#tree").on("show_contextmenu.jstree", function(e, data) {
const menuContainer = $(".jstree-contextmenu");
// Add custom class based on node type
menuContainer.removeClass("folder-menu file-menu");
if (data.node.type === "folder") {
menuContainer.addClass("folder-menu");
} else if (data.node.type === "file") {
menuContainer.addClass("file-menu");
}
});Advanced configuration patterns for complex scenarios.
/**
* Advanced context menu patterns
*/
interface AdvancedContextMenuConfig {
/** Multi-selection context menu */
multi_selection?: boolean;
/** Custom menu positioning */
position?: function;
/** Menu theme/styling */
theme?: string;
/** Animation settings */
animation?: object;
}Usage Examples:
// Multi-selection context menu
$("#tree").jstree({
"plugins": ["contextmenu"],
"contextmenu": {
"items": function(node) {
const inst = $.jstree.reference($("#tree"));
const selected = inst.get_selected();
const isMultiple = selected.length > 1;
const items = {};
if (isMultiple) {
items.multi_delete = {
"label": "Delete Selected (" + selected.length + ")",
"icon": "fa fa-trash",
"action": function(data) {
if (confirm("Delete " + selected.length + " items?")) {
inst.delete_node(selected);
}
}
};
items.multi_move = {
"label": "Move Selected",
"icon": "fa fa-arrows",
"action": function(data) {
// Show move dialog for multiple items
showMoveDialog(selected);
}
};
} else {
// Single item context menu
items.rename = {
"label": "Rename",
"action": function(data) {
inst.edit(data.reference);
}
};
}
return items;
}
}
});
// Integration with other plugins
$("#tree").jstree({
"plugins": ["contextmenu", "dnd", "clipboard"],
"contextmenu": {
"items": function(node) {
const tree = $.jstree.reference($("#tree"));
return {
"cut": {
"label": "Cut",
"action": function(data) {
tree.cut(data.reference);
}
},
"copy": {
"label": "Copy",
"action": function(data) {
tree.copy(data.reference);
}
},
"paste": {
"label": "Paste",
"action": function(data) {
tree.paste(data.reference);
},
"visible": function() {
return tree.can_paste();
}
}
};
}
}
});// Context menu data structures
interface ContextMenuData {
reference: jQuery;
element: jQuery;
position: {x: number, y: number};
node: object;
}
// Menu action function signature
type MenuActionFunction = (data: ContextMenuData) => void;
// Context menu settings
interface ContextMenuSettings {
select_node: boolean;
show_at_node: boolean;
items: object|DynamicMenuFunction;
}