rshinydt

Editing multiple cells in a datatable in shiny


I asked this question earlier - Write editable shiny Datatable to .csv file Trying to edit cells in a shiny datatable and replace the original data with the edited data.

I was able to figure out how to edit a single cell in the datatable, however if multiple cells are edited only the last edit is saved.

Code:

library(shiny)
library(shinydashboard)
library(tidyverse)
library(DT)


users <- reactiveFileReader(
  intervalMillis = 1000,  
  NULL,
  filePath = 'appData/userTest.csv',
  readFunc = read.csv,
  stringsAsFactors = FALSE
)

header <- dashboardHeader(title = "demo")
sidebar <- dashboardSidebar(uiOutput('sidebar'))
body <- dashboardBody(uiOutput("body"))

f1 <- fluidRow(
  box(
    dataTableOutput('userTable'),
    width = 6
  )
)

ui <- dashboardPage(title = 'admin function test', header, sidebar, body, skin='blue')

server <- function(input, output, session){

  output$body <- renderUI({
    tabItems(
      tabItem(
        tabName = 'admin', class = 'active', h2(f1)
      )
    )
  })

  output$sidebar <- renderUI({
    sidebarMenu(id = 'sidebarmenu',
                menuItem("admin", tabName = "admin", icon = icon("adjust")),
                actionButton("do", 'save', icon = icon('redo'))
    )
  })

  observeEvent(
    input$do,{
      write.csv(edited(),'appData/userTest.csv', row.names = FALSE)
    })

  output$userTable <- renderDataTable({
    DT::datatable(users(),
                  editable = TRUE,
                  rownames = FALSE)
  })

  edited <- reactive({editData(users(), input$userTable_cell_edit, proxy = NULL, rownames = FALSE, resetPaging = FALSE)})
}

shinyApp(ui = ui, server = server)

Data:

   userName      start        end
1      John 06/08/2019 01/10/2019
2      Mary 01/01/2019 01/10/2019
3      Mike 23/10/2019 01/10/2019
4     Steve 25/07/2019 07/02/2015
5      Kate 01/01/2019 29/04/2019

I imagine this is because the editData() function records only a single edit at a time. How can multiple cells be edited and saved in one go?


Solution

  • See below, this will allow multiple changes and each one is tracked.

    library(shiny)
    library(shinydashboard)
    library(tidyverse)
    library(DT)
    
    header <- dashboardHeader(title = "demo")
    sidebar <- dashboardSidebar(
      sidebarMenu(id = 'sidebarmenu',
                  menuItem("admin", tabName = "admin", icon = icon("adjust")),
                  downloadButton("downloadResults","Download Results")
      )
    )
    
    body <- dashboardBody(
      tabItems(
        tabItem(
          tabName = 'admin', class = 'active', 
            fluidRow(
              box(
                dataTableOutput('userTable'), width = 6
              )
            )
        )
      )
    )
    
    
    ui <- dashboardPage(title = 'admin function test', header, sidebar, body, skin='blue')
    
    server <- function(input, output, session){
    
      dat <- data.frame(userName = c("John","Mary","Mike"), start = c("06/08/2019","01/01/2019","23/10/2019"), stringsAsFactors = FALSE)
    
      output$userTable <- renderDataTable({
        DT::datatable(isolate(dat),
                      editable = TRUE,
                      rownames = FALSE)
      })
    
      ###Tracking Changes###
      rvs <- reactiveValues(
        data = NA #dynamic data object
      )
    
      observe({
        rvs$data <- dat
      })
    
      proxy = dataTableProxy('userTable')
      observe({
        DT::replaceData(proxy, rvs$data, rownames = FALSE, resetPaging = FALSE)
      })
    
      observeEvent(input$userTable_cell_edit, {
        rvs$data <<- editData(rvs$data, input$userTable_cell_edit, rownames = FALSE)
      })
    
      # observeEvent(
      #   input$do,{
      #     write.csv(rvs$data,'userTest.csv', row.names = FALSE)
      #   })
    
      output$downloadResults <- downloadHandler(
        filename = function(){paste("userTest.csv.csv", sep = "")},
        content = function(file){write.csv(rvs$data, file, row.names = FALSE)}
      )
    
    }
    
    shinyApp(ui = ui, server = server)
    

    If you want multiple cells to be editable at the same time, add this line to your datatable:

    editable = list(target = "all") #can be all, row, or column
    

    Few tips when posting in the future:

    1. Create a reproducible example, having it pull data/save data from a file on your computer can't be reproduced
    2. Keep the code simple as possible to focus on your issue. Throwing renderUI's for the body and sidebar just clutters your code.