さまざまなノードタイプのjstree右クリックコンテキストメニューの構成


85

jstreeの右クリックコンテキストメニューの外観をカスタマイズする方法を示す例をオンラインのどこかで見ました(contextmenuプラグインを使用)。

たとえば、ユーザーが「ドキュメント」を削除できるようにしますが、「フォルダ」は削除できないようにします(フォルダのコンテキストメニューから「削除」オプションを非表示にします)。

今、私はその例を見つけることができません。誰かが私を正しい方向に向けることができますか?公式ドキュメントは実際には役に立ちませんでした。

編集:

1つまたは2つの小さな変更のみを含むデフォルトのコンテキストメニューが必要なので、メニュー全体を再作成しないことをお勧めします(もちろん、それが唯一の方法である場合は作成します)。私がやりたいのは次のようなものです。

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

ただし、機能しません。作成アイテムは常に無効になっています(アラートは表示されません)。

回答:


144

contextmenuプラグインはすでにこのためのサポートを持っています。リンクしたドキュメントから:

items:オブジェクトまたは関数を期待しますこれらはオブジェクトを返す必要があります。関数が使用される場合、それはツリーのコンテキストで起動され、1つの引数(右クリックされたノード)を受け取ります。

したがってcontextmenu、ハードコードされたオブジェクトを操作するように指定するのではなく、次の関数を指定できます。「folder」という名前のクラスでクリックされた要素をチェックし、オブジェクトから削除することで「delete」メニュー項目を削除します。

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 = trueif代わりにステートメント内で使用できます。

明らかなはず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()

ソースコードを直接変更することも難しくありません。バージョン1.0-rc1の2867行目が関連しています。

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

この行の前に、をチェックする行を追加するだけ$.isFunction(val._disabled)で、そうであれば、を追加できval._disabled = val._disabled()ます。次に、パッチとして作成者に送信します:)


ありがとう。メニュー全体を最初から作り直すのではなく、デフォルトから変更する必要があるものだけを変更するという解決策を見たことがあると思いました。報奨金の期限が切れる前にこれ以上の解決策がない場合は、この回答を受け入れます。
MGOwen 2011年

@MGOwen、概念的に「デフォルト」を変更していますが、関数が呼び出されるたびにオブジェクトが再作成されるのは確かです。ただし、デフォルトを最初に複製する必要があります。そうしないと、デフォルト自体が変更されます(元の状態に戻すには、より複雑なロジックが必要になります)。私が考えることができる別の方法var itemsは、関数の外側に移動して1回だけ作成し、関数から選択したアイテムを返すことです。たとえば、return {renameItem: items.renameItem};またはreturn {renameItem: items.renameItem, deleteItem: items.deleteItem};
David Tang

jstreeソースを変更する最後のものが特に好きです。試してみましたが、動作し、「_ disabled」(私の例では)に割り当てられた関数が実行されます。ただし、関数のスコープ内からノードにアクセスできないため(少なくとも、ノードタイプでノードをフィルタリングするにはrel属性が必要です)、役に立ちません。jstreeソースコードから渡すことができる変数を調べてみましたが、ノードが見つかりませんでした。何か案は?
MGOwen 2011年

@MGOwen、<a>クリックされた要素はに保存されているよう$.vakata.context.tgtです。だから調べてみてください$.vakata.context.tgt.attr("rel")
デビッドタン

1
jstree 3.0.8の場合:機能 if ($(node).hasClass("folder")) しませんでした。しかし、これは: if (node.children.length > 0) { items.deleteItem._disabled = true; }
Ryan Vettese 2014年

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
typejQueryを使用して取得したCSSクラスではなく、属性に依存しているため、この回答を好みます。
ベニーボッテマ2017年

'action': function () { /* action */ }2番目のスニペットにどのコードを入れていますか?「通常の」機能とメニュー項目を使用したいが、それらの1つを単に削除したい場合はどうなりますか(たとえば、削除を削除し、名前の変更と作成は保持します)。私の見解では、それは本当にOPがとにかく求めていたものです。確かに、削除などの別のアイテムを削除した場合、名前の変更や作成などの機能を書き直す必要はありませんか?
アンディ

あなたの質問を理解できるかわかりません。itemsオブジェクトのリストで完全なコンテキストメニューのすべての機能(たとえば、削除、名前の変更、作成)を定義し、関数node.typeの最後で、これらの項目のどれを削除するかを指定しますcustomMenu。ユーザーが指定されたノードをクリックするtypeと、コンテキストメニューに、customMenu関数の最後の条件で削除されたものを除いたすべての項目が一覧表示されます。機能を書き直していません(3年前のこの回答以降に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

ところで:既存のコンテキストメニューからオプションを削除したいだけなら-これは私にとってはうまくいきました:

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

}  

XMLまたはJSOnデータに1つの属性「xyz」を追加する必要があります


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属性を使用してさまざまなノードを区別し、それに基づいてさまざまなコンテキストメニューを生成できる、ソリューションの別のバリエーションを見つけました。

以下の例では、FolderとFilesの2種類のノードを使用しました。私もグリフィコンを使って別のアイコンを使いました。ファイルタイプノードの場合、名前を変更して削除するコンテキストメニューのみを取得できます。フォルダには、ファイルの作成、フォルダの作成、名前の変更、削除など、すべてのオプションがあります。

完全なコードスニペットについては、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'} },
            ];

ファイルとフォルダを作成するためのcontectメニュー項目の一部として、ファイルアクションとして以下の同様のコードを使用します。

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.