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

jsTree层级树拖拽操作时如何自动重排节点连续序号

jsTreeR结合Shiny实现拖拽复制与自动连续序号需求

我正在基于jsTreeR、Shiny搭建层级树组件,需实现两类核心功能:

  • 支持用户将源节点下标记为moveable类型的元素,拖拽复制到类型为target的目标节点下,可自由选择元素放置位置
  • 目标节点下的所有元素,在新增、删除、位置调整操作完成后,需按当前排列顺序生成连续序号,序号需随元素位置变化同步更新
    效果参考文末附的示意图,以下提供可复现代码,求实现思路。

注:为精简代码篇幅,已删除部分实用扩展功能,待完成jsTreeR包的功能探索后,会公开完整代码供大家参考。本问题是此前Stack Overflow提问《如何将jsTree中的指定节点元素提取为R数据框》的后续问题。

初始版本可复现代码

library(jsTreeR)
library(shiny)

nodes <- list(
  list(
    text = "Menu",
    state = list(opened = TRUE),
    children = list(
      list(
        text = "A",
        type = "moveable",
        state = list(disabled = TRUE)
      ),
      list(
        text = "B",
        type = "moveable",
        state = list(disabled = TRUE)
      )
    )
  ),
  list(
    text = ">>> Drag here <<<",
    type = "target",
    state = list(opened = TRUE)
    )
)

checkCallback <- JS(
  "function(operation, node, parent, position, more) { ",
  "  if(operation === 'copy_node') {",
  "    if(parent.id === '#' || node.parent !== 'j1_1' || parent.type !== 'target') {",
  "      return false;",
  "    }",
  "  }",
  "  if(operation === 'delete_node') {",
  "    Shiny.setInputValue('deletion', position + 1);",
  "  }",
  "  return true;",
  "}"
)

dnd <- list(
  always_copy = TRUE,
  is_draggable = JS(
    "function(node) {",
    "  return node[0].type === 'moveable';",
    "}"
  )
)

mytree <- jstree(
  nodes, dragAndDrop = TRUE, dnd = dnd, checkCallback = checkCallback,
  types = list(moveable = list(), target = list())
)

script <- '
$(document).ready(function(){
  $("#mytree").on("copy_node.jstree", function(e, data){
    var instance = data.new_instance;
    var node = data.node;
    var id = node.id;
    var index = $("#"+id).index() + 1;
    var text = index + ". " + node.text;
    Shiny.setInputValue("choice", text);
    instance.rename_node(node, text);
  });
});
'

library(shiny)
ui <- fluidPage(
  tags$head(tags$script(HTML(script))),
  fluidRow(
    column(width = 4,jstreeOutput("mytree")),
    column(width = 8,verbatimTextOutput("choices"))
  )
)

server <- function(input, output, session){
  output[["mytree"]] <- renderJstree(mytree)
  Choices <- reactiveVal(data.frame(choice = character(0)))
  observeEvent(input[["choice"]],{Choices(rbind(Choices(),data.frame(choice = input[["choice"]])))})
  observeEvent(input[["deletion"]], {Choices(Choices()[-input[["deletion"]], , drop = FALSE])})
  output[["choices"]] <- renderPrint({Choices()})
}

shinyApp(ui, server)

初始版本效果示意图

  • 效果示意图1
  • 效果示意图2

调整版本:限制拖拽仅可放置到目标节点末尾

针对首条评论做的修改:限制用户仅可拖拽复制到“>>> Drag here <<<”节点的末尾位置
修改说明:两处新增内容已在代码中以#注释标注,修改后仍存在序号异常问题。

nodes <- list(nodes <- list(
  list(
    text = "Menu",
    state = list(opened = TRUE),
    children = list(
      list(
        text = "A",
        type = "moveable",
        state = list(disabled = TRUE)
      ),
      list(
        text = "B",
        type = "moveable",
        state = list(disabled = TRUE)
      ),
      list( # 新增第三个可拖拽元素
        text = "C",
        type = "moveable",
        state = list(disabled = TRUE)
      )
    )
  ),
  list(
    text = ">>> Drag here <<<",
    type = "target",
    state = list(opened = TRUE)
  )
)

checkCallback <- JS(
  "function(operation, node, parent, position, more) { ",
  "  if(operation === 'copy_node') {",
  "    if(parent.id === '#' || node.parent !== 'j1_1' || parent.type !== 'target') {",
  "      return false;", 
  "    }",               
  "  }",
  "  if(operation === 'delete_node') {",
  "    Shiny.setInputValue('deletion', position + 1);",
  "  }",
  "  return true;",      
  "}"
)

dnd <- list(
  always_copy = TRUE,
  inside_pos = "last", # 新增配置:拖拽到节点内时默认放末尾
  is_draggable = JS(
    "function(node) {",
    "  return node[0].type === 'moveable';",
    "}"
  )
)

mytree <- jstree(
  nodes, dragAndDrop = TRUE, dnd = dnd, checkCallback = checkCallback,
  types = list(moveable = list(), target = list())
)

script <- '
$(document).ready(function(){
  $("#mytree").on("copy_node.jstree", function(e, data){
    var instance = data.new_instance;
    var node = data.node;
    var id = node.id;
    var index = $("#"+id).index() + 1;
    var text = index + ". " + node.text;
    Shiny.setInputValue("choice", text);
    instance.rename_node(node, text);
  });
});
'

library(shiny)
ui <- fluidPage(
  tags$head(tags$script(HTML(script))),
  fluidRow(
    column(width = 4,jstreeOutput("mytree")),
    column(width = 8,verbatimTextOutput("choices"))
  )
)

server <- function(input, output, session){
  output[["mytree"]] <- renderJstree(mytree)
  Choices <- reactiveVal(data.frame(choice = character(0)))
  observeEvent(input[["choice"]],{Choices(rbind(Choices(),data.frame(choice = input[["choice"]])))})
  observeEvent(input[["deletion"]], {Choices(Choices()[-input[["deletion"]], , drop = FALSE])})
  output[["choices"]] <- renderPrint({Choices()})
}

shinyApp(ui, server)

序号异常问题示意图

  • 问题示意图1
  • 问题示意图2

内容的提问来源于stack exchange,提问作者Curious Jorge - user9788072

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 13:18:22