Konfigurieren des Kontextmenüs jstree mit der rechten Maustaste für verschiedene Knotentypen


84

Ich habe irgendwo online ein Beispiel gesehen, das zeigt, wie man das Erscheinungsbild des Kontextmenüs von jstree mit der rechten Maustaste anpasst (mithilfe des Kontextmenü-Plugins).

Erlauben Sie meinen Benutzern beispielsweise, "Dokumente", aber nicht "Ordner" zu löschen (indem Sie die Option "Löschen" im Kontextmenü für Ordner ausblenden).

Jetzt kann ich dieses Beispiel nicht finden. Kann mich jemand in die richtige Richtung weisen? Die offizielle Dokumentation hat nicht wirklich geholfen.

Bearbeiten:

Da ich das Standardkontextmenü mit nur ein oder zwei geringfügigen Änderungen verwenden möchte, würde ich es vorziehen, nicht das gesamte Menü neu zu erstellen (obwohl ich es natürlich tun werde, wenn dies der einzige Weg ist). Was ich gerne machen würde, ist ungefähr so:

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

aber es funktioniert nicht - das Erstellungselement ist einfach immer deaktiviert (die Warnung wird nie angezeigt).

Antworten:


143

Das contextmenuPlugin unterstützt dies bereits. Aus der Dokumentation, die Sie verlinkt haben:

items: Erwartet ein Objekt oder eine Funktion, die ein Objekt zurückgeben soll . Wenn eine Funktion verwendet wird, wird sie im Kontext des Baums ausgelöst und erhält ein Argument - den Knoten, auf den mit der rechten Maustaste geklickt wurde.

Anstatt contextmenuein fest codiertes Objekt zum Arbeiten anzugeben, können Sie die folgende Funktion bereitstellen. Es überprüft das Element, auf das geklickt wurde, auf eine Klasse mit dem Namen "Ordner" und entfernt das Menüelement "Löschen", indem es aus dem Objekt gelöscht wird:

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

Beachten Sie, dass oben die Löschoption vollständig ausgeblendet wird. Mit dem Plugin können Sie jedoch auch ein Element anzeigen, während Sie dessen Verhalten deaktivieren, indem Sie _disabled: truees dem entsprechenden Element hinzufügen . In diesem Fall können Sie stattdessen items.deleteItem._disabled = trueinnerhalb der ifAnweisung verwenden.

Sollte offensichtlich sein, aber denken Sie daran, das Plugin mit der customMenuFunktion zu initialisieren, anstatt mit dem, was Sie zuvor hatten:

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

Bearbeiten: Wenn Sie nicht möchten, dass das Menü bei jedem Rechtsklick neu erstellt wird, können Sie die Logik in den Aktionshandler für das Menüelement Löschen selbst einfügen.

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

Erneut bearbeiten: Nach dem Betrachten des jsTree-Quellcodes sieht es so aus, als würde das Kontextmenü jedes Mal neu erstellt, wenn es trotzdem angezeigt wird (siehe show()und parse()Funktionen), sodass ich bei meiner ersten Lösung kein Problem sehe.

Ich mag jedoch die von Ihnen vorgeschlagene Notation mit einer Funktion als Wert für _disabled. Ein möglicher Weg, den Sie erkunden sollten, besteht darin, ihre parse()Funktion mit Ihrer eigenen zu versehen, die die Funktion bei bewertet disabled: function () {...}und das Ergebnis darin speichert _disabled, bevor Sie das Original aufrufen parse().

Es wird auch nicht schwierig sein, den Quellcode direkt zu ändern. Zeile 2867 der Version 1.0-rc1 ist die relevante:

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

Sie können einfach eine Zeile vor dieser hinzufügen, die dies überprüft $.isFunction(val._disabled), und wenn ja val._disabled = val._disabled(). Dann sende es den Machern als Patch :)


Vielen Dank. Ich dachte, ich hätte einmal eine Lösung gesehen, bei der nur das geändert wurde, was von der Standardeinstellung geändert werden musste (anstatt das gesamte Menü von Grund auf neu zu erstellen). Ich werde diese Antwort akzeptieren, wenn es keine bessere Lösung gibt, bevor das Kopfgeld abläuft.
MGOwen

@MGOwen, konzeptionell ich bin Modifizieren der „default“, aber ja du hast Recht , dass das Objekt wird jedes Mal neu erstellt wird die Funktion aufgerufen. Der Standard muss jedoch zuerst geklont werden, andernfalls wird der Standard selbst geändert (und Sie benötigen eine komplexere Logik, um ihn wieder in den ursprünglichen Zustand zu versetzen). Eine Alternative, die ich mir vorstellen kann, besteht darin, var itemssich außerhalb der Funktion zu bewegen , damit sie nur einmal erstellt wird, und eine Auswahl von Elementen aus der Funktion zurückzugeben, z. B. return {renameItem: items.renameItem};oderreturn {renameItem: items.renameItem, deleteItem: items.deleteItem};
David Tang

