Search code examples
jquerycontextmenujstree

Configuring jstree right-click contextmenu for different node types


I've seen an example somewhere online showing how to customise the appearance of jstree's right-click context menu (using contextmenu plugin).

For example, allow my users to delete "documents" but not "folders" (by hiding the "delete" option from the context menu for folders).

Now I can't find that example. Can anyone point me in the right direction? The official documentation didn't really help.

Edit:

Since I want the default context menu with only one or two minor changes, I'd prefer to not recreate the whole menu (though of course I will if it's the only way). What I'd like to do is something like this:

"contextmenu" : {
    items: {
        "ccp" : false,
        "create" : {
            // The item label
            "label" : "Create",
            // The function to execute upon a click
            "action": function (obj) { this.create(obj); },
            "_disabled": function (obj) { 
                alert("obj=" + obj); 
                return "default" != obj.attr('rel'); 
            }
        }
    }
}

but it doesn't work - the create item is just always disabled (the alert never appears).


Solution

  • The contextmenu plugin already has support for this. From the documentation you linked to:

    items: Expects an object or a function, which should return an object. If a function is used it fired in the tree's context and receives one argument - the node that was right clicked.

    So rather than give contextmenu a hard-coded object to work with, you can supply the following function. It checks the element that was clicked for a class named "folder", and removes the "delete" menu item by deleting it from the object:

    function customMenu(node) {
        // The default set of all items
        var items = {
            renameItem: { // The "rename" menu item
                label: "Rename",
                action: function () {...}
            },
            deleteItem: { // The "delete" menu item
                label: "Delete",
                action: function () {...}
            }
        };
    
        if ($(node).hasClass("folder")) {
            // Delete the "delete" menu item
            delete items.deleteItem;
        }
    
        return items;
    }
    

    Note that the above will hide the delete option completely, but the plugin also allows you to show an item while disabling its behaviour, by adding _disabled: true to the relevant item. In this case you can use items.deleteItem._disabled = true within the if statement instead.

    Should be obvious, but remember to initialise the plugin with the customMenu function instead of what you had previously:

    $("#tree").jstree({plugins: ["contextmenu"], contextmenu: {items: customMenu}});
    //                                                                    ^
    // ___________________________________________________________________|
    

    Edit: If you don't want the menu to be recreated on every right-click, you can put the logic in the action handler for the delete menu item itself.

    "label": "Delete",
    "action": function (obj) {
        if ($(this._get_node(obj)).hasClass("folder") return; // cancel action
    }
    

    Edit again: After looking at the jsTree source code, it looks like the contextmenu is being re-created every time it is shown anyway (see the show() and parse() functions), so I don't see a problem with my first solution.

    However, I do like the notation you are suggesting, with a function as the value for _disabled. A potential path to explore is to wrap their parse() function with your own one that evaluates the function at disabled: function () {...} and stores the result in _disabled, before calling the original parse().

    It won't be difficult either to modify their source code directly. Line 2867 of version 1.0-rc1 is the relevant one:

    str += "<li class='" + (val._class || "") + (val._disabled ? " jstree-contextmenu-disabled " : "") + "'><ins ";
    

    You can simply add a line before this one that checks $.isFunction(val._disabled), and if so, val._disabled = val._disabled(). Then submit it to the creators as a patch :)