Shiny应用中DT表格重渲染后内嵌链接绑定失效问题
问题根因
DT表格因selectInput筛选触发全量重渲染时,旧表格节点被移除前没有完成Shiny输入解绑,同ID的链接元素再次插入DOM后,Shiny会检测到重复ID的输入绑定,导致新元素的点击事件无法被正常捕获。你配置的preDrawCallback和drawCallback仅在DT内部的分页、搜索、排序重绘时生效,表格全量重渲染时不会触发预期的解绑流程。
修复方案
提供两种稳定修复方式,任选其一即可:
方案1:关闭DT服务端渲染,强制所有数据在客户端处理
在renderDataTable中新增server = FALSE参数,所有表格渲染逻辑都在客户端执行,避免服务端触发的全量重渲染导致的绑定冲突,修改后的output$tab代码如下:
output$tab <- renderDataTable({ datatable( DF %>% filter(a %in% input$sel), escape = FALSE, selection = 'none', options = list( preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'), drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ') ) ) }, server = FALSE)
方案2:使用JS事件委托,规避Shiny输入绑定
不需要依赖Shiny.bindAll,直接给链接添加onclick事件触发标签页切换,完全避免绑定冲突问题,修改步骤如下:
- 调整DF中的链接代码,去掉
action-button类,添加onclick事件:
DF <- data.frame(a = c(1,2), b = c(HTML('<a href="#" onclick="Shiny.setInputValue(\'goToTab\', \'B\', {priority: \'event\'})">goToTabPanelB1</a>'), HTML('<a href="#" onclick="Shiny.setInputValue(\'goToTab\', \'B\', {priority: \'event\'})">goToTabPanelB2</a>')))
- 去掉datatable中的
preDrawCallback和drawCallback配置 - 调整对应的观察事件:
observeEvent(input$goToTab, { updateTabsetPanel(session, "panels", input$goToTab) })
该方案稳定性更高,无论表格怎么重渲染都不会出现点击失效问题。
完整可运行修复代码(方案1版本)
library(shiny) library(DT) library(dplyr) ui <- fluidPage(tabsetPanel( id = "panels", tabPanel("A", selectInput("sel", "Select", choices = c(1,2)), DTOutput("tab")), tabPanel("B", h3("Some information"), tags$li("Item 1"), tags$li("Item 2"), actionLink("goToTabPanelA", "goToTabPanelA") ) )) server <- function(input, output, session) { DF <- data.frame(a = c(1,2), b = c(HTML('<a id="goToTabPanelB1" class="action-button" href="#">goToTabPanelB1</a>'), HTML('<a id="goToTabPanelB2" class="action-button" href="#">goToTabPanelB2</a>'))) output$tab <- renderDataTable({ datatable( DF %>% filter(a %in% input$sel), escape = FALSE, selection = 'none', options = list( preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'), drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ') ) ) }, server = FALSE) observeEvent(c(input$goToTabPanelB1, input$goToTabPanelB2), { updateTabsetPanel(session, "panels", "B") }) observeEvent(input$goToTabPanelA, { updateTabsetPanel(session, "panels", "A") }) } shinyApp(ui, server)
内容的提问来源于stack exchange,提问作者manu p
相关产品推荐
相关产品推荐