Das letzte gefällt mir besonders gut, wenn Sie die jstree-Quelle ändern. Ich habe es versucht und es funktioniert, die Funktion "_disabled" (in meinem Beispiel) wird ausgeführt. Dies hilft jedoch nicht, da ich im Funktionsumfang nicht auf den Knoten zugreifen kann (ich benötige zumindest das rel-Attribut, um Knoten nach Knotentyp zu filtern). Ich habe versucht, die Variablen zu untersuchen, die ich aus dem jstree-Quellcode übergeben konnte, konnte aber den Knoten nicht finden. Irgendwelche Ideen?
MGOwen

@MGOwen, es sieht so aus, als ob das <a>Element, auf das geklickt wurde, unter gespeichert ist $.vakata.context.tgt. Also versuchen Sie nachzuschauen $.vakata.context.tgt.attr("rel").
David Tang

1
in jstree 3.0.8: if ($(node).hasClass("folder")) hat nicht funktioniert. aber das tat: if (node.children.length > 0) { items.deleteItem._disabled = true; }
Ryan Vettese

19

Implementiert mit verschiedenen Knotentypen:

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

Und die customMenu-Funktion:

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

Funktioniert wunderbar.


1
Ich bevorzuge diese Antwort, da sie sich typeeher auf das Attribut als auf eine mit jQuery erhaltene CSS-Klasse stützt .
Benny Bottema

Welchen Code fügen Sie 'action': function () { /* action */ }in das zweite Snippet ein? Was ist, wenn Sie die "normalen" Funktionen und Menüelemente verwenden möchten, aber einfach eines davon entfernen möchten (z. B. Löschen entfernen, aber Umbenennen und Erstellen beibehalten)? Meiner Meinung nach hat das OP sowieso wirklich darum gebeten. Sicherlich müssen Sie die Funktionen für Dinge wie Umbenennen und Erstellen nicht neu schreiben, wenn Sie ein anderes Element wie Löschen entfernen.
Andy

Ich bin mir nicht sicher, ob ich deine Frage verstehe. Sie definieren alle Funktionen für das vollständige Kontextmenü (z. B. Löschen, Umbenennen und Erstellen) in der itemsListe der Objekte und geben dann node.typeam Ende der customMenuFunktion an , welche dieser Elemente für ein bestimmtes Element entfernt werden sollen. Wenn der Benutzer auf einen bestimmten Knoten klickt type, werden im Kontextmenü alle Elemente abzüglich aller in der Bedingung am Ende der customMenuFunktion entfernten Elemente aufgelistet . Sie schreiben keine Funktionen neu (es sei denn, jstree hat sich seit dieser Antwort vor drei Jahren geändert. In diesem Fall ist sie möglicherweise nicht mehr relevant).
gestapelt

12

Alles klären.

An Stelle von:

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

Benutze das:

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

5

Ich habe die vorgeschlagene Lösung für die Arbeit mit Typen etwas anders angepasst, vielleicht kann sie jemand anderem helfen:

Wobei # {$ id_arr [$ k]} der Verweis auf den div-Container ist ... in meinem Fall verwende ich viele Bäume, sodass der gesamte Code an den Browser ausgegeben wird, aber Sie haben die Idee. Grundsätzlich möchte ich alles die Kontextmenüoptionen, aber nur 'Erstellen' und 'Einfügen' auf dem Laufwerksknoten. Offensichtlich mit den richtigen Bindungen zu diesen Operationen später:

<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

Übrigens: Wenn Sie nur Optionen aus dem vorhandenen Kontextmenü entfernen möchten, hat dies bei mir funktioniert:

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

Sie können den @ Box9-Code so ändern, dass er Ihren Anforderungen an die dynamische Deaktivierung des Kontextmenüs entspricht:

function customMenu(node) {

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


       items.deleteItem._disabled = true;
    }   

}  

Sie müssen ein Attribut "xyz" in Ihre XML- oder JSOn-Daten einfügen


1

Ab jsTree 3.0.9 musste ich so etwas verwenden

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

weil das nodebereitgestellte Objekt kein jQuery-Objekt ist.


1

Davids Antwort scheint gut und effizient zu sein. Ich habe eine andere Variante der Lösung gefunden, bei der Sie das Attribut a_attr verwenden können, um verschiedene Knoten zu unterscheiden, und basierend darauf können Sie verschiedene Kontextmenüs generieren.

Im folgenden Beispiel habe ich zwei Arten von Knoten verwendet: Ordner und Dateien. Ich habe auch verschiedene Symbole mit Glyphicon verwendet. Für Dateitypknoten können Sie nur das Kontextmenü zum Umbenennen und Entfernen aufrufen. Für Ordner stehen alle Optionen zur Verfügung: Datei erstellen, Ordner erstellen, umbenennen, entfernen.

Für ein vollständiges Code-Snippet können Sie https://everyething.com/Example-of-jsTree-with-different-context-menu-for-different-node-type anzeigen

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

Die anfänglichen JSON-Daten waren wie folgt, wobei der Knotentyp in a_attr erwähnt wird.

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

Verwenden Sie als Teil des Kontextmenüelements zum Erstellen einer Datei und eines Ordners den folgenden Code als Dateiaktion.

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

als Ordneraktion:

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);
                            }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.