Configuring jstree right-click contextmenu for different node types

前端 未结 8 2311
甜味超标
甜味超标 2020-12-04 07:29

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

相关标签:
8条回答
  • 2020-12-04 07:44

    I have adapted the suggested solution for working with types a bit differently though, perhaps it can help someone else:

    Where #{$id_arr[$k]} is the reference to the div container... in my case I use many trees so all this code will be the output to the browser, but you get the idea.. Basically I want all the context menu options but only 'Create' and 'Paste' on the Drive node. Obviously with the correct bindings to those operations later on:

    <div id="$id_arr[$k]" class="jstree_container"></div>
    </div>
    </li>
    <!-- JavaScript neccessary for this tree : {$value} -->
    <script type="text/javascript" >
    jQuery.noConflict();
    jQuery(function ($) {
    // This is for the context menu to bind with operations on the right clicked node
    function customMenu(node) {
        // The default set of all items
        var control;
        var items = {
            createItem: {
                label: "Create",
                action: function (node) { return { createItem: this.create(node) }; }
            },
            renameItem: {
                label: "Rename",
                action: function (node) { return { renameItem: this.rename(node) }; }
            },
            deleteItem: {
                label: "Delete",
                action: function (node) { return { deleteItem: this.remove(node) }; },
                "separator_after": true
            },
            copyItem: {
                label: "Copy",
                action: function (node) { $(node).addClass("copy"); return { copyItem: this.copy(node) }; }
            },
            cutItem: {
                label: "Cut",
                action: function (node) { $(node).addClass("cut"); return { cutItem: this.cut(node) }; }
            },
            pasteItem: {
                label: "Paste",
                action: function (node) { $(node).addClass("paste"); return { pasteItem: this.paste(node) }; }
            }
        };
    
        // We go over all the selected items as the context menu only takes action on the one that is right clicked
        $.jstree._reference("#{$id_arr[$k]}").get_selected(false, true).each(function (index, element) {
            if ($(element).attr("id") != $(node).attr("id")) {
                // Let's deselect all nodes that are unrelated to the context menu -- selected but are not the one right clicked
                $("#{$id_arr[$k]}").jstree("deselect_node", '#' + $(element).attr("id"));
            }
        });
    
        //if any previous click has the class for copy or cut
        $("#{$id_arr[$k]}").find("li").each(function (index, element) {
            if ($(element) != $(node)) {
                if ($(element).hasClass("copy") || $(element).hasClass("cut")) control = 1;
            }
            else if ($(node).hasClass("cut") || $(node).hasClass("copy")) {
                control = 0;
            }
        });
    
        //only remove the class for cut or copy if the current operation is to paste
        if ($(node).hasClass("paste")) {
            control = 0;
            // Let's loop through all elements and try to find if the paste operation was done already
            $("#{$id_arr[$k]}").find("li").each(function (index, element) {
                if ($(element).hasClass("copy")) $(this).removeClass("copy");
                if ($(element).hasClass("cut")) $(this).removeClass("cut");
                if ($(element).hasClass("paste")) $(this).removeClass("paste");
            });
        }
        switch (control) {
            //Remove the paste item from the context menu
            case 0:
                switch ($(node).attr("rel")) {
                    case "drive":
                        delete items.renameItem;
                        delete items.deleteItem;
                        delete items.cutItem;
                        delete items.copyItem;
                        delete items.pasteItem;
                        break;
                    case "default":
                        delete items.pasteItem;
                        break;
                }
                break;
                //Remove the paste item from the context menu only on the node that has either copy or cut added class
            case 1:
                if ($(node).hasClass("cut") || $(node).hasClass("copy")) {
                    switch ($(node).attr("rel")) {
                        case "drive":
                            delete items.renameItem;
                            delete items.deleteItem;
                            delete items.cutItem;
                            delete items.copyItem;
                            delete items.pasteItem;
                            break;
                        case "default":
                            delete items.pasteItem;
                            break;
                    }
                }
                else //Re-enable it on the clicked node that does not have the cut or copy class
                {
                    switch ($(node).attr("rel")) {
                        case "drive":
                            delete items.renameItem;
                            delete items.deleteItem;
                            delete items.cutItem;
                            delete items.copyItem;
                            break;
                    }
                }
                break;
    
                //initial state don't show the paste option on any node
            default: switch ($(node).attr("rel")) {
                case "drive":
                    delete items.renameItem;
                    delete items.deleteItem;
                    delete items.cutItem;
                    delete items.copyItem;
                    delete items.pasteItem;
                    break;
                case "default":
                    delete items.pasteItem;
                    break;
            }
                break;
        }
        return items;
    $("#{$id_arr[$k]}").jstree({
      // List of active plugins used
      "plugins" : [ "themes","json_data", "ui", "crrm" , "hotkeys" , "types" , "dnd", "contextmenu"],
      "contextmenu" : { "items" : customMenu  , "select_node": true},
    
    0 讨论(0)
  • 2020-12-04 07:54

    Implemented with different node types:

    $('#jstree').jstree({
        'contextmenu' : {
            'items' : customMenu
        },
        'plugins' : ['contextmenu', 'types'],
        'types' : {
            '#' : { /* options */ },
            'level_1' : { /* options */ },
            'level_2' : { /* options */ }
            // etc...
        }
    });
    

    And the customMenu function:

    function customMenu(node)
    {
        var items = {
            'item1' : {
                'label' : 'item1',
                'action' : function () { /* action */ }
            },
            'item2' : {
                'label' : 'item2',
                'action' : function () { /* action */ }
            }
        }
    
        if (node.type === 'level_1') {
            delete items.item2;
        } else if (node.type === 'level_2') {
            delete items.item1;
        }
    
        return items;
    }
    

    Works beautifully.

    0 讨论(0)
  • 2020-12-04 07:54

    as of jsTree 3.0.9 I needed to use something like

    var currentNode = treeElem.jstree('get_node', node, true);
    if (currentNode.hasClass("folder")) {
        // Delete the "delete" menu item
        delete items.deleteItem;
    }
    

    because the node object that is provided is not a jQuery object.

    0 讨论(0)
  • 2020-12-04 07:55

    You can modify @Box9 code as to suit your requirement of dynamic disabling of context menu as:

    function customMenu(node) {
    
      ............
      ................
       // Disable  the "delete" menu item  
       // Original // delete items.deleteItem; 
       if ( node[0].attributes.yyz.value == 'notdelete'  ) {
    
    
           items.deleteItem._disabled = true;
        }   
    
    }  
    

    You need add one attribute "xyz" in your XML or JSOn data

    0 讨论(0)
  • 2020-12-04 07:57

    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 :)

    0 讨论(0)
  • 2020-12-04 07:57

    To clear everything.

    Instead of this:

    $("#xxx").jstree({
        'plugins' : 'contextmenu',
        'contextmenu' : {
            'items' : { ... bla bla bla ...}
        }
    });
    

    Use this:

    $("#xxx").jstree({
        'plugins' : 'contextmenu',
        'contextmenu' : {
            'items' : customMenu
        }
    });
    
    0 讨论(0)
提交回复
热议问题