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

在R Shiny动态renderUI环境中使用Sortable List的问题及解决

动态Shiny UI中Sortable列表的问题修复方案

我帮你梳理下这个问题的核心原因,以及对应的解决办法——你提到的shinyjqui开发版中的orderInput()确实是最适配你场景的方案,我再给你补充细节,同时也提供手动修复原生Sortable绑定的思路。

问题根源

你遇到的Uncaught TypeError: Cannot read property 'toArray' of null错误,本质是动态生成的UI组件没有被Shiny的Input Binding正确初始化:

  • 你的自定义sortableListBinding只会在页面首次加载时执行initialize方法,创建Sortable实例;
  • 当用renderUI替换DOM,或用insertUI新增元素时,新的.sortableList元素不会自动触发初始化逻辑,导致Sortable实例不存在,调用toArray()时就会报错;
  • 同时Shiny也无法识别这些未初始化的组件,自然捕获不到输入值。

推荐方案:使用shinyjqui开发版的orderInput()

这个组件已经封装了动态UI下的Sortable绑定逻辑,不需要你手动写JS,开箱即用:

步骤1:安装开发版包

remotes::install_github("yang-tang/shinyjqui")

步骤2:替换自定义组件,重构代码

用renderUI的版本:

library(shiny)
library(shinydashboard)
library(stringi)
library(shinyjqui)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    actionButton('actbtn', 'Add Sortable List'),
    uiOutput("moreControls"),
    verbatimTextOutput('sorted_output')
  ))

server <- function(input, output, session) {
  # 实时输出所有排序后的列表内容
  output$sorted_output <- renderPrint({
    lapply(0:input$actbtn, function(numb){
      input[[paste0("sortable_list", numb)]]
    })
  })
  
  output$moreControls <- renderUI({
    current_count <- input$actbtn
    tagList(lapply(0:current_count, function(numb){
      fluidRow(
        column(6, box(
          orderInput(
            inputId = paste0("sortable_list", numb),
            label = NULL,
            items = replicate(6, paste(unlist(strsplit(stri_rand_lipsum(1, start_lipsum = F), " "))[1:10], collapse = " ")),
            connect = NULL # 不需要跨列表拖拽的话设为NULL
          ),
          width = 12, height = '300px'
        ))
      )
    }))
  })
}

shinyApp(ui=ui, server=server)

用insertUI的版本:

# 在server函数里替换成这段observeEvent
observeEvent(input$actbtn,{
  insertUI(
    selector = "#actbtn",
    where = "afterEnd",
    ui = tags$div(id = paste0('divID_', input$actbtn),
                  box(
                    column(
                      6,
                      orderInput(
                        inputId = paste0("sortable_list", input$actbtn),
                        label = NULL,
                        items = replicate(6, paste(unlist(strsplit(stri_rand_lipsum(1, start_lipsum = F), " "))[1:10], collapse = " "))
                      )
                    )
                  )
    )
  )
})

手动修复原生Sortable绑定的方案(不依赖第三方包)

如果你不想用shinyjqui,可以修改你的JS绑定逻辑,确保动态新增的元素能被初始化:

步骤1:修改sortableList.js

var sortableListBinding = new Shiny.InputBinding();
$.extend(sortableListBinding, {
  find: function(scope) {
    return $(scope).find(".sortableList");
  },
  getValue: function(el) {
    // 先检查Sortable实例是否存在
    const sortableInstance = Sortable.get(el);
    return sortableInstance ? sortableInstance.toArray() : [];
  },
  subscribe: function(el, callback) {
    $(el).on("change.sortableListBinding", function(e) {
      callback();
    });
  },
  unsubscribe: function(el) {
    $(el).off(".sortableListBinding");
  },
  initialize: function(el) {
    // 避免重复初始化
    if (!Sortable.get(el)) {
      Sortable.create(el,{
        onUpdate: function (evt) {
          $(el).trigger("change");
        }
      });
    }
  }
});

Shiny.inputBindings.register(sortableListBinding);

// 新增手动初始化函数,供Shiny动态添加后调用
window.initSortableLists = function() {
  $(".sortableList").each(function() {
    sortableListBinding.initialize(this);
  });
};

步骤2:在Shiny中触发初始化

在UI里添加监听自定义消息的脚本:

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    tags$script(HTML("
      Shiny.addCustomMessageHandler('initSortable', function() {
        window.initSortableLists();
      });
    ")),
    # 原来的UI元素...
  ))

在动态生成UI后,调用初始化函数:

output$moreControls <- renderUI({
  current_count <- input$actbtn
  ui <- tagList(lapply(0:current_count, function(numb){
    # 原来的sortableList组件代码...
  }))
  # 通知前端初始化新的Sortable列表
  session$sendCustomMessage("initSortable", NULL)
  ui
})

这种方法需要自己维护绑定逻辑,适合需要高度定制的场景,但不如shinyjqui的方案简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:39:49