Kevin Tracey
Kevin Tracey

Reputation: 314

How to use the Undo button in R shiny to undo earlier operations and recover them

I am working on a R shiny app that reads CSV and produces a dataTable. I am looking for a way to undo prior actions one by one whenever I clik the Undo button (like CTRL+ Z in Windows), however, the code below restores all previous actions once I press the Undo button.

Could someone please assist me in resolving this problem?

csv data

ID  Type   Range
21  A1 B1   100
22  C1 D1   200
23  E1 F1   300

app.R


library(shiny)
library(reshape2)
library(DT)
library(tibble)


###function for deleting the rows
splitColumn <- function(data, column_name) {
  newColNames <- c("Unmerged_type1", "Unmerged_type2")
  newCols <- colsplit(data[[column_name]], " ", newColNames)
  after_merge <- cbind(data, newCols)
  after_merge[[column_name]] <- NULL
  after_merge
}
###_______________________________________________
### function for inserting a new column

fillvalues <- function(data, values, columName){
  df_fill <- data
  vec <- strsplit(values, ",")[[1]]
  df_fill <- tibble::add_column(df_fill, newcolumn = vec, .after = columName)
  df_fill
}

##function for removing the colum

removecolumn <- function(df, nameofthecolumn){
  df[ , -which(names(df) %in% nameofthecolumn)]
}

### use a_splitme.csv for testing this program

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      fileInput("file1", "Choose CSV File", accept = ".csv"),
      checkboxInput("header", "Header", TRUE),
      actionButton("Splitcolumn", "SplitColumn"),
      uiOutput("selectUI"),
      actionButton("deleteRows", "Delete Rows"),
      textInput("textbox", label="Input the value to replace:"),
      actionButton("replacevalues", label = 'Replace values'),
      actionButton("removecolumn", "Remove Column"),
      actionButton("Undo", 'Undo')
    ),
    mainPanel(
      DTOutput("table1")
    )
  )
)

server <- function(session, input, output) {
  rv <- reactiveValues(data = NULL, orig=NULL)
  
  observeEvent(input$file1, {
    file <- input$file1
    ext <- tools::file_ext(file$datapath)
    
    req(file)
    
    validate(need(ext == "csv", "Please upload a csv file"))
    
    rv$orig <- read.csv(file$datapath, header = input$header)
    rv$data <- rv$orig
  })
  
  output$selectUI<-renderUI({
    req(rv$data)
    selectInput(inputId='selectcolumn', label='select column', choices = names(rv$data))
  })
  
  
  observeEvent(input$Splitcolumn, {
    rv$data <- splitColumn(rv$data, input$selectcolumn)
  })
  
  observeEvent(input$deleteRows,{
    if (!is.null(input$table1_rows_selected)) {
      rv$data <- rv$data[-as.numeric(input$table1_rows_selected),]
    }
  })
  
  output$table1 <- renderDT({
    rv$data
  })
  observeEvent(input$replacevalues, {
    rv$data <- fillvalues(rv$data, input$textbox, input$selectcolumn)
  })
  observeEvent(input$removecolumn, {
    rv$data <- removecolumn(rv$data,input$selectcolumn)
  })
  observeEvent(input$Undo, {
    rv$data <- rv$orig
  })
}

Upvotes: 3

Views: 397

Answers (1)

jpdugo17
jpdugo17

Reputation: 7106

We can create a list to host every instance of the table to recover multiple undo's. Note that if the .csv is very big this approach will become inefficient very quick. We can mitigate this infefficiency by implementing a button that clears the undo list up to a point or implementing an append function that saves only the part modified of the table rather than the whole table.

Please, fill free to modify the answer or use it for another answer.

library(shiny)
library(reshape2)
library(DT)
library(tibble)


###function for deleting the rows
splitColumn <- function(data, column_name) {
  
  newColNames <- c("Unmerged_type1", "Unmerged_type2")
  newCols     <- colsplit(data[[column_name]], " ", newColNames)
  after_merge <- cbind(data, newCols)
  after_merge[[column_name]] <- NULL
  after_merge
}

###_______________________________________________
### function for inserting a new column

fillvalues <- function(data, values, columName){
  
  df_fill <- data
  vec     <- strsplit(values, ",")[[1]]
  tibble::add_column(df_fill, newcolumn = vec, .after = columName)
}

##function for removing the colum

removecolumn <- function(df, nameofthecolumn){
  df[ , -which(names(df) %in% nameofthecolumn)]
}


# APP ---------------------------------------------------------------------



ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      fileInput("file1", "Choose CSV File", accept = ".csv"),
      checkboxInput("header", "Header", TRUE),
      actionButton("Splitcolumn", "SplitColumn"),
      uiOutput("selectUI"),
      actionButton("deleteRows", "Delete Rows"),
      textInput("textbox", label = "Input the value to replace:"),
      actionButton("replacevalues", label = 'Replace values'),
      actionButton("removecolumn", "Remove Column"),
      actionButton("Undo", 'Undo')
    ),
    mainPanel(
      DTOutput("table1")
    )
  )
)



server <- function(session, input, output) {
  
  #added undo (a list) and counter to accumulate more than one undo
  rv <- reactiveValues(data = NULL, orig=NULL, undo = list(), counter = 1)
  
  

# csv file ----------------------------------------------------------------

  
  observeEvent(input$file1, {
    file <- input$file1
    ext  <- tools::file_ext(file$datapath)
    
    req(file)
    
    validate(need(ext == "csv", "Please upload a csv file"))
    
    rv$orig <- read.csv(file$datapath, header = input$header)
    rv$data <- rv$orig
  })
  
  
  
  output$selectUI <- renderUI({
    req(rv$data)
    selectInput(inputId='selectcolumn', label='select column', choices = names(rv$data))
  })
  
  

# rest of the app ---------------------------------------------------------
  

  
  
  observeEvent(input$Splitcolumn, {
    rv$undo[[rv$counter]] <- rv$data
    rv$counter <- rv$counter + 1
    rv$data <- splitColumn(rv$data, input$selectcolumn)
  })
  
  observeEvent(input$deleteRows,{
    
    if (!is.null(input$table1_rows_selected)) {
      rv$undo[[rv$counter]] <- rv$data
      rv$counter <- rv$counter + 1
      rv$data <- rv$data[-as.numeric(input$table1_rows_selected),]
    }
  })
  
  output$table1 <- renderDT({
    rv$data
  })
  
  observeEvent(input$replacevalues, {
    rv$undo[[rv$counter]] <- rv$data
    rv$counter <- rv$counter + 1
    rv$data <- fillvalues(rv$data, input$textbox, input$selectcolumn)
  })
  
  observeEvent(input$removecolumn, {
    rv$undo[[rv$counter]] <- rv$data
    rv$counter <- rv$counter + 1
    rv$data <- removecolumn(rv$data,input$selectcolumn)
  })
  
  observeEvent(input$Undo, {
    if (rv$counter > 1) {
    rv$data <- rv$undo[[rv$counter - 1]]
    #index must be more than 1
    
      rv$counter <- rv$counter - 1
    }
  })
  
}

shinyApp(ui, server)


Upvotes: 2

Related Questions