R Shiny中如何用jsTreeR为拖拽树节点添加次级顺序字母编号
jsTreeR拖拽节点字母编号实现
需求说明
使用jsTreeR开发R Shiny应用时,需要为拖拽到目标容器的节点添加字母形式的次级编号,编号对应该类元素被拖入目标节点的累计次数,编号规则为第1次拖入加后缀a、第2次加b,超过26次自动生成aa/ab类双字母后缀,删除节点后剩余同类型节点编号自动重排保证连续。

完整可运行代码
library(jsTreeR) library(shiny) nodes <- list( list( text = "Menu", state = list(opened = TRUE), children = list( list( text = "Mickey", type = "moveable", state = list(disabled = TRUE) ), list( text = "Mouse", 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()) ) # 核心自定义JS事件逻辑 script <- ' $(document).ready(function(){ // 数字转字母序号工具:1→a, 2→b...26→z,27→aa function numToLetter(num) { let s = "", t; while(num > 0) { t = (num - 1) % 26; s = String.fromCharCode(97 + t) + s; num = (num - t - 1)/26; } return s; } // 监听节点复制事件,添加字母后缀 $("#mytree").on("copy_node.jstree", function(e, data){ const instance = data.new_instance; const node = data.node; const parentNode = instance.get_node(data.parent); // 统计目标容器内同原始名称的节点数量 const sameNameCount = parentNode.children.filter(childId => { return instance.get_node(childId).original.text === node.original.text; }).length; const newText = node.original.text + numToLetter(sameNameCount); instance.rename_node(node, newText); Shiny.setInputValue("choice", newText); }); // 监听节点删除事件,自动重排剩余节点编号避免断层 $("#mytree").on("delete_node.jstree", function(e, data){ const instance = data.instance; const parentNode = instance.get_node(data.parent); if(!parentNode || parentNode.type !== "target") return; // 按原始节点名称分组重编号 const nameGroups = {}; parentNode.children.forEach(childId => { const child = instance.get_node(childId); const originName = child.original.text; if(!nameGroups[originName]) nameGroups[originName] = []; nameGroups[originName].push(child); }); Object.keys(nameGroups).forEach(name => { nameGroups[name].forEach((child, idx) => { instance.rename_node(child, name + numToLetter(idx+1)); }); }); }); }); ' 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)
实现要点
- 数字转字母逻辑兼容26个以上节点的编号场景,自动生成多字母后缀
- 编号按节点原始名称独立计数,不同名称节点的编号互不干扰
- 新增删除事件监听,节点删除后同组剩余节点自动重新编号,不会出现序号跳变
- 编号直接通过jsTree原生
rename_node方法修改,不影响树的拖拽、删除等原生交互
内容的提问来源于stack exchange,提问作者Curious Jorge - user9788072
相关产品推荐
相关产品推荐

