You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用jsTreeR创建不可编辑的叶子节点子集

使用jsTreeR实现不可编辑叶子节点及关联分支保护

需求说明

需要创建jsTreeR树结构,满足以下要求:

  • 部分叶子节点不可操作:禁止重命名、删除、添加子节点
  • 若分支节点包含上述不可操作叶子,则该分支节点也不能被删除
  • 不可操作叶子节点允许在不同分支/父节点间拖拽

尝试使用state = list(disabled = TRUE)参数,但该设置对右键菜单无效果,测试代码如下:

library(jsTreeR)
library(shiny)

nodes <- list(
  list(
    text = "分支1",
    state = list(opened = TRUE, disabled = FALSE),
    type = "parent",
    children = list(
      list(text = "叶子A", type = "child", state = list(disabled = TRUE), data = list(customdata = 1)),
      list(text = "叶子B", type = "child", state = list(disabled = FALSE), data = list(customdata = 2)),
      list(text = "叶子C", type = "child", state = list(disabled = FALSE), data = list(customdata = 3)),
      list(text = "叶子D", type = "child", state = list(disabled = FALSE), data = list(customdata = 4))
    )
  ),
  list(text = "分支2", type = "parent", state = list(opened = TRUE))
)

ui <- fluidPage(
  jstreeOutput("jstree")
)

server <- function(input, output, session){
  output[["jstree"]] <- renderJstree({
    suppressMessages(jstree(
      nodes,
      search = list(
        show_only_matches = TRUE,
        case_sensitive = FALSE,
        search_leaves_only = FALSE
      ),
      dragAndDrop = TRUE,
      multiple = TRUE,
      contextMenu = TRUE,
      types = list(default = list(icon = "fa fa-caret-right"), child = list(icon = "fa-solid fa-leaf"), parent = list(icon = "fa-brands fa-pagelines")),
      theme = "proton"
    ))
  })
}  

shinyApp(ui, server)

解决方案

通过自定义右键菜单结合节点类型标记实现需求,同时添加逻辑检查分支节点是否包含不可删除的子节点。以下是封装后的完整Shiny应用代码:

library(jsTreeR)
library(shiny)

nodes <- list(
  list(
    text = "分支1",
    state = list(opened = TRUE, disabled = FALSE),
    type = "parent",
    children = list(
      list(text = "叶子A", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 1)),
      list(text = "叶子B", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 2)),
      list(text = "叶子C", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 3)),
      list(text = "叶子D", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 4))
    )
  ),
  list(text = "分支2", type = "parent", state = list(opened = TRUE))
)

ui <- fluidPage(
  jstreeOutput("mytree")
)

customMenu <- JS(
  "function customMenu(node) {",
  "  var tree = $('#mytree').jstree(true);", // 'mytree'对应Shiny中的树ID
  "  var hasUndeletableChild = false;",
  "  // 检查当前节点是否包含不可删除的子节点",
  "  if(node.children.length > 0){",
  "    node.children.forEach(function(childId){",
  "      var childNode = tree.get_node(childId);",
  "      if(childNode.type === 'undeletable'){",
  "        hasUndeletableChild = true;",
  "      }",
  "    });",
  "  }",
  "  var items = {",
  "    'delete' : {",
  "      'label'  : '删除',",
  "      'action' : function (obj) {",
  "        // 仅当节点不是不可删除类型,且不包含不可删除子节点时允许删除",
  "        if(node.type !== 'undeletable' && !hasUndeletableChild){",
  "          tree.delete_node(node);",
  "        }",
  "      },",
  "      'icon'   : 'glyphicon glyphicon-trash'",
  "     },",
  "     'rename' : {",
  "       'label'  : '重命名',",
  "       'action' : function (obj) {",
  "         // 仅允许重命名非不可删除类型的节点",
  "         if(node.type !== 'undeletable'){",
  "           tree.rename_node(node);",
  "         }",
  "       },",
  "       'icon'   : 'glyphicon glyphicon-pencil'",
  "     },",
  "     'create' : {",
  "       'label'  : '添加子节点',",
  "       'action' : function (obj) {",
  "         // 仅允许给非不可删除类型的节点添加子节点",
  "         if(node.type !== 'undeletable'){",
  "           tree.create_node(node, {}, 'last', function(new_node){",
  "             setTimeout(function(){ tree.edit(new_node); }, 0);",
  "           });",
  "         }",
  "       },",
  "       'icon'   : 'glyphicon glyphicon-plus'",
  "     }",
  "  };",
  "  // 隐藏不可删除节点的所有编辑操作",
  "  if(node.type === 'undeletable'){",
  "    delete items.rename;",
  "    delete items.create;",
  "  }",
  "  return items;",
  "}")

server <- function(input, output, session){
  output[["mytree"]] <- renderJstree({
    suppressMessages(jstree(
      nodes,
      search = list(
        show_only_matches = TRUE,
        case_sensitive = FALSE,
        search_leaves_only = FALSE
      ),
      dragAndDrop = TRUE,
      multiple = TRUE,
      contextMenu = list(items = customMenu),
      types = list(default = list(icon = "fa fa-caret-right"), undeletable = list(icon = "fa-solid fa-leaf"), parent = list(icon = "fa-brands fa-pagelines")),
      theme = "proton"
    ))
  })
}  

shinyApp(ui, server)

核心逻辑说明

  1. 节点类型标记:将需要保护的叶子节点标记为undeletable类型,分支节点使用parent类型区分。
  2. 自定义右键菜单:
    • 删除操作:检查节点类型是否为undeletable,同时检查该节点是否包含undeletable子节点,两者都不满足才允许删除。
    • 重命名/添加子节点操作:仅对非undeletable节点开放。
  3. 拖拽保留:保持dragAndDrop = TRUE,未对拖拽逻辑做限制,满足叶子节点可跨分支拖拽的需求。

内容的提问来源于stack exchange,提问作者ahnungslos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 01:30:00