87

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

例如,允许我的用户删除“文档”而不是“文件夹”(通过从文件夹的上下文菜单中隐藏“删除”选项)。

现在我找不到那个例子了。谁能指出我正确的方向?官方文档并没有真正帮助。

编辑:

因为我想要默认的上下文菜单只有一两个小改动,所以我宁愿不重新创建整个菜单(当然,如果这是唯一的方法,我会这样做)。我想做的是这样的:

"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'); 
            }
        }
    }
}

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

4

9 回答 9

151

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

items: 期望一个对象或函数,它应该返回一个 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;
}

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

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

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

编辑:如果您不希望在每次右键单击时重新创建菜单,您可以将逻辑放在删除菜单项本身的操作处理程序中。

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

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

但是,我确实喜欢您建议的表示法,其中函数作为_disabled. 一个潜在的探索途径是parse()disabled: function () {...}调用_disabled原始parse().

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

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

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

于 2011-01-05T02:19:29.610 回答
20

使用不同的节点类型实现:

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

和 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;
}
于 2015-02-27T20:48:08.497 回答
12

清除一切。

而不是这个:

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

用这个:

$("#xxx").jstree({
    'plugins' : 'contextmenu',
    'contextmenu' : {
        'items' : customMenu
    }
});
于 2011-05-11T18:50:19.293 回答
5

我已经调整了建议的解决方案来处理类型有点不同,也许它可以帮助其他人:

其中 #{$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},
于 2012-06-16T21:51:22.417 回答
3

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

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

于 2018-11-01T16:19:30.390 回答
2

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

function customMenu(node) {

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


       items.deleteItem._disabled = true;
    }   

}  

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

于 2011-10-22T12:49:57.783 回答
2

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

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

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

于 2015-02-13T17:55:33.330 回答
2

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

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

完整的代码片段,可以查看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);                        
                    }
                }
            });

初始 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'} },
            ];

作为创建文件和文件夹的 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);
                            }

作为文件夹操作:

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);
                            }
于 2019-11-02T09:00:27.757 回答
1

这是我的完整插件设置。

var ktTreeDocument = $("#jstree_html_id");

jQuery(document).ready(function () {
    DocumentKTTreeview.init();
});

var DocumentKTTreeview = function() {
    var treeDocument = function() {
        ktTreeDocument.jstree({
            "core": {
                "themes": {
                    "responsive": false
                },
                "check_callback": function(operation, node, node_parent, node_position, more) {
                    documentAllModuleObj.selectedNode = ktTreeDocument.jstree().get_selected('full', true);
                    if (operation === 'delete_node') {
                        if (!confirm('are you sure?')) {
                            return false;
                        }
                    }
                    return true;
                },
                'data': {
                    'dataType': 'json',
                    'url': BASE_URL + ('tree/get/?lazy'),
                    'data': function(node) {
                        return { 'id': node.id };
                    }
                },
            },
            "types": {
                "default": {
                    "icon": "fa fa-folder kt-font-success"
                },
                "file": {
                    "icon": "fa fa-file  kt-font-success"
                }
            },
            "state": { "key": "demo2" },
            "plugins": ["contextmenu", "dnd", "state", "types"],
            "contextmenu": {
                "items": function($node) {
                    var tree = $("#jstree_html_id").jstree(true);
                    return {
                        "Create": {
                            "separator_before": false,
                            "separator_after": false,
                            "label": "Create",
                            "action": function(obj) {
                                tree.create_node($node);
                            }
                        },
                        "Rename": {
                            "separator_before": false,
                            "separator_after": false,
                            "label": "Rename",
                            "action": function(obj) {
                                tree.edit($node);
                            }
                        },
                        "Remove": {
                            "separator_before": false,
                            "separator_after": false,
                            "_disabled": $node.original.root ? true : false,
                            "label": "Remove",
                            "action": function(obj) {
                                tree.delete_node($node);
                            }
                        }
                    };
                }
            }
        })
    }
    return {
        init: function() {
            treeDocument();
        }
    };
}();
于 2021-11-12T05:29:01.593 回答