다양한 노드 유형에 대해 jstree 오른쪽 클릭 컨텍스트 메뉴 구성


85

온라인 어딘가에서 jstree의 오른쪽 클릭 컨텍스트 메뉴 (contextmenu 플러그인 사용)의 모양을 사용자 정의하는 방법을 보여주는 예제를 보았습니다.

예를 들어, 내 사용자가 "문서"는 삭제할 수 있지만 "폴더"는 삭제할 수 없도록 허용합니다 (폴더의 상황에 맞는 메뉴에서 "삭제"옵션을 숨겨서).

이제 그 예를 찾을 수 없습니다. 누구든지 올바른 방향으로 나를 가리킬 수 있습니까? 공식 문서 는 실제로 도움이되지 않았습니다.

편집하다:

하나 또는 두 개의 사소한 변경 만있는 기본 컨텍스트 메뉴를 원하기 때문에 전체 메뉴를 다시 생성하지 않는 것이 좋습니다 (물론 그것이 유일한 방법이라면 할 것입니다). 내가하고 싶은 것은 다음과 같습니다.

"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작업 할 하드 코딩 된 개체를 제공 하는 대신 다음 함수를 제공 할 수 있습니다. "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 = 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()

소스 코드를 직접 수정하는 것도 어렵지 않습니다. 버전 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은 함수 외부 로 이동 하여 한 번만 생성되도록하고 함수에서 선택한 항목을 반환하는 것입니다. 예 : 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").
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
typejQuery를 사용하여 얻은 CSS 클래스보다는 속성에 의존하기 때문에이 답변을 선호합니다 .
Benny Bottema

'action': function () { /* action */ }두 번째 스 니펫에 어떤 코드를 삽입 하고 있습니까? "일반"기능과 메뉴 항목을 사용하고 싶지만 그 중 하나만 제거하려면 (예 : 삭제를 제거하고 이름 바꾸기 및 만들기는 유지)? 제 생각에는 그것이 OP가 어쨌든 요구했던 것입니다. 삭제와 같은 다른 항목을 제거하는 경우 이름 바꾸기 및 만들기와 같은 기능을 다시 작성할 필요가 없습니다.
Andy

질문을 이해했는지 잘 모르겠습니다. 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

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

}  

XML 또는 JSOn 데이터에 "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

David의 반응은 훌륭하고 효율적으로 보입니다. a_attr 속성을 사용하여 다른 노드를 구별 할 수 있고이를 기반으로 다른 컨텍스트 메뉴를 생성 할 수있는 솔루션의 또 다른 변형을 찾았습니다.

아래 예에서는 두 가지 유형의 노드 폴더 및 파일을 사용했습니다. 나는 glyphicon을 사용하여 다른 아이콘도 사용했습니다. 파일 유형 노드의 경우 컨텍스트 메뉴 만 가져와 이름을 바꾸고 제거 할 수 있습니다. 폴더의 경우 모든 옵션이 있습니다. 파일 생성, 폴더 생성, 이름 바꾸기, 제거.

전체 코드 스 니펫은 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.