Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Export all user inputs in a Shiny app to file and load them later

Tags:

r

shiny

My Shiny app has several inputs which are used to define several parameters of a generated plot. It's very likely that the user will spend some minutes going through all possible options until he's satisfied with the output. Obviously the plot can be exported in different formats, but it's possible that the user will want to recreate the same plot with different data later, or maybe just change one small detail.

Because of this, I need to offer the user a way to export all his settings and keep that file for later use. I've developed an approach, but it isn't working well. I'm using reactiveValuesToList to get the names of all input elements and save as a simple text file with the format inputname=inputvalue. This is the downloadHandler on server.R:

output$bt_export <- downloadHandler(
  filename = function() {
    "export.txt"
  },
  content = function(file) {
    inputsList <- names(reactiveValuesToList(input))
    exportVars <- paste0(inputsList, "=", sapply(inputsList, function(inpt) input[[inpt]]))
    write(exportVars, file)
  })

This works fine, but loading isn't going very smoothly. Since I don't (and couldn't figure out how) save the input type, I have to update the values blindly. This is how I do it:

importFile <- reactive({      
  inFile <- input$fileImport      
  if (is.null(inFile))
    return(NULL)      
  lines <- readLines(inFile$datapath)
  out <- lapply(lines, function(l) unlist(strsplit(l, "=")))      
  return(out)
})

observe({
    imp <- importFile()            
    for (inpt in imp) {
      if (substr(inpt[2], 0, 1) == "#") {
        shinyjs::updateColourInput(session, inputId = inpt[1], value = inpt[2])
      } else {
        try({
          updateTextInput(session, inputId = inpt[1], value = inpt[2])
          updateNumericInput(session, inputId = inpt[1], value = inpt[2])
          updateSelectInput(session, inputId = inpt[1], selected = inpt[2])              
        })
      }
    }       
  })

Apart from the shinyjs::colorInput, which can be recognized by the # start, I have to use try() for the others. This works, partially, but some inputs are not being updated. Inspecting the exported file manually shows that inputs which weren't updated are there, so I suppose that updating 100+ inputs at once isn't a good idea. Also the try() part doesn't look good and is probably not a good idea.

The app is close to finished, but will probably be updated in the future, having some inputs added/changed. It's acceptable if this even make some "old" exported inputs invalid, since I'll try keep the backwards compatibility. But I'm looking for an approach that isn't just writing hundreds of lines to update the inputs one-by-one.

I've thought about using save.image() but simply using load() does not restore the app inputs. I also considered a way to somehow update all inputs at once, instead of one-by-one, but didn't come up with anything. Is there any better way to export all user inputs to a file and then load them all? It doesn't matter if it's a tweak to this one that works better or a completely different approach.

like image 474
Molx Avatar asked Sep 08 '15 14:09

Molx


People also ask

What is a datapath in shiny apps?

This is a formal specification of the file type that is usually derived from the extension and is rarely needed in Shiny apps. datapath: the path to where the data has been uploaded on the server. Treat this path as ephemeral: if the user uploads more files, this file may be deleted.

Can I allow users to upload their own data to shiny?

Sometimes you’ll want users to be able to upload their own data to your application. Shiny makes it easy to offer your users file uploads straight from the browser, which you can then access from your server logic. This feature does not work with Internet Explorer 9 and earlier (not even with Shiny Server).

What is a fileinput in shiny?

Most inputs return simple vectors, but fileInput () returns a data frame with four columns: name: the original file name on the user’s computer. size: the file size, in bytes. By default, the user can only upload files up to 5 MB. You can increase this limit by setting the shiny.maxRequestSize option prior to starting Shiny.

Why does shiny have a file management interface?

This is an unusual interface, but it allows Shiny to control where the file should be saved (so it can be placed in a secure location) while you still control the contents of that file. Next we’ll put these pieces together to show how to transfer data files or reports to the user.


2 Answers

If you look at the code of the shiny input update functions, they end by session$sendInputMessage(inputId, message). message is a list of attributes that need to be changed in the input, for ex, for a checkbox input: message <- dropNulls(list(label = label, value = value))

Since most of the input have the value attribute, you can just use the session$sendInputMessage function directly on all of them without the try.

Here's an example, I created dummy_data to update all the inputs when you click on the button, the structure should be similar to what you export:

ui.R

library(shiny)
shinyUI(fluidPage(
  textInput("control_label",
            "This controls some of the labels:",
            "LABEL TEXT"),
  numericInput("inNumber", "Number input:",
               min = 1, max = 20, value = 5, step = 0.5),
  radioButtons("inRadio", "Radio buttons:",
               c("label 1" = "option1",
                 "label 2" = "option2",
                 "label 3" = "option3")),
  actionButton("update_data", "Update")

  ))

server.R

library(shiny)

dummy_data <- c("inRadio=option2","inNumber=10","control_label=Updated TEXT" )

shinyServer(function(input, output,session) {
  observeEvent(input$update_data,{    
    out <- lapply(dummy_data, function(l) unlist(strsplit(l, "="))) 
   for (inpt in out) {
     session$sendInputMessage(inpt[1], list(value=inpt[2]))
    }
   })

})

All the update functions also preformat the value before calling session$sendInputMessage. I haven't tried all possible inputs but at least for these 3 you can pass a string to the function to change the numericInput and it still works fine.

If this is an issue for some of your inputs, you might want to save reactiveValuesToList(input) using save, and when you want to update your inputs, use load and run the list in the for loop (you'll have to adapt it to a named list).

like image 96
NicE Avatar answered Oct 12 '22 00:10

NicE


This is a bit old but I think is usefull to post a complete example, saving and loading user inputs.

library(shiny)  

ui <- shinyUI(fluidPage(
  textInput("control_label",
            "This controls some of the labels:",
            "LABEL TEXT"),
  numericInput("inNumber", "Number input:",
               min = 1, max = 20, value = 5, step = 0.5),
  radioButtons("inRadio", "Radio buttons:",
               c("label 1" = "option1",
                 "label 2" = "option2",
                 "label 3" = "option3")),

  actionButton("load_inputs", "Load inputs"), 
  actionButton('save_inputs', 'Save inputs')

)) 

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

  observeEvent(input$load_inputs,{   

    if(!file.exists('inputs.RDS')) {return(NULL)}

    savedInputs <- readRDS('inputs.RDS')

    inputIDs      <- names(savedInputs) 
    inputvalues   <- unlist(savedInputs) 
    for (i in 1:length(savedInputs)) { 
      session$sendInputMessage(inputIDs[i],  list(value=inputvalues[[i]]) )
    }
  })

  observeEvent(input$save_inputs,{ 
    saveRDS( reactiveValuesToList(input) , file = 'inputs.RDS')
  })  
})
like image 29
Eduardo Bergel Avatar answered Oct 12 '22 01:10

Eduardo Bergel