การกำหนดคอนฟิกเมนูบริบทคลิกขวา jstree สำหรับโหนดประเภทต่างๆ


85

ฉันเคยเห็นตัวอย่างออนไลน์ที่แสดงวิธีปรับแต่งลักษณะของเมนูบริบทคลิกขวาของ jstree (โดยใช้ปลั๊กอินบริบทเมนู)

ตัวอย่างเช่นอนุญาตให้ผู้ใช้ของฉันลบ "เอกสาร" แต่ไม่ใช่ "โฟลเดอร์" (โดยซ่อนตัวเลือก "ลบ" จากเมนูบริบทสำหรับโฟลเดอร์)

ตอนนี้หาตัวอย่างนั้นไม่เจอ ใครช่วยชี้ทิศทางที่ถูกต้องให้ฉันได้ไหม เอกสารอย่างเป็นทางการไม่ได้ช่วยอะไรเลย

แก้ไข:

เนื่องจากฉันต้องการให้เมนูบริบทเริ่มต้นที่มีการเปลี่ยนแปลงเล็กน้อยเพียงหนึ่งหรือสองรายการฉันจึงไม่ต้องการสร้างเมนูทั้งหมดขึ้นมาใหม่ (แต่แน่นอนว่าจะเป็นวิธีเดียว) สิ่งที่ฉันต้องการทำมีดังนี้:

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

แต่ไม่ได้ผล - รายการสร้างจะถูกปิดใช้งานตลอดเวลา (การแจ้งเตือนไม่ปรากฏขึ้น)

คำตอบ:


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 () {...}และร้านค้าผลในก่อนที่จะเรียกร้องเดิม_disabledparse()

การแก้ไขซอร์สโค้ดโดยตรงจะไม่ใช่เรื่องยาก บรรทัด 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; }
Ryan Vettese

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 */ }ในตัวอย่างข้อมูลที่สอง จะเป็นอย่างไรหากคุณต้องการใช้ฟังก์ชันและรายการเมนู "ปกติ" แต่เพียงลบรายการใดรายการหนึ่งออก (เช่นลบลบ แต่เปลี่ยนชื่อและสร้างต่อไป) ในมุมมองของฉันนั่นคือสิ่งที่ OP ถามอยู่แล้ว แน่นอนว่าคุณไม่จำเป็นต้องเขียนฟังก์ชันซ้ำสำหรับสิ่งต่างๆเช่นเปลี่ยนชื่อและสร้างหากคุณลบรายการอื่นเช่น Delete?
Andy

ฉันไม่แน่ใจว่าฉันเข้าใจคำถามของคุณ คุณกำลังกำหนดฟังก์ชันการทำงานทั้งหมดสำหรับเมนูบริบททั้งหมด (เช่นลบเปลี่ยนชื่อและสร้าง) ใน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

Btw: หากคุณเพียงแค่ต้องการลบตัวเลือกออกจากเมนูบริบทที่มีอยู่ - สิ่งนี้ใช้ได้กับฉัน:

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" 1 รายการในข้อมูล 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 เพื่อแยกความแตกต่างของโหนดต่างๆและขึ้นอยู่กับว่าคุณสามารถสร้างเมนูบริบทที่แตกต่างกันได้

ในตัวอย่างด้านล่างฉันใช้โหนดโฟลเดอร์และไฟล์สองประเภท ฉันใช้ไอคอนที่แตกต่างกันมากเกินไปโดยใช้ไกลฟีคอน สำหรับโหนดประเภทไฟล์คุณสามารถรับได้เฉพาะเมนูบริบทเพื่อเปลี่ยนชื่อและลบเท่านั้น สำหรับโฟลเดอร์มีตัวเลือกทั้งหมดสร้างไฟล์สร้างโฟลเดอร์เปลี่ยนชื่อลบ

สำหรับข้อมูลโค้ดที่สมบูรณ์คุณสามารถดู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);
                            }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.