Configurazione del menu contestuale del tasto destro del mouse jstree per diversi tipi di nodo


85

Ho visto un esempio da qualche parte online che mostra come personalizzare l'aspetto del menu contestuale del tasto destro di jstree (utilizzando il plugin del menu contestuale).

Ad esempio, consenti ai miei utenti di eliminare "documenti" ma non "cartelle" (nascondendo l'opzione "elimina" dal menu contestuale per le cartelle).

Ora non riesco a trovare quell'esempio. Qualcuno può indicarmi la giusta direzione? La documentazione ufficiale non è stata d'aiuto.

Modificare:

Dal momento che voglio il menu di scelta rapida predefinito con solo una o due modifiche minori, preferirei non ricreare l'intero menu (anche se ovviamente lo farò se è l'unico modo). Quello che mi piacerebbe fare è qualcosa del genere:

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

ma non funziona: l'elemento creato è sempre disabilitato (l'avviso non viene mai visualizzato).

Risposte:


145

Il contextmenuplugin ha già il supporto per questo. Dalla documentazione a cui ti sei collegato:

items: Si aspetta un oggetto o una funzione, che dovrebbe restituire un oggetto . Se viene utilizzata una funzione, viene attivata nel contesto dell'albero e riceve un argomento: il nodo su cui è stato fatto clic con il pulsante destro del mouse.

Quindi, invece di fornire contextmenuun oggetto hardcoded con cui lavorare, puoi fornire la seguente funzione. Controlla l'elemento su cui è stato fatto clic per una classe denominata "cartella" e rimuove la voce di menu "elimina" eliminandola dall'oggetto:

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

Nota che quanto sopra nasconderà completamente l'opzione di cancellazione, ma il plugin ti consente anche di mostrare un elemento disabilitandone il comportamento, aggiungendolo _disabled: trueall'elemento pertinente. In questo caso puoi invece usare items.deleteItem._disabled = trueall'interno ifdell'istruzione.

Dovrebbe essere ovvio, ma ricorda di inizializzare il plugin con la customMenufunzione invece di quella che avevi in ​​precedenza:

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

Modifica: se non desideri ricreare il menu a ogni clic con il pulsante destro del mouse, puoi inserire la logica nel gestore delle azioni per la voce di menu Elimina stessa.

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

Modifica di nuovo: dopo aver esaminato il codice sorgente di jsTree, sembra che il menu contestuale venga ricreato ogni volta che viene mostrato comunque (vedere le funzioni show()e parse()), quindi non vedo alcun problema con la mia prima soluzione.

Tuttavia, mi piace la notazione che suggerisci, con una funzione come valore per _disabled. Un potenziale percorso da esplorare è avvolgere la loro parse()funzione con la tua che valuta la funzione in disabled: function () {...}e memorizza il risultato _disabled, prima di chiamare l'originale parse().

Non sarà nemmeno difficile modificare direttamente il loro codice sorgente. La riga 2867 della versione 1.0-rc1 è quella pertinente:

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

Puoi semplicemente aggiungere una riga prima di questa che controlla $.isFunction(val._disabled)e, in caso affermativo val._disabled = val._disabled(),. Quindi invialo ai creatori come patch :)


Grazie. Pensavo di aver visto una volta una soluzione che comportava la modifica solo di ciò che era necessario cambiare rispetto all'impostazione predefinita (piuttosto che ricreare l'intero menu da zero). Accetterò questa risposta se non esiste una soluzione migliore prima della scadenza della taglia.
MGOwen

@ MGOwen, concettualmente sto modificando il "default", ma sì, hai ragione che l'oggetto viene ricreato ogni volta che viene chiamata la funzione. Tuttavia, l'impostazione predefinita deve essere prima clonata, altrimenti l'impostazione predefinita stessa viene modificata (e avrai bisogno di una logica più complessa per ripristinarla allo stato originale). Un'alternativa a cui riesco a pensare è spostarmi var itemsall'esterno della funzione in modo che venga creata una sola volta e restituire una selezione di elementi dalla funzione, ad esempio return {renameItem: items.renameItem};oreturn {renameItem: items.renameItem, deleteItem: items.deleteItem};
David Tang

