Định cấu hình menu ngữ cảnh nhấp chuột phải jstree cho các loại nút khác nhau


85

Tôi đã thấy một ví dụ trực tuyến ở đâu đó cho thấy cách tùy chỉnh giao diện của menu ngữ cảnh nhấp chuột phải của jstree (sử dụng plugin contextmenu).

Ví dụ: cho phép người dùng của tôi xóa "tài liệu" nhưng không xóa "thư mục" (bằng cách ẩn tùy chọn "xóa" khỏi menu ngữ cảnh cho thư mục).

Bây giờ tôi không thể tìm thấy ví dụ đó. ai đó có thể chỉ cho tôi phương hướng đúng không? Tài liệu chính thức không thực sự giúp ích.

Biên tập:

Vì tôi muốn menu ngữ cảnh mặc định chỉ có một hoặc hai thay đổi nhỏ, nên tôi không muốn tạo lại toàn bộ menu (mặc dù tất nhiên tôi sẽ làm nếu đó là cách duy nhất). Những gì tôi muốn làm là một cái gì đó như thế này:

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

nhưng nó không hoạt động - mục tạo luôn bị vô hiệu hóa (cảnh báo không bao giờ xuất hiện).

Câu trả lời:


144

Các contextmenuPlugin đã có hỗ trợ cho việc này. Từ tài liệu bạn đã liên kết đến:

items: Mong đợi một đối tượng hoặc một hàm, sẽ trả về một đối tượng . Nếu một hàm được sử dụng, nó được kích hoạt trong ngữ cảnh của cây và nhận một đối số - nút được nhấp chuột phải.

Vì vậy, thay vì cung cấp contextmenumột đối tượng được mã hóa cứng để làm việc, bạn có thể cung cấp hàm sau. Nó kiểm tra phần tử được nhấp cho một lớp có tên "thư mục" và loại bỏ mục menu "xóa" bằng cách xóa nó khỏi đối tượng:

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

Lưu ý rằng phần trên sẽ ẩn hoàn toàn tùy chọn xóa, nhưng plugin cũng cho phép bạn hiển thị một mục trong khi vô hiệu hóa hành vi của nó, bằng cách thêm _disabled: truevào mục có liên quan. Trong trường hợp này, bạn có thể sử dụng items.deleteItem._disabled = truetrongif câu lệnh để thay thế.

Rõ ràng là vậy, nhưng hãy nhớ khởi tạo plugin với customMenuchức năng thay vì những gì bạn đã có trước đó:

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

Chỉnh sửa: Nếu bạn không muốn tạo lại menu trên mỗi lần nhấp chuột phải, bạn có thể đặt logic trong trình xử lý hành động cho chính mục menu xóa.

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

Chỉnh sửa lại: Sau khi xem mã nguồn jsTree, có vẻ như menu ngữ cảnh vẫn đang được tạo lại mỗi khi nó được hiển thị (xem phần show()và các parse()chức năng), vì vậy tôi không thấy vấn đề với giải pháp đầu tiên của mình.

Tuy nhiên, tôi thích ký hiệu bạn đang đề xuất, với một hàm làm giá trị _disabled. Một con đường tiềm năng để khám phá là kết hợp parse()chức năng của chúng với một chức năng của riêng bạn để đánh giá chức năng tại disabled: function () {...}và lưu trữ kết quả _disabled, trước khi gọiparse() .

Sẽ không khó để sửa đổi mã nguồn của chúng trực tiếp. Dòng 2867 của phiên bản 1.0-rc1 là dòng có liên quan:

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

Bạn có thể chỉ cần thêm một dòng trước dòng này để kiểm tra $.isFunction(val._disabled), và nếu có val._disabled = val._disabled(),. Sau đó, gửi nó cho người sáng tạo như một bản vá :)


Cảm ơn. Tôi nghĩ rằng tôi đã từng thấy một giải pháp chỉ liên quan đến việc thay đổi những gì cần thay đổi so với mặc định (thay vì tạo lại toàn bộ menu từ đầu). Tôi sẽ chấp nhận câu trả lời này nếu không có giải pháp nào tốt hơn trước khi tiền thưởng hết hạn.
MGOwen

@MGOwen, về mặt khái niệm, tôi đang sửa đổi "mặc định", nhưng vâng, bạn nói đúng rằng đối tượng được tạo lại mỗi khi hàm được gọi. Tuy nhiên, mặc định cần được sao chép trước, nếu không, bản thân mặc định sẽ bị sửa đổi (và bạn sẽ cần logic phức tạp hơn để hoàn nguyên nó về trạng thái ban đầu). Một thay thế tôi có thể nghĩ là để di chuyển var itemsra ngoài của hàm để tạo ra nó một lần duy nhất, và trả về một lựa chọn các mặt hàng từ các chức năng, ví dụ return {renameItem: items.renameItem};hoặcreturn {renameItem: items.renameItem, deleteItem: items.deleteItem};
David Tang

