Jstree sağ tıklama bağlam menüsünü farklı düğüm türleri için yapılandırma


85

Çevrimiçi bir yerde jstree'nin sağ tıklama bağlam menüsünün görünümünü nasıl özelleştireceğinizi gösteren bir örnek gördüm (bağlam menüsü eklentisini kullanarak).

Örneğin, kullanıcılarımın "belgeleri" silmelerine ancak "klasörleri" silmemelerine izin ver (klasörler için bağlam menüsünden "sil" seçeneğini gizleyerek).

Şimdi bu örneği bulamıyorum. Biri beni doğru yöne yönlendirebilir mi? Resmi belgeler gerçekten yardımcı olmadı.

Düzenle:

Varsayılan bağlam menüsünü yalnızca bir veya iki küçük değişiklikle istediğim için, tüm menüyü yeniden oluşturmamayı tercih ederim (elbette tek yol buysa yapacağım). Yapmak istediğim şey şöyle bir şey:

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

ancak çalışmaz - öğe oluşturma her zaman devre dışıdır (uyarı hiçbir zaman görünmez).

Yanıtlar:


145

contextmenuEklenti zaten bu desteği vardır. Bağladığınız belgelerden:

items: Bir nesneyi döndürmesi gereken bir nesne veya işlev bekler . Bir işlev kullanılırsa, ağacın bağlamında tetiklenir ve bir bağımsız değişken alır - sağ tıklanan düğüm.

Bu nedenle contextmenu, çalışmak için sabit kodlanmış bir nesne vermek yerine , aşağıdaki işlevi sağlayabilirsiniz. "Klasör" adlı bir sınıf için tıklanan öğeyi kontrol eder ve "sil" menü öğesini nesneden silerek kaldırır:

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

Yukarıdakinin silme seçeneğini tamamen gizleyeceğini unutmayın, ancak eklenti ayrıca bir öğeyi _disabled: trueilgili öğeye ekleyerek davranışını devre dışı bırakırken göstermenize de izin verir . Bu durumda bunun yerine ifade items.deleteItem._disabled = trueiçinde kullanabilirsiniz if.

Açık olmalı, ancak eklentiyi customMenudaha önce sahip olduğunuz işlev yerine işlevle başlatmayı unutmayın :

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

Düzenle: Menünün her sağ tıklamada yeniden oluşturulmasını istemiyorsanız, silme menüsü öğesinin kendisinin eylem işleyicisine mantığı koyabilirsiniz.

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

Tekrar düzenle: jsTree kaynak koduna baktıktan sonra, her şekilde gösterildiğinde bağlam menüsü yeniden oluşturuluyor gibi görünüyor ( show()ve parse()işlevlerine bakın), bu nedenle ilk çözümümde bir sorun görmüyorum.

Bununla birlikte, önerdiğiniz gösterimi, değeri bir işlevle beğeniyorum _disabled. Keşfedilecek potansiyel bir yol , orijinali çağırmadan önce parse()işlevlerini, işlevinde değerlendiren disabled: function () {...}ve sonucu depolayan kendi işlevinizle sarmalamaktır ._disabledparse()

Kaynak kodunu doğrudan değiştirmek de zor olmayacak. 1.0-rc1 sürümünün 2867 satırı bununla ilgilidir:

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

Bundan önce basitçe kontrol eden bir satır ekleyebilirsiniz $.isFunction(val._disabled)ve eğer öyleyse val._disabled = val._disabled(),. Ardından bunu içerik oluşturuculara yama olarak gönderin :)


Teşekkürler. Bir keresinde, yalnızca varsayılandan (tüm menüyü sıfırdan yeniden oluşturmak yerine) değiştirilmesi gerekenleri değiştirmeyi içeren bir çözüm gördüğümü düşünmüştüm. Ödülün süresi dolmadan daha iyi bir çözüm yoksa bu cevabı kabul edeceğim.
MGOwen

@MGOwen, kavramsal olarak ben am "varsayılan" modifiye ancak nesne işlevi denir yeniden oluşturulan her zaman aldığını evet haklısın. Ancak, önce varsayılanın klonlanması gerekir, aksi takdirde varsayılanın kendisi değiştirilir (ve onu orijinal durumuna geri döndürmek için daha karmaşık mantığa ihtiyacınız olacak). Düşünebildiğim bir alternatif var items, işlevin dışına çıkarak yalnızca bir kez oluşturulmuş olması ve işlevden bir öğe seçkisi döndürmek, örneğin return {renameItem: items.renameItem};veyareturn {renameItem: items.renameItem, deleteItem: items.deleteItem};
David Tang

Özellikle jstree kaynağını değiştirdiğiniz sonuncusu seviyorum. Denedim ve çalışıyor, "_disabled" a atanan fonksiyon (benim örneğimde) çalışıyor. Ancak, bu yardımcı olmuyor çünkü düğüme erişemiyorum (en azından düğüm türüne göre düğümleri filtrelemek için rel özniteliğine ihtiyacım var) işlevin kapsamı içinden. Jstree kaynak kodundan geçirebildiğim değişkenleri incelemeye çalıştım ama düğümü bulamadım. Herhangi bir fikir?
MGOwen

