【问题标题】:Move rows from one DT to other DTs using action buttons in R Shiny使用 R Shiny 中的操作按钮将行从一个 DT 移动到其他 DT
【发布时间】:2020-10-28 03:11:59
【问题描述】:

更新

我正在尝试使用shinyDTsimilar to the accepted answer from Shree here 制作应用程序。我想,你,有以下补充:

  1. 从 Shree 扩展解决方案,以便左侧(源)DT 中的项目可以移动到右侧和后面的多个表并且可以扩展,以便我可以决定我想要多少表放在右边。也就是说,左侧表格中的不同项目可以放在右侧的不同表格中。
  2. 此外,在右侧每个表格旁边都有双箭头按钮,这样可以通过单击双箭头按钮来添加或删除表格中的所有项目,而不仅仅是用于移动所选变量的单箭头按钮, like here,但仍然可以决定是否显示它们。
  3. 即使是空的,右侧的表格也可见。

有人可以帮忙吗?

【问题讨论】:

  • 一般来说,如果您向我们展示您到目前为止所尝试的内容会很好。还可以更轻松地获得您想要实现的目标。对于 1) 我不确定您是否要为右侧的所有 n 个表显示相同的输出,或者您是否甚至希望能够选择 n 个表的子集并且只向它们添加行。对于 2) 和 3) 我添加了一个答案。
  • @Tonio Liebrand:对不起,你是对的,我不清楚#1。我的意思是左侧表格中的不同项目到右侧的不同表格中。我已经编辑了问题。

标签: r shiny dt action-button


【解决方案1】:

为了推广到任意数量的表,我会使用一个模块。该模块将包含单个DT 的 GUI 和逻辑。它将具有“输入 DT”(从中接收行的表)和“输出 DT”(将行发送到的表)的参数。一个或两个都可以是NULL。 GUI 将显示DT 并有一个小部件来启动各种“发送行”命令。有关模块的更多详细信息,请参阅here

至于您无法从源表中删除行:我对DT 并不太熟悉,但我相信您需要使用代理:正如this page 所说“在表格中呈现后闪亮的应用,你可以使用dataTableProxy()返回的代理对象来操作它。目前支持的方法有selectRows()selectColumns()selectCells()selectPage()addRow()。"。

