使用 DT 和下拉选择的闪亮值未更新

问题描述 投票:0回答:1

我正在尝试使用 Shiny App 动态编辑 data.frame。该表描述了一个项目的实验结构,因此将包括可变的实验因素,每个因素都有不同的水平。对于多级因素,我想允许从下拉菜单中选择值。 出于实际原因,我希望用户也能够在 Excel/任何其他表格编辑器中编辑表格,因此也将其保存为 csv。 所以应用程序应该:

  • 重新加载本地csv表,
  • 在适当的单元格中使用允许值(因子水平)为因子创建下拉选择,
  • 还允许手动编辑其他列中的值,
  • 单击“保存”按钮时,应用程序应保存更新的 csv 表,然后关闭 然而,虽然手动编辑按预期保存,如下例所示,下拉选择不起作用。
require(shiny)
require(DT)

# Prepare mock data
wd <- getwd()
Factors <- c("Experiment", "Condition", "Replicate", "Genotype")
ExpMapNm <- "Experiment map"
ExpMapPath <- paste0(wd, "/", ExpMapNm, ".csv")
ExpMap <- data.frame(Experiment = "Exp1",
                     Sample = paste0("Sample", 1:20),
                     Condition = as.character(sapply(c("Treated", "Mock"), function(x) { rep(x, 10) })),
                     Genotype = as.character(sapply(c("KO", "WT"), function(x) { rep(x, 5) })),
                     Replicate = 1:5)
FactorsList <- setNames(lapply(Factors, function(x) { unique(ExpMap[[x]]) }), Factors)
#
# Users should be able to edit this table in two ways:
# - in Excel manually, hence why a local version is saved below and in server when closing the app
# - within the Shiny app
# In either case, the edited table will then be reloaded into r as a data.frame which will guide further data processing
if (!file.exists(ExpMapPath)) { write.csv(ExpMap, ExpMapPath, row.names = FALSE) }

ui <- shiny::fluidPage(shiny::titlePanel(ExpMapNm), # This is the name of the table
                       shiny::mainPanel(shiny::br(),
                                        shiny::actionButton("saveBtn", "Save"),
                                        DT::dataTableOutput("Data"),
                                        verbatimTextOutput(Factors)
                       ))
server <- function(input, output) {
  Data <- read.csv(ExpMapPath)
  for (Fact in Factors) {
    if (length(FactorsList[[Fact]]) > 1) { # We only want to have a drop-down selection if a factor has more than one level.
      # Thus, in this example we do not create a drop-down selection for factor Experiment.
      lvls <- FactorsList[[Fact]] # Allowed values
      for (i in 1:nrow(Data)) {
        val <- Data[[Fact]][i]
        dflt <- c(lvls[1], val)[(val %in% lvls)+1] # If the current value is not a valid level, revert to default (1st) level
        # Otherwise keep current value, but still apply drop-down selection so choices can be changed
        Data[[Fact]][i] <- as.character(selectInput(paste0(Fact, "_", i), NULL, lvls, dflt, width = "100px"))
      }
    } else { Data[[Fact]] <- FactorsList[[Fact]] }
  }
  output$Data <- DT::renderDataTable(
    Data, escape = FALSE, selection = "none", server = FALSE,
    editable = TRUE, # Non-drop-down fields can still be edited: this works
    options = list(paging = TRUE, searching = TRUE, fixedColumns = TRUE, autoWidth = TRUE,
                   ordering = TRUE, dom = "Bfrtip"),
    callback = JS("table.rows().every(function(i, tab, row) {
        var $this = $(this.node());
        $this.attr('id', this.data()[0]);
        $this.addClass('shiny-input-container');
      });
      Shiny.unbindAll(table.table().node());
      Shiny.bindAll(table.table().node());"),
    class = "display")
  shiny::observeEvent(input$Data_cell_edit, {
    Data[input$Data_cell_edit$row, input$Data_cell_edit$col] <<- input$Data_cell_edit$value
  })
  shiny::observeEvent(input$saveBtn, {
    # Hacky HTML cleanup
    # I am not interested in getting cells with html formatting in my table, I just want the value!
    # Moreover, this would clash with the code above when initiliazing the table and comparing values.
    #print(Data$Condition[[1]])
    for (Fact in Factors) {
      if (length(FactorsList[[Fact]]) > 1) {
        for (i in 1:nrow(Data)) {
          val <- Data[[Fact]][i]
          val <- gsub("^option value=\"[^\"]+\" selected>", "",
                      grep("^option value=\"[^\"]+\" selected>", unlist(strsplit(a, " *<|>[\n ]*<|> *$")), value = TRUE))
          Data[[Fact]][i] <- val
        }
      }
    }
    # Check if I have managed to change from the default value:
    print(Data$Condition[[1]])
    # When testing, I change from "Treated" to "Mock" in the table, hit save, but it prints "Treated", and sure enough the table saved contains "Treated".
    #
    write.csv(Data, ExpMapPath, row.names = FALSE)
    tstExpMap <<- Data # Another way to check table values
    stopApp()
  })
}
print(shiny::shinyApp(ui, server, options = list(launch.browser = TRUE)))

