1. Edit a table with Shiny and rhandsontable

    2016-08-15
    Source

    With the help of the awesome rhandsontable package, I’ve done a Shiny app allowing to edit a data frame and to save the result in a file than can be loaded in R. I was greatly helped by an example given in the github repo of this package.

    For example, take the following data frame:

    ( DF <- data.frame(Value = 1:10, Status = TRUE, Name = LETTERS[1:10],
                     Date = seq(from = Sys.Date(), by = "days", length.out = 10),
                     stringsAsFactors = FALSE) )
    ##    Value Status Name       Date
    ## 1      1   TRUE    A 2016-08-15
    ## 2      2   TRUE    B 2016-08-16
    ## 3      3   TRUE    C 2016-08-17
    ## 4      4   TRUE    D 2016-08-18
    ## 5      5   TRUE    E 2016-08-19
    ## 6      6   TRUE    F 2016-08-20
    ## 7      7   TRUE    G 2016-08-21
    ## 8      8   TRUE    H 2016-08-22
    ## 9      9   TRUE    I 2016-08-23
    ## 10    10   TRUE    J 2016-08-24

    The app is launched by running the function editTable I will give below. Just take a look before:

    editTable(DF, outdir="~/Documents/", outfilename="newDF")

    Now I edit the table:

    I click on the Save button and I get the ASCII file newDF.txt containing the new data frame:

    dget("newDF.txt")
    ##    Value Status  Name       Date
    ## 1   1000  FALSE Alice 2006-01-01
    ## 2   2000  FALSE   Bob 2006-01-02
    ## 3   3000  FALSE Chris 2006-01-03
    ## 4      4   TRUE     D 2016-08-18
    ## 5      5   TRUE     E 2016-08-19
    ## 6      6   TRUE     F 2016-08-20
    ## 7      7   TRUE     G 2016-08-21
    ## 8      8   TRUE     H 2016-08-22
    ## 9      9   TRUE     I 2016-08-23
    ## 10    10   TRUE     J 2016-08-24

    Here is my code:

    library(rhandsontable)
    library(shiny)
    
    editTable <- function(DF, outdir=getwd(), outfilename="table"){
      ui <- shinyUI(fluidPage(
        
        titlePanel("Edit and save a table"),
        sidebarLayout(
          sidebarPanel(
            helpText("Shiny app based on an example given in the rhandsontable package.", 
                     "Right-click on the table to delete/insert rows.", 
                     "Double-click on a cell to edit"),
            
            wellPanel(
              h3("Table options"),
              radioButtons("useType", "Use Data Types", c("TRUE", "FALSE"))
            ),
            br(), 
            
            wellPanel(
              h3("Save table"), 
              div(class='row', 
                  div(class="col-sm-6", 
                      actionButton("save", "Save")),
                  div(class="col-sm-6",
                      radioButtons("fileType", "File type", c("ASCII", "RDS")))
              )
            )
            
          ),
          
          mainPanel(
            wellPanel(
              uiOutput("message", inline=TRUE)
            ),
            
            actionButton("cancel", "Cancel last action"),
            br(), br(), 
            
            rHandsontableOutput("hot"),
            br(),
            
            wellPanel(
              h3("Add a column"),
              div(class='row', 
                  div(class="col-sm-5", 
                      uiOutput("ui_newcolname"),
                      actionButton("addcolumn", "Add")),
                  div(class="col-sm-4", 
                      radioButtons("newcolumntype", "Type", c("integer", "double", "character"))),
                  div(class="col-sm-3")
              )
            )
            
          )
        )
      ))
      
      server <- shinyServer(function(input, output) {
        
        values <- reactiveValues()
        
        ## Handsontable
        observe({
          if (!is.null(input$hot)) {
            values[["previous"]] <- isolate(values[["DF"]])
            DF = hot_to_r(input$hot)
          } else {
            if (is.null(values[["DF"]]))
              DF <- DF
            else
              DF <- values[["DF"]]
          }
          values[["DF"]] <- DF
        })
        
        output$hot <- renderRHandsontable({
          DF <- values[["DF"]]
          if (!is.null(DF))
            rhandsontable(DF, useTypes = as.logical(input$useType), stretchH = "all")
        })
        
        ## Save 
        observeEvent(input$save, {
          fileType <- isolate(input$fileType)
          finalDF <- isolate(values[["DF"]])
          if(fileType == "ASCII"){
            dput(finalDF, file=file.path(outdir, sprintf("%s.txt", outfilename)))
          }
          else{
            saveRDS(finalDF, file=file.path(outdir, sprintf("%s.rds", outfilename)))
          }
        }
        )
        
        ## Cancel last action    
        observeEvent(input$cancel, {
          if(!is.null(isolate(values[["previous"]]))) values[["DF"]] <- isolate(values[["previous"]])
        })
        
        ## Add column
        output$ui_newcolname <- renderUI({
          textInput("newcolumnname", "Name", sprintf("newcol%s", 1+ncol(values[["DF"]])))
        })
        observeEvent(input$addcolumn, {
          DF <- isolate(values[["DF"]])
          values[["previous"]] <- DF
          newcolumn <- eval(parse(text=sprintf('%s(nrow(DF))', isolate(input$newcolumntype))))
          values[["DF"]] <- setNames(cbind(DF, newcolumn, stringsAsFactors=FALSE), c(names(DF), isolate(input$newcolumnname)))
        })
        
        ## Message
        output$message <- renderUI({
          if(input$save==0){
            helpText(sprintf("This table will be saved in folder \"%s\" once you press the Save button.", outdir))
          }else{
            outfile <- ifelse(isolate(input$fileType)=="ASCII", "table.txt", "table.rds")
            fun <- ifelse(isolate(input$fileType)=="ASCII", "dget", "readRDS")
            list(helpText(sprintf("File saved: \"%s\".", file.path(outdir, outfile))),
                 helpText(sprintf("Type %s(\"%s\") to get it.", fun, outfile)))
          }
        })
        
      })
      
      ## run app 
      runApp(list(ui=ui, server=server))
      return(invisible())
    }