Mi piace particolarmente quest'ultimo, in cui modifichi il sorgente jstree. L'ho provato e funziona, la funzione assegnata a "_disabled" (nel mio esempio) viene eseguita. Ma non aiuta perché non riesco ad accedere al nodo (ho almeno bisogno del suo attributo rel per filtrare i nodi per tipo di nodo) dall'ambito della funzione. Ho provato a ispezionare le variabili che potevo passare dal codice sorgente jstree ma non sono riuscito a trovare il nodo. Qualche idea?
MGOwen

@ MGOwen, sembra che l' <a>elemento su cui è stato fatto clic sia memorizzato in $.vakata.context.tgt. Quindi prova a guardare in alto $.vakata.context.tgt.attr("rel").
David Tang,

1
in jstree 3.0.8: if ($(node).hasClass("folder")) non funzionava. ma questo ha fatto: if (node.children.length > 0) { items.deleteItem._disabled = true; }
Ryan Vettese

19

Implementato con diversi tipi di nodo:

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

E la funzione 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;
}

Funziona magnificamente.


1
Preferisco questa risposta in quanto si basa sull'attributo typepiuttosto che su una classe CSS ottenuta utilizzando jQuery.
Benny Bottema

Quale codice stai inserendo 'action': function () { /* action */ }nel secondo frammento? Cosa succede se si desidera utilizzare la funzionalità "normale" e le voci di menu, ma semplicemente rimuoverne una (es. Rimuovere Elimina ma mantenere Rinomina e Crea)? A mio avviso, è proprio quello che chiedeva comunque l'OP. Sicuramente non hai bisogno di riscrivere funzionalità per cose come Rinomina e Crea se rimuovi un altro elemento come Elimina?
Andy

Non sono sicuro di aver capito la tua domanda. Stai definendo tutte le funzionalità per il menu contestuale completo (ad esempio, Elimina, Rinomina e Crea) itemsnell'elenco degli oggetti, quindi specifichi quale di questi elementi rimuovere per un dato node.typealla fine della customMenufunzione. Quando l'utente fa clic su un nodo di un dato type, il menu contestuale elencherà tutti gli elementi meno quelli rimossi nel condizionale alla fine della customMenufunzione. Non stai riscrivendo alcuna funzionalità (a meno che jstree non sia cambiato da questa risposta tre anni fa, nel qual caso potrebbe non essere più pertinente).
accatastato il

12

Per cancellare tutto.

Invece di questo:

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

Usa questo:

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

5

Ho adattato la soluzione suggerita per lavorare con i tipi in modo leggermente diverso, forse può aiutare qualcun altro:

Dove # {$ id_arr [$ k]} è il riferimento al contenitore div ... nel mio caso uso molti alberi quindi tutto questo codice sarà l'output del browser, ma hai un'idea .. Fondamentalmente voglio tutto le opzioni del menu contestuale ma solo "Crea" e "Incolla" sul nodo Drive. Ovviamente con i collegamenti corretti a quelle operazioni successive:

<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: se vuoi solo rimuovere le opzioni dal menu contestuale esistente, questo ha funzionato per me:

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

È possibile modificare il codice @ Box9 in base alle proprie esigenze di disabilitazione dinamica del menu contestuale come:

function customMenu(node) {

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


       items.deleteItem._disabled = true;
    }   

}  

È necessario aggiungere un attributo "xyz" nei dati XML o JSOn


1

a partire da jsTree 3.0.9 avevo bisogno di usare qualcosa di simile

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

perché l' nodeoggetto fornito non è un oggetto jQuery.


1

La risposta di David sembra buona ed efficiente. Ho trovato un'altra variante della soluzione in cui è possibile utilizzare l'attributo a_attr per differenziare diversi nodi e in base a ciò è possibile generare un menu di scelta rapida diverso.

Nell'esempio seguente, ho utilizzato due tipi di nodi Cartella e File. Ho usato anche icone diverse usando glyphicon. Per il tipo di file node, puoi solo ottenere il menu contestuale da rinominare e rimuovere. Per Cartella, sono disponibili tutte le opzioni, crea file, crea cartella, rinomina, rimuovi.

Per lo snippet di codice completo, puoi visualizzare 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);                        
                    }
                }
            });

I dati json iniziali sono stati i seguenti, dove il tipo di nodo è menzionato all'interno di 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'} },
            ];

Come parte della voce di menu contect per creare un file e una cartella, utilizzare un codice simile di seguito, come azione sul file.

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

come azione della cartella:

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);
                            }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.