Is there a way to just read an edited DT?

Viewed 34

I'm trying to make an app where users can edit some tables and run a calculation, and using DT. Is there a way to just read in what's currently in a DT table? This would simplify things a lot for me. All the solutions I've been able to find involve detecting when the table is edited, and then updating the data accordingly. This seems clunky and also might cause problems for my use case later.

Here's an example: after editing the data zTable, I'd like something that just returns what is now in zTable after clicking the calculate button aside from just watching every edit and updating z$data.

library(shiny)
library(DT)



ui <- fluidPage(

    DT::dataTableOutput("zTable"),
    actionButton("calcButton","Calculate!")
    
)


server <- function(input, output) {
    
    z<-reactiveValues(data={data.frame(x=c(0,1),
                        y=c(0,1))
    })
    
    output$zTable <- DT::renderDT(z$data,editable=T)
    
    observeEvent(input$calcButton,{
        print(z$data)
    })
    
    observeEvent(input$zTable_cell_edit, {

        info = input$zTable_cell_edit
        z$data[as.numeric(info$row),as.numeric(info$col)] <- as.numeric(info$value)
    })

}

shinyApp(ui = ui, server = server)

2 Answers

You can do as follows. There's a problem with the current version of DT: when you edit a numeric cell, the new value is stored as a string instead of a number. I've just done a pull request which fixes this issue. With the next version of DT the .map(Number) in the JavaScript callback will not be needed anymore. If you are ok to adopt my solution, tell me if you want to use it with non-numeric cells, and I'll have to improve the code in order to handle this situation. Or you can install my fork of DT in which I fixed the issue: remotes::install_github("stla/DT@numericvalue").

library(shiny)
library(DT)

callback <- c(
  '$("#show").on("click", function(){',
  '  var headers = Array.from(table.columns().header()).map(x => x.innerText);',
  '  var arrayOfColumns = Array.from(table.columns().data());',
  '  var rownames = arrayOfColumns[0]',
  '  headers.shift(); arrayOfColumns.shift();',
  '  var entries = headers.map((h, i) => [h, arrayOfColumns[i].map(Number)]);',
  '  var columns = Object.fromEntries(entries);',
  '  Shiny.setInputValue(',
  '    "tabledata", {rownames: rownames, columns: columns}, {priority: "event"}',
  '  );',
  '});'
)

ui <- fluidPage(
  br(),
  DTOutput("dtable"),
  br(),
  tags$h3("Edit a cell and click"),
  actionButton("show", "Print data")
)


server <- function(input, output) {
  
  dat <- data.frame(x=c(0,1),
                    y=c(0,1))
  
  output[["dtable"]] <- renderDT({
    datatable(
      dat, 
      editable = TRUE,
      callback = JS(callback)
    )
  }, server = FALSE)
  
  observeEvent(input[["tabledata"]], {
    columns <- lapply(input[["tabledata"]][["columns"]], unlist)
    df <- as.data.frame(columns)
    rownames(df) <- input[["tabledata"]][["rownames"]]
    print(df)
  })
  
}

shinyApp(ui = ui, server = server)

You can rely on JavaScript to get the data via the DataTable api:

library(shiny)
library(DT)
library(shinyjs)


ui <- fluidPage(
   useShinyjs(),
   DT::dataTableOutput("zTable"),
   actionButton("calcButton","Calculate!")
)


server <- function(input, output) {
   
   z <- reactiveValues(data = data.frame(x = 0:1, y = 0:1))
   
   output$zTable <- renderDT(z$data, editable = TRUE)
   
   observeEvent(input$calcButton, {
      runjs('Shiny.setInputValue("mydata", $("#zTable table").DataTable().rows().data())')
   })
   
   observeEvent(input$mydata, {
      dat <- req(input$mydata)
      ## remove chunk from .data()
      dat[c("length", "selector", "ajax", "context")] <- NULL
      print(do.call(rbind, dat))
   })
}

shinyApp(ui = ui, server = server)

However, as you need to some data wrangling to back-transfrom the data, I am not sure whether this is eventually such a good idea.

What is your general issue with the _cell_edit approach? (which I would prefer because no need to additional data wrangling other than storing it in the right spot?

Related