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

85

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

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

MGOwen
источник

Ответы:

145

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(). Тогда отправьте его создателям как патч :)

Дэвид Танг
источник
Благодарю. Я думал, что однажды увидел решение, включающее изменение только того, что требовало изменения по умолчанию (вместо того, чтобы воссоздавать все меню с нуля). Я приму этот ответ, если не будет лучшего решения до истечения срока награды.
MGOwen
@MGOwen, концептуально я имею в модификации « по умолчанию», но да , вы правы , что объект получает повторно создается каждый раз при вызове функции. Однако сначала необходимо клонировать значение по умолчанию, иначе изменяется само значение по умолчанию (и вам понадобится более сложная логика, чтобы вернуть его обратно в исходное состояние). Альтернатива, которую я могу придумать, - это выйти var itemsза пределы функции, чтобы она создавалась только один раз, и возвращать выбранные элементы из функции, например, return {renameItem: items.renameItem};илиreturn {renameItem: items.renameItem, deleteItem: items.deleteItem};
Дэвид Танг
Мне особенно нравится последний вариант, в котором вы изменяете исходный код jstree. Я попробовал, и он работает, функция, назначенная для "_disabled" (в моем примере), работает. Но это не помогает, потому что я не могу получить доступ к узлу (мне, по крайней мере, нужен атрибут rel для фильтрации узлов по типу узла) из области действия функции. Я попытался проверить переменные, которые мог передать из исходного кода jstree, но не смог найти узел. Есть идеи?
MGOwen
@MGOwen, похоже, что выбранный <a>элемент хранится в $.vakata.context.tgt. Так что попробуйте поискать $.vakata.context.tgt.attr("rel").
Дэвид Тан
1
в jstree 3.0.8: if ($(node).hasClass("folder")) не сработало. но это произошло: if (node.children.length > 0) { items.deleteItem._disabled = true; }
Райан Веттезе
19

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

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

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

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

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

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

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

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

$("#xxx").jstree({
    'plugins' : 'contextmenu',
    'contextmenu' : {
        'items' : customMenu
    }
});
Мангирдас
источник
5

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

Где # {$ 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},
Жан Поль AKA el_vete
источник
2

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

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

Флориан С.
источник
1

Вы можете изменить код @ 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.

user367134
источник
1

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

Craigh
источник
1

Ответ Дэвида кажется прекрасным и эффективным. Я нашел еще один вариант решения, в котором вы можете использовать атрибут 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);
                            }
Асиф Новай
источник