【讨论】:

    【解决方案2】:

    要获得双箭头按钮,您可以使用:

    actionButton("add_all", label = NULL, icon("angle-double-right"), 
                                      lib = "font-awesome")
    

    请注意,?icon 链接到 fontawesome 页面,该页面提供双箭头图标:https://fontawesome.com/icons?d=gallery&q=double%20arrow&m=free

    要删除所有项目,您只需切换到默认状态:

    observeEvent(input$remove_all, {
      mem$selected <- select_init
      mem$pool <- pool_init
    })
    

    默认状态定义为:

    pool_init <- data.frame(data = LETTERS[1:10])
    select_init <- data.frame(data = "")
    

    要添加所有行,您基本上可以切换状态:

    mem$selected <- pool_init
    mem$pool <- select_init
    

    请注意,我使用(几乎)空的 data.frame 来确保显示数据表,即使它是空的。这不是很优雅,因为它有一个空字符串。可能有更好的方法。例如。如果添加一行并再次取消选择,则表格为空,它将显示No data available in table。这实际上看起来更好。

    完整的可重现示例:

    library(shiny)
    library(DT)
    
    ui <- fluidPage(
      br(),
      splitLayout(cellWidths = c("40%", "10%", "40%", "10%"),
                  DTOutput("pool"),
                  list(
                    br(),br(),br(),br(),br(),br(),br(),
                    actionButton("add", label = NULL, icon("arrow-right")),
                    br(),br(),
                    actionButton("remove", label = NULL, icon("arrow-left"))
                  ),
                  DTOutput("selected"),
                  list(
                    br(),br(),br(),br(),br(),br(),br(),
                    actionButton("add_all", label = NULL, icon("angle-double-right"), 
                                  lib = "font-awesome"),
                    br(),br(),
                    actionButton("remove_all", label = NULL, icon("angle-double-left"), 
                                  lib = "font-awesome")
                  )
      )
    )
    
    
    pool_init <- data.frame(data = LETTERS[1:10])
    select_init <- data.frame(data = "")
    
    server <- function(input, output, session) {
      
      mem <- reactiveValues(
        pool = pool_init, selected = select_init
      )
      
      observeEvent(input$add, {
        req(input$pool_rows_selected)
        mem$selected <- rbind(isolate(mem$selected), mem$pool[input$pool_rows_selected, , drop = F])
        mem$selected <- mem$selected[sapply(mem$selected, nchar) > 0, , drop = FALSE]
        mem$pool <- isolate(mem$pool[-input$pool_rows_selected, , drop = F])
      })
      
      observeEvent(input$remove, {
        req(input$selected_rows_selected)
        mem$pool <- rbind(isolate(mem$pool), mem$selected[input$selected_rows_selected, , drop = F])
        mem$pool <- mem$pool[sapply(mem$pool, nchar) > 0, , drop = FALSE]
        mem$selected <- isolate(mem$selected[-input$selected_rows_selected, , drop = F])
      })
      
      observeEvent(input$add_all, {
        mem$selected <- pool_init
        mem$pool <- data.frame(data = "")
      })
      
      observeEvent(input$remove_all, {
        mem$selected <- select_init
        mem$pool <- pool_init
      })
      
      
      output$pool <- renderDT({
        mem$pool
      })
      
      output$selected <- renderDT({
        mem$selected
      })
    }
    
    shinyApp(ui, server)
    

    关于多表的要求,请看我的评论。

    【讨论】:

    • 感谢您的解释和解决方案。关于多个表的要求,我已经更新了问题。我希望现在更清楚 - 将不同的项目从左侧移动到右侧的不同表格中。
    • 托尼奥?右边多张桌子有消息吗?
    • 我一直在努力,但没时间了。为这些表中的每一个添加模块并拥有动态内存需要更多的工作,但我认为你现在有一个很好的答案吧?
    【解决方案3】:

    如前所述,shiny modules 是解决此问题的一种优雅方法。你必须传入一些reactives 来接收行,你必须返回一些reactives 来发送行/告诉主表它应该删除它刚刚发送的行。

    一个完整的例子如下所示:

    library(shiny)
    library(DT)
    
    receiver_ui <- function(id, class) {
       ns <- NS(id)
       fluidRow(
          column(width = 1,
                 actionButton(ns("add"), 
                              label = NULL,
                              icon("angle-right")),
                 actionButton(ns("add_all"), 
                              label = NULL,
                              icon("angle-double-right")),
                 actionButton(ns("remove"),
                              label = NULL,
                              icon("angle-left")),
                 actionButton(ns("remove_all"),
                              label = NULL,
                              icon("angle-double-left"))),
          column(width = 11,
                 dataTableOutput(ns("sink_table"))),
          class = class
       )
    }
    
    receiver_server <- function(input, output, session, selected_rows, full_page, blueprint) {
       ## data_exch contains 2 data.frames:
       ## send: the data.frame which should be sent back to the source
       ## receive: the data which should be added to this display
       data_exch <- reactiveValues(send    = blueprint,
                                   receive = blueprint)
       
       ## trigger_delete is used to signal the source to delete the rows whihc just were sent
       trigger_delete <- reactiveValues(trigger = NULL, all = FALSE)
       
       ## render the table and remove .original_order, which is used to keep always the same order
       output$sink_table <- renderDataTable({
          dat <- data_exch$receive
          dat$.original_order <- NULL
          dat
       })
       
       ## helper function to move selected rows from this display back 
       ## to the source via data_exch
       shift_rows <- function(selector) {
          data_exch$send <- data_exch$receive[selector, , drop = FALSE]
          data_exch$receive <- data_exch$receive[-selector, , drop = FALSE]
       }
       
       ## helper function to add the relevant rows
       add_rows <- function(all) {
          rel_rows <- if(all) req(full_page()) else req(selected_rows())
          data_exch$receive <- rbind(data_exch$receive, rel_rows)
          data_exch$receive <- data_exch$receive[order(data_exch$receive$.original_order), ]
          ## trigger delete, such that the rows are deleted from the source
          old_value <- trigger_delete$trigger
          trigger_delete$trigger <- ifelse(is.null(old_value), 0, old_value) + 1
          trigger_delete$all <- all
       }
       
       observeEvent(input$add, {
          add_rows(FALSE)
       })
       
       observeEvent(input$add_all, {
          add_rows(TRUE)
       })
       
       observeEvent(input$remove, {
          shift_rows(req(input$sink_table_rows_selected))
       })
       
       observeEvent(input$remove_all, {
          shift_rows(req(input$sink_table_rows_current))
       })
       
       ## return the send reactive to signal the main app which rows to add back
       ## and the delete trigger to remove rows
       list(send   = reactive(data_exch$send),
            delete = trigger_delete)
    }
    
    
    ui <- fluidPage(
       tags$head(tags$style(HTML(".odd {background: #DDEBF7;}",
                                 ".even {background: #BDD7EE;}",
                                 ".btn-default {min-width:38.25px;}",
                                 ".row {padding-top: 15px;}"))),
       fluidRow(
          actionButton("add", "Add Table") 
       ),
       fluidRow(
          column(width = 6, dataTableOutput("source_table")),
          column(width = 6, div(id = "container")),
       )
    )
    
    server <- function(input, output, session) {
       orig_data <- mtcars
       orig_data$.original_order <- seq(1, NROW(orig_data), 1)
       my_data <- reactiveVal(orig_data)
       
       handlers <- reactiveVal(list())
       
       selected_rows <- reactive({
          my_data()[req(input$source_table_rows_selected), , drop = FALSE]
       })
       
       all_rows <- reactive({
          my_data()[req(input$source_table_rows_current), , drop = FALSE]
       })
       
       observeEvent(input$add, {
          old_handles <- handlers()
          n <- length(old_handles) + 1
          uid <- paste0("row", n)
          insertUI("#container", ui = receiver_ui(uid, ifelse(n %% 2, "odd", "even")))
          new_handle <- callModule(
             receiver_server,
             uid,
             selected_rows = selected_rows,
             full_page = all_rows,
             ## select 0 rows data.frame to get the structure
             blueprint = orig_data[0, ])
          
          observeEvent(new_handle$delete$trigger, {
             if (new_handle$delete$all) {
                selection <- req(input$source_table_rows_current)
             } else {
                selection <- req(input$source_table_rows_selected)
             }
             my_data(my_data()[-selection, , drop = FALSE])
          })
          
          observe({
             req(NROW(new_handle$send()) > 0)
             dat <- rbind(isolate(my_data()), new_handle$send())
             my_data(dat[order(dat$.original_order), ])
          })
          handlers(c(old_handles, setNames(list(new_handle), uid)))
       })
       
       output$source_table <- renderDataTable({
          dat <- my_data()
          dat$.original_order <- NULL
          dat
       })
    }
    
    
    shinyApp(ui, server)
    

    说明

    一个模块包含 UI 和服务器,并且由于命名空间技术,名称只需要在一个模块中是唯一的(并且每个模块以后也必须有一个唯一的名称)。该模块可以通过reactives 与主应用程序通信,它们要么传递给callModule(请注意,我仍在使用旧函数,因为我还没有更新我的闪亮库),或者从服务器函数返回.

    在主应用程序中,我们有一个按钮,它动态插入 UI 并调用callModule 来激活逻辑。 observers 也在同一个调用中生成,以使服务器逻辑正常工作。

    【讨论】:

    • 非常感谢!解决方案相当令人印象深刻。只有一个额外的问题/请求。这可以概括,以便右侧的表数作为模块的函数参数传递,因此模块可以多次重用(例如在应用程序的不同选项卡中)而不是“添加表”按钮.
    • 嗯,你可以从任何你想要的地方调用这个模块。您不需要动态添加它。在任何 UI 中,您也可以直接调用 UI 函数。例如fluidPage(receiver_ui("my_fixed_ui", "odd")) 也可以。因此,如果您想拥有多个表,只需使用某种循环,例如fluidPage(lapply(1:4, function(i) receiver_ui(paste0("row",i), "odd"))
    • 谢谢。我尝试了您评论中的建议,但页面顶部出现带有按钮的空蓝色字段。抱歉,我是 Shiny 的新手,(尤其是)这些模块对我来说很神奇。你能修改解决方案吗?
    • 当然还需要定义服务端函数。您是否阅读了链接中的文章?
    猜你喜欢
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 2020-12-29
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    • 2016-01-17
    • 1970-01-01
    相关资源
    最近更新 更多