Tôi thích điều cuối cùng đặc biệt, nơi bạn sửa đổi nguồn jstree. Tôi đã thử và nó hoạt động, chức năng được gán cho "_disabled" (trong ví dụ của tôi) chạy. Tuy nhiên, nó không hữu ích vì tôi không thể truy cập vào nút (ít nhất tôi cần thuộc tính rel của nó để lọc các nút theo loại nút) từ trong phạm vi của hàm. Tôi đã thử kiểm tra các biến mà tôi có thể chuyển vào từ mã nguồn jstree nhưng không thể tìm thấy nút. Có ý kiến ​​gì không?
MGOwen

@MGOwen, có vẻ như <a>phần tử đã được nhấp được lưu trữ tại $.vakata.context.tgt. Vì vậy, hãy thử tra cứu $.vakata.context.tgt.attr("rel").
David Tang

1
trong jstree 3.0.8: if ($(node).hasClass("folder")) không hoạt động. nhưng điều này đã làm: if (node.children.length > 0) { items.deleteItem._disabled = true; }
Ryan Vettese

19

Được triển khai với các loại nút khác nhau:

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

Và chức năng 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;
}

Hoạt động đẹp.


1
Tôi thích câu trả lời này vì nó dựa vào typethuộc tính hơn là một lớp CSS thu được bằng jQuery.
Benny Bottema

Bạn đang đặt 'action': function () { /* action */ }mã nào trong đoạn mã thứ hai? Điều gì sẽ xảy ra nếu bạn muốn sử dụng chức năng "bình thường" và các mục menu, nhưng chỉ cần xóa một trong số chúng (ví dụ: xóa Xóa nhưng vẫn giữ Đổi tên và Tạo)? Theo quan điểm của tôi, đó thực sự là những gì OP đã hỏi. Chắc chắn bạn không cần phải viết lại chức năng cho những thứ như Đổi tên và Tạo nếu bạn xóa một mục khác chẳng hạn như Xóa?
Andy

Tôi không chắc mình hiểu câu hỏi của bạn. Bạn đang xác định tất cả các chức năng cho menu ngữ cảnh đầy đủ (ví dụ: Xóa, Đổi tên và Tạo) trong itemsdanh sách các đối tượng, sau đó bạn chỉ định mục nào trong số các mục này cần xóa đối với một mục nhất định node.typeở cuối customMenuhàm. Khi người dùng nhấp vào một nút đã cho type, menu ngữ cảnh sẽ liệt kê tất cả các mục trừ đi bất kỳ mục nào bị loại bỏ trong điều kiện ở cuối customMenuhàm. Bạn không viết lại bất kỳ chức năng nào (trừ khi jstree đã thay đổi kể từ câu trả lời này ba năm trước, trong trường hợp đó, nó có thể không còn phù hợp nữa).
xếp chồng vào

12

Để xóa mọi thứ.

Thay vì điều này:

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

Dùng cái này:

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

5

Tuy nhiên, tôi đã điều chỉnh giải pháp được đề xuất để làm việc với các loại hơi khác một chút, có lẽ nó có thể giúp người khác:

Trong đó # {$ id_arr [$ k]} là tham chiếu đến vùng chứa div ... trong trường hợp của tôi, tôi sử dụng nhiều cây nên tất cả mã này sẽ là đầu ra cho trình duyệt, nhưng bạn hiểu rõ .. Về cơ bản, tôi muốn tất cả các tùy chọn menu ngữ cảnh nhưng chỉ có 'Tạo' và 'Dán' trên nút Drive. Rõ ràng với các ràng buộc chính xác cho các hoạt động đó sau này:

<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

Btw: Nếu bạn chỉ muốn xóa các tùy chọn khỏi menu ngữ cảnh hiện có - điều này làm việc với tôi:

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

Bạn có thể sửa đổi mã @ Box9 để phù hợp với yêu cầu tắt động của menu ngữ cảnh như:

function customMenu(node) {

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


       items.deleteItem._disabled = true;
    }   

}  

Bạn cần thêm một thuộc tính "xyz" trong dữ liệu XML hoặc JSOn của mình


1

kể từ jsTree 3.0.9, tôi cần sử dụng một số thứ như

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

nodeđối tượng được cung cấp không phải là đối tượng jQuery.


1

Câu trả lời của David có vẻ ổn và hiệu quả. Tôi đã tìm thấy một biến thể khác của giải pháp nơi bạn có thể sử dụng thuộc tính a_attr để phân biệt các nút khác nhau và dựa vào đó bạn có thể tạo menu ngữ cảnh khác nhau.

Trong ví dụ dưới đây, tôi đã sử dụng hai loại nút Thư mục và Tệp. Tôi cũng đã sử dụng các biểu tượng khác nhau bằng cách sử dụng glyphicon. Đối với nút loại tệp, bạn chỉ có thể nhận được menu ngữ cảnh để đổi tên và xóa. Đối với Thư mục, tất cả các tùy chọn đều có, tạo tệp, tạo thư mục, đổi tên, xóa.

Để có đoạn mã hoàn chỉnh, bạn có thể xem https://everyething.com/Example-of-jsTree-with-dierence-context-menu-for-dierence-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);                        
                    }
                }
            });

Dữ liệu json ban đầu như bên dưới, trong đó loại nút được đề cập trong 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'} },
            ];

Là một phần của mục menu contect để tạo tệp và thư mục, hãy sử dụng mã tương tự bên dưới, làm hành động tệp.

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

dưới dạng hành động thư mục:

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);
                            }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.