jQuery 为不同的节点类型配置jstree右键上下文菜单

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4559543/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 17:29:12  来源:igfitidea点击:

Configuring jstree right-click contextmenu for different node types

jquerycontextmenujstree

提问by MGOwen

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

我在网上看到了一个例子,展示了如何自定义 jstree 的右键单击上下文菜单的外观(使用上下文菜单插件)。

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 documentationdidn'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).

但它不起作用 - 创建项目总是被禁用(警报永远不会出现)。

回答by David Tang

The contextmenuplugin already has support for this. From the documentation you linked to:

contextmenu插件已经对此提供了支持。从您链接到的文档中:

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.

items: 期望一个对象或一个函数,它应该返回一个对象。如果使用了一个函数,它会在树的上下文中触发并接收一个参数 - 右键单击​​的节点。

So rather than give contextmenua 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:

因此,与其提供要contextmenu使用的硬编码对象,不如提供以下函数。它检查为名为“文件夹”的类单击的元素,并通过从对象中删除“删除”菜单项来删除它:

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: trueto the relevant item. In this case you can use items.deleteItem._disabled = truewithin the ifstatement instead.

请注意,以上将完全隐藏删除选项,但该插件还允许您通过添加_disabled: true到相关项目,在禁用其行为的同时显示项目。在这种情况下,您可以items.deleteItem._disabled = trueif语句中使用。

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

应该很明显,但请记住使用customMenu函数而不是之前的函数初始化插件:

$("#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.

再次编辑:查看 jsTree 源代码后,似乎每次显示时都会重新创建上下文菜单(请参阅show()parse()函数),所以我没有看到我的第一个解决方案有问题。

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().

但是,我确实喜欢您建议的符号,将函数作为_disabled. 探索出一条潜在的路径是包装自己的parse()功能,用自己的一个,在计算函数disabled: function () {...}中并存储结果_disabled,称前原parse()

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

直接修改他们的源代码也不难。版本 1.0-rc1 的第 2867 行是相关的:

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

您可以简单地在该检查之前添加一行$.isFunction(val._disabled),如果是,则添加val._disabled = val._disabled(). 然后将其作为补丁提交给创作者:)

回答by stacked

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:

和 customMenu 功能:

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.

工作精美。

回答by Mangirdas

To clear everything.

清除一切。

Instead of this:

取而代之的是:

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

Use this:

用这个:

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

回答by Jean Paul A.K.A el_vete

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:

#{$id_arr[$k]} 是对 div 容器的引用......在我的例子中,我使用了很多树,所以所有这些代码都将输出到浏览器,但你明白了......基本上我想要所有上下文菜单选项,但只有 Drive 节点上的“创建”和“粘贴”。显然,稍后将正确绑定到这些操作:

<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},

回答by Florian S.

Btw: If you just want to remove options from the existing context menu - this worked for me:

顺便说一句:如果您只想从现有的上下文菜单中删除选项 - 这对我有用:

function customMenu(node)
{
    var items = $.jstree.defaults.contextmenu.items(node);

    if (node.type === 'root') {
        delete items.create;
        delete items.rename;
        delete items.remove;
        delete items.ccp;
    }

    return items;
}

回答by Asif Nowaj

David's response seems fine and efficient. I have found another variation of the solution where you can use a_attr attribute to differentiate different nodes and based on that you can generate different context menu.

大卫的回应似乎很好而且很有效率。我发现了解决方案的另一个变体,您可以使用 a_attr 属性来区分不同的节点,并基于此可以生成不同的上下文菜单。

In the below example, I have used two types of nodes Folder and Files. I have used different icons too using glyphicon. For file type node, you can only get context menu to rename and remove. For Folder, all options are there, create file, create folder, rename, remove.

在下面的示例中,我使用了两种类型的节点文件夹和文件。我也使用 glyphicon 使用了不同的图标。对于文件类型节点,您只能获取上下文菜单来重命名和删除。对于文件夹,所有选项都在那里,创建文件,创建文件夹,重命名,删除。

For complete code snippet, you can view https://everyething.com/Example-of-jsTree-with-different-context-menu-for-different-node-type

完整的代码片段,可以查看https://everyething.com/Example-of-jsTree-with-different-context-menu-for-different-node-type

 $('#SimpleJSTree').jstree({
                "core": {
                    "check_callback": true,
                    'data': jsondata

                },
                "plugins": ["contextmenu"],
                "contextmenu": {
                    "items": function ($node) {
                        var tree = $("#SimpleJSTree").jstree(true);
                        if($node.a_attr.type === 'file')
                            return getFileContextMenu($node, tree);
                        else
                            return getFolderContextMenu($node, tree);                        
                    }
                }
            });

Initial json data has been as below, where node type is mentioned within a_attr.

初始 json 数据如下,其中 a_attr 中提到了节点类型。

var jsondata = [
                           { "id": "ajson1", "parent": "#", "text": "Simple root node", icon: 'glyphicon glyphicon-folder-open', "a_attr": {type:'folder'} },
                           { "id": "ajson2", "parent": "#", "text": "Root node 2", icon: 'glyphicon glyphicon-folder-open', "a_attr": {type:'folder'} },
                           { "id": "ajson3", "parent": "ajson2", "text": "Child 1", icon: 'glyphicon glyphicon-folder-open', "a_attr": {type:'folder'} },
                           { "id": "ajson4", "parent": "ajson2", "text": "Child 2", icon: 'glyphicon glyphicon-folder-open', "a_attr": {type:'folder'} },
            ];

As part of contect menu item to create a file and folder use similar code below, as file action.

作为创建文件和文件夹的 contect 菜单项的一部分,使用下面的类似代码作为文件操作。

action: function (obj) {
                                $node = tree.create_node($node, { text: 'New File', icon: 'glyphicon glyphicon-file', a_attr:{type:'file'} });
                                tree.deselect_all();
                                tree.select_node($node);
                            }

as folder action:

作为文件夹操作:

action: function (obj) {
                                $node = tree.create_node($node, { text: 'New Folder', icon:'glyphicon glyphicon-folder-open', a_attr:{type:'folder'} });
                                tree.deselect_all();
                                tree.select_node($node);
                            }

回答by craigh

as of jsTree 3.0.9 I needed to use something like

从 jsTree 3.0.9 开始,我需要使用类似的东西

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

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

因为提供的node对象不是 jQuery 对象。

回答by user367134

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

您可以修改@Box9 代码以满足您动态禁用上下文菜单的要求:

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

您需要在 XML 或 JSOn 数据中添加一个属性“xyz”