Настройка контекстного меню jstree, вызываемого правой кнопкой мыши, для различных типов узлов

Я видел где-то в Интернете пример, показывающий, как настроить внешний вид контекстного меню 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'); 
            }
        }
    }
}

но это не работает - элемент создания просто всегда отключен (предупреждение никогда не появляется).


person MGOwen    schedule 30.12.2010    source источник


Ответы (8)


Плагин contextmenu уже поддерживает это. Из документации, на которую вы ссылаетесь:

items: ожидает объект или функцию, которая должна возвращать объект. Если функция используется, она запускается в контексте дерева и получает один аргумент - узел, который был нажат правой кнопкой мыши.

Поэтому вместо того, чтобы давать 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 = true в операторе if.

Это должно быть очевидно, но не забудьте инициализировать плагин функцией 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().

Также не составит труда модифицировать их исходный код напрямую. Строка 2867 версии 1.0-rc1 является релевантной:

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

Вы можете просто добавить перед этой строкой строку, которая проверяет $.isFunction(val._disabled), и если да, то val._disabled = val._disabled(). Тогда отправьте его создателям как патч :)

person David Tang    schedule 05.01.2011
comment
Спасибо. Мне показалось, что однажды я увидел решение, предусматривающее изменение только того, что требовало изменения от значения по умолчанию (вместо того, чтобы воссоздавать все меню с нуля). Я приму этот ответ, если не будет лучшего решения до истечения срока награды. - person MGOwen; 06.01.2011
comment
@MGOwen, концептуально я я изменяю значение по умолчанию, но да, вы правы, что объект создается заново каждый раз, когда вызывается функция. Однако сначала необходимо клонировать значение по умолчанию, иначе изменяется само значение по умолчанию (и вам потребуется более сложная логика, чтобы вернуть его в исходное состояние). Альтернатива, которую я могу придумать, - это переместить var items за пределы функции, чтобы он был создан только один раз, и вернуть набор элементов из функции, например. return {renameItem: items.renameItem}; или return {renameItem: items.renameItem, deleteItem: items.deleteItem}; - person David Tang; 06.01.2011
comment
Мне особенно нравится последний вариант, в котором вы изменяете исходный код jstree. Я попробовал, и он работает, функция, назначенная _disabled (в моем примере), работает. Но это не помогает, потому что я не могу получить доступ к узлу (мне по крайней мере нужен атрибут rel для фильтрации узлов по типу узла) из области действия функции. Я попытался проверить переменные, которые мог передать из исходного кода jstree, но не смог найти узел. Любые идеи? - person MGOwen; 12.01.2011
comment
@MGOwen, похоже, что выбранный элемент <a> хранится в $.vakata.context.tgt. Так что попробуйте поискать $.vakata.context.tgt.attr("rel"). - person David Tang; 12.01.2011
comment
@ Box9 К тому времени, как это дойдет до этого, $ .vakata.context.tgt == false. Я просмотрел все переменные, которые я мог найти для передачи ($ .vakata, s, val, i), и нет ничего, что ссылается на узел, который был нажат правой кнопкой мыши. Я попробую ваше первоначальное предложение. Спасибо. - person MGOwen; 17.01.2011
comment
@ Box9 - Это очень помогло. Спасибо! - person JasCav; 18.02.2011
comment
в jstree 3.0.8: if ($(node).hasClass("folder")) не работает. но это произошло: if (node.children.length > 0) { items.deleteItem._disabled = true; } - person Ryan Vettese; 27.11.2014
comment
Какой код вы вставляете в action: function () {...} в первом фрагменте? Что, если вы хотите использовать обычные функции и пункты меню, но просто удалить один из них (например, удалить «Удалить», но оставить «Переименовать» и «Создать»)? На мой взгляд, это действительно то, о чем спрашивал ОП. Конечно, вам не нужно переписывать функции для таких вещей, как Rename и Create, если вы удалите другой элемент, такой как Delete? - person Andy; 16.01.2018

Реализовано с разными типами узлов:

$('#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;
}

Красиво работает.

person stacked    schedule 27.02.2015
comment
Я предпочитаю этот ответ, поскольку он основан на атрибуте type, а не на классе CSS, полученном с помощью jQuery. - person Benny Bottema; 18.06.2017
comment
Какой код вы вставляете в 'action': function () { /* action */ } во втором фрагменте? Что, если вы хотите использовать обычные функции и пункты меню, но просто удалить один из них (например, удалить «Удалить», но оставить «Переименовать» и «Создать»)? На мой взгляд, это действительно то, о чем спрашивал ОП. Конечно, вам не нужно переписывать функции для таких вещей, как Rename и Create, если вы удалите другой элемент, такой как Delete? - person Andy; 16.01.2018
comment
Я не уверен, что понимаю ваш вопрос. Вы определяете все функции для полного контекстного меню (например, «Удалить», «Переименовать» и «Создать») в items списке объектов, затем вы указываете, какие из этих элементов следует удалить для данного node.type в конце функции customMenu. Когда пользователь щелкает узел заданного type, в контекстном меню будут перечислены все элементы за вычетом удаленных в условном выражении в конце функции customMenu. Вы не переписываете какие-либо функции (если jstree не изменился с момента этого ответа три года назад, и в этом случае он может больше не иметь значения). - person stacked; 18.01.2018

Чтобы все очистить.

Вместо этого:

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

Использовать этот:

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

Предлагаемое решение для работы с типами я адаптировал немного по-другому, возможно, это поможет кому-то другому:

Где # {$ id_arr [$ k]} - ссылка на контейнер div ... в моем случае я использую много деревьев, поэтому весь этот код будет выводиться в браузер, но вы поняли идею .. В основном я хочу, чтобы все параметры контекстного меню, но только «Создать» и «Вставить» в узле «Диск». Очевидно, с правильными привязками к этим операциям позже:

<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},
person Jean Paul -JP A.K.A el_vete    schedule 16.06.2012

Кстати: если вы просто хотите удалить параметры из существующего контекстного меню - это сработало для меня:

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

person Florian S.    schedule 01.11.2018

Ответ Дэвида кажется прекрасным и эффективным. Я нашел еще один вариант решения, в котором вы можете использовать атрибут a_attr для различения разных узлов и на основе этого вы можете создавать другое контекстное меню.

В приведенном ниже примере я использовал два типа узлов: Папка и Файлы. Я также использовал разные значки, используя глификон. Для узла типа файла вы можете получить только контекстное меню для переименования и удаления. Для папки есть все параметры: создать файл, создать папку, переименовать, удалить.

Полный фрагмент кода можно найти в 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'} },
            ];

В рамках пункта контекстного меню для создания файла и папки используйте код, аналогичный приведенному ниже, как действие с файлом.

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);
                            }
person Asif Nowaj    schedule 02.11.2019
comment
отличный рабочий человек! - person Jean Paul -JP A.K.A el_vete; 10.07.2021

Вы можете изменить код @ Box9 в соответствии с вашим требованием динамического отключения контекстного меню следующим образом:

function customMenu(node) {

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


       items.deleteItem._disabled = true;
    }   

}  

Вам нужно добавить один атрибут "xyz" в ваши данные XML или JSOn.

person user367134    schedule 22.10.2011

с 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.

person craigh    schedule 13.02.2015