> sessionInfo()
R version 4.2.3 (2023-03-15 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows Server x64 (build 20348)

Matrix products: default

locale:
[1] LC_COLLATE=English_United Kingdom.utf8  LC_CTYPE=English_United Kingdom.utf8    LC_MONETARY=English_United Kingdom.utf8
[4] LC_NUMERIC=C                            LC_TIME=English_United Kingdom.utf8    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] DT_0.26     shiny_1.7.4

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.9        rstudioapi_0.14   magrittr_2.0.3    xtable_1.8-4      R6_2.5.1          rlang_1.0.6       fastmap_1.1.0     tools_4.2.3      
 [9] aRmel_4.0.0.13    cli_3.5.0         jquerylib_0.1.4   htmltools_0.5.4   crosstalk_1.2.0   ellipsis_0.3.2    yaml_2.3.6        digest_0.6.31    
[17] lifecycle_1.0.3   crayon_1.5.2      later_1.3.0       sass_0.4.4        htmlwidgets_1.6.0 promises_1.2.0.1  memoise_2.0.1     cachem_1.0.6     
[25] mime_0.12         compiler_4.2.3    bslib_0.4.2       jsonlite_1.8.4    httpuv_1.6.7

我在 StackOverflow 上看过很多类似的帖子,例如 https://github.com/ejbeaty/CellEdit/tree/master/jsDT: Dynamically change column values based on selectinput from another column in R shiny app 使用下拉选择(对于 DT v0.19)编辑闪亮的数据表,我实际上基于这个应用程序,但我一定遗漏了一些东西,因为它对我不起作用。不幸的是,我对 JavaScript 一无所知,所以我很难弄清楚缺少什么。

r shiny datatables dropdown dt
1个回答
0
投票

以下是我的答案here的修改(通用)版本,它读取csv文件并允许将其保存回磁盘。

library(DT)
library(shiny)

# Prepare mock data
wd <- getwd()
Factors <- c("Experiment", "Condition", "Replicate", "Genotype")
ExpMapNm <- "dummy_data"
ExpMapPath <- paste0(wd, "/", ExpMapNm, ".csv")
ExpMap <- data.frame(Experiment = "Exp1",
                     Sample = paste0("Sample", 1:20),
                     Condition = as.character(sapply(c("Treated", "Mock"), function(x) { rep(x, 10) })),
                     Genotype = as.character(sapply(c("KO", "WT"), function(x) { rep(x, 5) })),
                     Replicate = 1:5)
FactorsList <- setNames(lapply(Factors, function(x) { unique(ExpMap[[x]]) }), Factors)

if (!file.exists(ExpMapPath)) { write.csv(ExpMap, ExpMapPath, row.names = FALSE) }

ui <- fluidPage(
  shiny::actionButton("saveBtn", "Save"),
  DT::dataTableOutput(outputId = 'my_table'),
)

server <- function(input, output, session) {
  
  resultDF <- displayHTMLDF <- initHTMLDF <- initData <- read.csv(ExpMapPath)
  
  dropdownCols <- names(initData)[3:5]
  dropdownIDs <- setNames(lapply(dropdownCols, function(x){paste0(x, seq_len(nrow(initData)))}), dropdownCols)
  
  for(dropdownCol in dropdownCols){
    colDropdownIDs <- dropdownIDs[[dropdownCol]]
    initHTMLDF[[dropdownCol]] <- sapply(seq_along(colDropdownIDs), function(i){as.character(selectInput(inputId = colDropdownIDs[i], label = "", choices = unique(initData[[dropdownCol]]), selected = initData[[dropdownCol]][i]))})
  }
  
  reactiveHTMLDF <- reactive({
    for(dropdownCol in dropdownCols){
      colDropdownIDs <- dropdownIDs[[dropdownCol]]
      displayHTMLDF[[dropdownCol]] <- sapply(seq_along(colDropdownIDs), function(i){as.character(selectInput(inputId = colDropdownIDs[i], label = "", choices = unique(initData[[dropdownCol]]), selected = input[[colDropdownIDs[i]]]))})
    }
    return(displayHTMLDF)
  })
  
  reactiveResultDF <- reactive({
    for(dropdownCol in dropdownCols){
      colDropdownIDs <- dropdownIDs[[dropdownCol]]
      resultDF[[dropdownCol]] <- sapply(seq_along(colDropdownIDs), function(i){input[[colDropdownIDs[i]]]})
    }
    return(resultDF)
  })
  
  output$my_table = DT::renderDataTable({
    DT::datatable(
      initHTMLDF, escape = FALSE, selection = 'none', rownames = FALSE,
      options = list(paging = FALSE, ordering = FALSE, scrollx = TRUE, dom = "t",
                     preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
                     drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
      )
    )
  }, server = TRUE)
  
  my_table_proxy <- dataTableProxy(outputId = "my_table", session = session)
  
  observeEvent({sapply(unlist(dropdownIDs), function(x){input[[x]]})}, {
    replaceData(proxy = my_table_proxy, data = reactiveHTMLDF(), rownames = FALSE) # must repeat rownames = FALSE see ?replaceData and ?dataTableAjax
  }, ignoreInit = TRUE)
  
  shiny::observeEvent(input$saveBtn, {
    write.csv(reactiveResultDF(), ExpMapPath, row.names = FALSE)
    stopApp()
  })
}

shinyApp(ui = ui, server = server)

您可能需要根据自己的需要调整一些东西,但是不需要它们来说明原理。

© www.soinside.com 2019 - 2024. All rights reserved.