Налаштування контекстного меню 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'); 
            }
        }
    }
}

але це не працює - елемент створення просто завжди вимкнено (попередження ніколи не відображається).

Відповіді:


144

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

Мені особливо подобається цей останній, де ви змінюєте джерело jstree. Я спробував, і це працює, функція, призначена "_disabled" (у моєму прикладі) працює. Але це не допомагає, оскільки я не можу отримати доступ до вузла (принаймні мені потрібен його атрибут rel для фільтрації вузлів за типом вузла) з області дії функції. Я спробував перевірити змінні, які я міг передати із вихідного коду jstree, але не зміг знайти вузол. Будь-які ідеї?
MGOwen

@MGOwen, схоже, що <a>елемент, на який клацнули, зберігається в $.vakata.context.tgt. Тож спробуйте підняти очі $.vakata.context.tgt.attr("rel").
David Tang

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.
Бенні Боттема,

Який код ви вставляєте всередину 'action': function () { /* action */ }у другий фрагмент? Що робити, якщо ви хочете використовувати «звичайну» функціональність та пункти меню, а просто видалити один із них (наприклад, видалити Видалити, але зберегти Перейменувати та Створити)? На мій погляд, це справді те, про що запитував ОП. Звичайно, вам не потрібно переписувати функціональність для таких речей, як Перейменувати та Створити, якщо ви видалите інший елемент, наприклад Видалити?
Енді,

Я не впевнений, що розумію ваше запитання. Ви визначаєте всю функціональність повного контекстного меню (наприклад, Видалити, Перейменувати та Створити) у 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},

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


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.


1

Відповідь Девіда здається прекрасною та ефективною. Я знайшов ще один варіант рішення, де ви можете використовувати атрибут a_attr для диференціації різних вузлів і виходячи з цього ви можете генерувати різне контекстне меню.

У наведеному нижче прикладі я використовував два типи вузлів Folder і Files. Я теж використовував різні піктограми, використовуючи гліфікон. Для вузла типу файлу ви можете отримати лише контекстне меню для перейменування та видалення. Для папки є всі опції, створити файл, створити папку, перейменувати, видалити.

Повний фрагмент коду ви можете переглянути 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);
                            }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.