@MGOwen, <a>tıklanan öğenin depolanmış gibi görünüyor $.vakata.context.tgt. Bu yüzden yukarı bakmayı deneyin $.vakata.context.tgt.attr("rel").
David Tang

1
jstree 3.0.8'de: if ($(node).hasClass("folder")) işe yaramadı. ama bu yaptı: if (node.children.length > 0) { items.deleteItem._disabled = true; }
Ryan Vettese

19

Farklı düğüm türleriyle uygulanmıştır:

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

Ve customMenu işlevi:

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

Güzel çalışıyor.


1
Bu cevabı typejQuery kullanılarak elde edilen bir CSS sınıfından ziyade özniteliğe dayandığı için tercih ediyorum .
Benny Bottema

'action': function () { /* action */ }İkinci parçacığın içine hangi kodu koyuyorsunuz ? Ya "normal" işlevselliği ve menü öğelerini kullanmak, ancak bunlardan birini kaldırmak istiyorsanız (örneğin, Sil'i kaldırıp Yeniden Adlandır ve Oluşturmaya devam edin)? Benim görüşüme göre, OP'nin zaten sorduğu şey buydu. Sil gibi başka bir öğeyi kaldırırsanız, Yeniden Adlandır ve Oluştur gibi şeyler için işlevselliği yeniden yazmanıza kesinlikle gerek yoktur.
Andy

Sorunuzu anladığımdan emin değilim. itemsNesneler listesindeki tam bağlam menüsü (ör. Sil, Yeniden Adlandır ve Oluştur) için tüm işlevselliği tanımlıyorsunuz , ardından işlevin node.typesonunda verilen bir öğe için bu öğelerden hangisinin kaldırılacağını customMenubelirtiyorsunuz. Kullanıcı verilen düğüme tıkladığında type, bağlam menüsü tüm öğeleri, customMenuişlevin sonundaki koşulludan kaldırılanları çıkararak listeler . Herhangi bir işlevi yeniden yazmıyorsunuz (jstree üç yıl önce bu yanıttan sonra değişmedikçe, bu durumda artık geçerli olmayabilir).
yığılmış

12

Her şeyi temizlemek için.

Bunun yerine:

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

Bunu kullan:

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

5

Türlerle çalışmak için önerilen çözümü biraz farklı şekilde uyarladım, belki başka birine yardımcı olabilir:

# {$ İd_arr [$ k]}, div kapsayıcısına referanstır ... benim durumumda birçok ağaç kullanıyorum, bu nedenle tüm bu kod tarayıcıya çıktı olacak, ancak fikri anlıyorsunuz .. Temelde hepsini istiyorum bağlam menüsü seçenekleri, ancak Drive düğümünde yalnızca "Oluştur" ve "Yapıştır". Açıkçası, daha sonra bu işlemlere doğru bağlamalarla:

<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: Mevcut bağlam menüsünden seçenekleri kaldırmak istiyorsanız - bu benim için çalıştı:

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 kodunu, bağlam menüsünü dinamik olarak devre dışı bırakma gereksiniminize uyacak şekilde değiştirebilirsiniz:

function customMenu(node) {

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


       items.deleteItem._disabled = true;
    }   

}  

XML veya JSOn verilerinize bir "xyz" özniteliği eklemeniz gerekir


1

jsTree 3.0.9 itibariyle şunun gibi bir şey kullanmam gerekti

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

çünkü sağlanan nodenesne bir jQuery nesnesi değildir.


1

David'in cevabı güzel ve verimli görünüyor. Farklı düğümleri ayırt etmek için a_attr özniteliğini kullanabileceğiniz ve buna dayalı olarak farklı bağlam menüsü oluşturabileceğiniz çözümün başka bir varyasyonunu buldum.

Aşağıdaki örnekte, Klasör ve Dosyalar olmak üzere iki tür düğüm kullandım. Glificon kullanarak da farklı simgeler kullandım. Dosya türü düğümü için, yalnızca yeniden adlandırmak ve kaldırmak üzere bağlam menüsünü alabilirsiniz. Klasör için tüm seçenekler var, dosya oluştur, klasör oluştur, yeniden adlandır, kaldır.

Tam kod parçacığı için https://everyething.com/Example-of-jsTree-with-different-context-menu-for-different-node-type görüntüleyebilirsiniz.

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

İlk json verileri aşağıdaki gibidir, burada düğüm türü a_attr içinde belirtilmiştir.

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'} },
            ];

Bir dosya ve klasör oluşturmak için contect menü öğesinin parçası olarak, dosya eylemi olarak aşağıdaki benzer kodu kullanın.

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

klasör eylemi olarak:

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);
                            }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.