【问题标题】:Configuring jstree right-click contextmenu for different node types为不同的节点类型配置jstree右键上下文菜单
【发布时间】:2010-12-30 02:24:44
【问题描述】:

我在网上看到过一个例子,展示了如何自定义 jstree 的右键上下文菜单的外观(使用 contextmenu 插件)。

例如,允许我的用户删除“文档”而不是“文件夹”(通过隐藏文件夹上下文菜单中的“删除”选项)。

现在我找不到那个例子了。谁能指出我正确的方向?官方documentation 并没有真正帮助。

编辑:

由于我希望默认上下文菜单只有一两个微小的变化,我宁愿不重新创建整个菜单(当然,如果这是唯一的方法,我会这样做)。我想做的是这样的:

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

但它不起作用 - 创建项目总是被禁用(警报永远不会出现)。

【问题讨论】:

    标签: jquery contextmenu jstree


    【解决方案1】:

    contextmenu 插件已经支持此功能。从您链接到的文档中:

    items:需要一个对象或一个函数,它应该返回一个对象。如果使用了一个函数,它会在树的上下文中触发并接收一个参数 - 被右键单击的节点。

    因此,您可以提供以下函数,而不是给contextmenu 一个硬编码的对象来使用。它检查为名为“文件夹”的类单击的元素,并通过从对象中删除“删除”菜单项来移除它:

    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 添加到相关项目。在这种情况下,您可以在 if 语句中使用 items.deleteItem._disabled = true

    应该很明显,但请记住使用 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()函数,在调用原始parse()之前评估disabled: function () {...}处的函数并将结果存储在_disabled中。

    直接修改他们的源代码也不难。 1.0-rc1 版本的第 2867 行是相关的:

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

    您可以简单地在此行之前添加一行来检查$.isFunction(val._disabled),如果是这样,val._disabled = val._disabled()。然后将其作为补丁提交给创作者:)

    【讨论】:

    • 谢谢。我以为我曾经看到过一个解决方案,它只涉及从默认更改需要更改的内容(而不是从头开始重新创建整个菜单)。如果在赏金到期之前没有更好的解决方案,我会接受这个答案。
    • @MGOwen,从概念上讲,我 am 正在修改“默认”,但是是的,您是对的,每次调用函数时都会重新创建对象。但是,需要先克隆默认值,否则会修改默认值本身(您需要更复杂的逻辑才能将其恢复为原始状态)。我能想到的另一种选择是将var items 移动到函数外部,以便它只创建一次,并从函数中返回一组项目,例如return {renameItem: items.renameItem};return {renameItem: items.renameItem, deleteItem: items.deleteItem};
    • 我特别喜欢最后一个,您可以在其中修改 jstree 源代码。我试过了,它有效,分配给“_disabled”的函数(在我的例子中)运行。但是,它没有帮助,因为我无法从函数范围内访问节点(我至少需要它的 rel 属性来按节点类型过滤节点)。我尝试检查可以从 jstree 源代码传入的变量,但找不到节点。有什么想法吗?
    • @MGOwen,看起来被点击的&lt;a&gt;元素存储在$.vakata.context.tgt。所以尝试查找$.vakata.context.tgt.attr("rel")
    • 在 jstree 3.0.8 中:if ($(node).hasClass("folder")) 不起作用。但这样做了:if (node.children.length &gt; 0) { items.deleteItem._disabled = true; }
    【解决方案2】:

    用不同的节点类型实现:

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

    【讨论】:

    • 我更喜欢这个答案,因为它依赖于 type 属性,而不是使用 jQuery 获得的 CSS 类。
    • 你在第二个sn-p的'action': function () { /* action */ }里面放了什么代码?如果您想使用“正常”功能和菜单项,但只是删除其中一项(例如,删除删除但保留重命名和创建)怎么办?在我看来,这确实是 OP 所要求的。如果您删除其他项目(例如删除),您肯定不需要重新编写重命名和创建等功能吗?
    • 我不确定我是否理解您的问题。您在items 对象列表中定义完整上下文菜单的所有功能(例如,删除、重命名和创建),然后在末尾指定要删除给定node.type 的哪些项目customMenu 函数。当用户单击给定type 的节点时,上下文菜单将列出所有项目减去在customMenu 函数末尾的条件中删除的任何项目。您没有重写任何功能(除非 jstree 自三年前此答案以来发生了变化,在这种情况下它可能不再相关)。
    【解决方案3】:

    清除一切。

    而不是这个:

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

    使用这个:

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

    【讨论】:

      【解决方案4】:

      我已经调整了建议的解决方案来处理类型有点不同,也许它可以帮助其他人:

      #{$id_arr[$k]} 是对 div 容器的引用......在我的例子中,我使用了很多树,所以所有这些代码都将成为浏览器的输出,但你明白了......基本上我想要所有上下文菜单选项,但只有 Drive 节点上的“创建”和“粘贴”。显然,稍后将正确绑定到这些操作:

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

      【讨论】:

        【解决方案5】:

        顺便说一句:如果您只想从现有的上下文菜单中删除选项 - 这对我有用:

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

        【讨论】:

          【解决方案6】:

          您可以修改@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”

          【讨论】:

            【解决方案7】:

            从 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 对象。

            【讨论】:

              【解决方案8】:

              David 的反应似乎很好而且很有效率。我发现了另一种解决方案的变体,您可以使用 a_attr 属性来区分不同的节点,并在此基础上生成不同的上下文菜单。

              在下面的示例中,我使用了两种类型的节点文件夹和文件。我也使用 glyphicon 使用了不同的图标。对于文件类型节点,您只能获取上下文菜单来重命名和删除。对于文件夹,所有选项都在那里,创建文件、创建文件夹、重命名、删除。

              完整代码sn-p,可以查看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'} },
                          ];
              

              作为创建文件和文件夹的相关菜单项的一部分,使用下面的类似代码作为文件操作。

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

              【讨论】:

                【解决方案9】:

                这是我的完整插件设置。

                var ktTreeDocument = $("#jstree_html_id");
                
                jQuery(document).ready(function () {
                    DocumentKTTreeview.init();
                });
                
                var DocumentKTTreeview = function() {
                    var treeDocument = function() {
                        ktTreeDocument.jstree({
                            "core": {
                                "themes": {
                                    "responsive": false
                                },
                                "check_callback": function(operation, node, node_parent, node_position, more) {
                                    documentAllModuleObj.selectedNode = ktTreeDocument.jstree().get_selected('full', true);
                                    if (operation === 'delete_node') {
                                        if (!confirm('are you sure?')) {
                                            return false;
                                        }
                                    }
                                    return true;
                                },
                                'data': {
                                    'dataType': 'json',
                                    'url': BASE_URL + ('tree/get/?lazy'),
                                    'data': function(node) {
                                        return { 'id': node.id };
                                    }
                                },
                            },
                            "types": {
                                "default": {
                                    "icon": "fa fa-folder kt-font-success"
                                },
                                "file": {
                                    "icon": "fa fa-file  kt-font-success"
                                }
                            },
                            "state": { "key": "demo2" },
                            "plugins": ["contextmenu", "dnd", "state", "types"],
                            "contextmenu": {
                                "items": function($node) {
                                    var tree = $("#jstree_html_id").jstree(true);
                                    return {
                                        "Create": {
                                            "separator_before": false,
                                            "separator_after": false,
                                            "label": "Create",
                                            "action": function(obj) {
                                                tree.create_node($node);
                                            }
                                        },
                                        "Rename": {
                                            "separator_before": false,
                                            "separator_after": false,
                                            "label": "Rename",
                                            "action": function(obj) {
                                                tree.edit($node);
                                            }
                                        },
                                        "Remove": {
                                            "separator_before": false,
                                            "separator_after": false,
                                            "_disabled": $node.original.root ? true : false,
                                            "label": "Remove",
                                            "action": function(obj) {
                                                tree.delete_node($node);
                                            }
                                        }
                                    };
                                }
                            }
                        })
                    }
                    return {
                        init: function() {
                            treeDocument();
                        }
                    };
                }();
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2012-02-03
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多