2015-09-08 7 views
5

La mia app Shiny ha diversi input che vengono utilizzati per definire diversi parametri di un grafico generato. È molto probabile che l'utente trascorrerà alcuni minuti attraverso tutte le opzioni possibili finché non sarà soddisfatto dell'output. Ovviamente la trama può essere esportata in diversi formati, ma è possibile che l'utente vorrà ricreare la stessa trama con dati diversi in seguito, o forse semplicemente cambiare un piccolo dettaglio.Esporta tutti gli input utente in un'app Shiny per caricarli e caricarli in un secondo momento

Per questo motivo, ho bisogno di offrire all'utente un modo per esportare tutte le sue impostazioni e conservare quel file per un uso successivo. Ho sviluppato un approccio, ma non sta funzionando bene. Sto usando reactiveValuesToList per ottenere i nomi di tutti gli elementi di input e salvarli come un semplice file di testo con il formato inputname=inputvalue. Questo è il downloadHandler su 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) 
    }) 

Questo funziona bene, ma il caricamento non sta andando molto bene. Dal momento che non riesco (e non riesco a capire come) salvare il tipo di input, devo aggiornare i valori alla cieca. Questo è come lo faccio:

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])    
     }) 
     } 
    }  
    }) 

A parte il shinyjs::colorInput, che può essere riconosciuto dal inizio #, devo usare try() per gli altri. Questo funziona, parzialmente, ma alcuni input non vengono aggiornati. Ispezionare manualmente il file esportato mostra che gli input che non sono stati aggiornati sono lì, quindi suppongo che l'aggiornamento di 100+ input contemporaneamente non sia una buona idea. Anche la parte try() non sembra buona e probabilmente non è una buona idea.

L'app è quasi terminata, ma probabilmente verrà aggiornata in futuro, con alcuni input aggiunti/modificati. È accettabile se questo rende invalidi alcuni "vecchi" input esportati, poiché proverò a mantenere la compatibilità con le versioni precedenti. Ma sto cercando un approccio che non stia solo scrivendo centinaia di righe per aggiornare gli input uno per uno.

Ho pensato di utilizzare save.image() ma semplicemente utilizzando load() non ripristinare gli input dell'app. Ho anche considerato un modo per aggiornare in qualche modo tutti gli input contemporaneamente, invece che uno alla volta, ma non ho trovato nulla. Esiste un modo migliore per esportare tutti gli input dell'utente in un file e quindi caricarli tutti? Non importa se si tratta di un tweak per questo che funziona meglio o un approccio completamente diverso.

+0

La tua app presenta input altamente dipendenti? Sono molti i blocchi 'renderUI' o ci sono molti input' numericInput'? – Mark

+0

@Mark Sono tutti input "statici", nessuno dei quali è stato creato usando 'renderUI'. – Molx

+0

@Molx Se non trovi una soluzione di cui sei soddisfatto, ti prego di contattarmi. Se una delle soluzioni qui funziona, allora ottimo :) (bello vedere il mio coloreInput utilizzato!) –

risposta

9

Se si guarda il codice delle funzioni di aggiornamento dell'input lucido, terminano con session$sendInputMessage(inputId, message). message è una lista di attributi che hanno bisogno di essere cambiato nel ingresso, ad es, per un ingresso casella di controllo: message <- dropNulls(list(label = label, value = value))

Poiché la maggior parte degli input hanno l'attributo value, si può semplicemente utilizzare la funzione session$sendInputMessage direttamente su ognuna di esse senza lo try.

Ecco un esempio, ho creato dummy_data di aggiornare tutti gli ingressi quando si fa clic sul pulsante, la struttura dovrebbe essere simile a quello che si esporta:

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])) 
    } 
    }) 

}) 

Tutto ilLe funzioni 0 preformano anche il valore prima di chiamare session$sendInputMessage. Non ho provato tutti gli ingressi possibili ma almeno per questi 3 è possibile passare una stringa alla funzione per cambiare il numericInput e funziona ancora bene.

Se questo è un problema per alcuni dei vostri input, si potrebbe desiderare di salvare reactiveValuesToList(input) utilizzando save, e quando si desidera aggiornare gli ingressi, utilizzare load ed eseguire l'elenco nel ciclo for (dovrete adattarsi in una lista nominata).

+0

Anche se questo è stato un cambiamento piuttosto semplice, è stato molto utile rimuovere il brutto 'try()' e una buona conoscenza. Alla fine mi ha aiutato a trovare il problema più grosso dell'approccio, ovvero l'esportazione di alcuni valori reattivi indesiderati e inattesi e l'esecuzione di un attacco di iniezione sulla funzione di caricamento. – Molx

1

A meno che non si sta facendo un sacco di ingressi di tipo altamente flessibili (renderUI blocchi che potrebbe essere qualsiasi tipo di ingresso) allora si potrebbe creare una lista memorizzare tutti i valori di corrente, utilizzare dput per salvarli in un file con un corrispondente dget per leggerlo.

In una app che ho, autorizzo gli utenti a scaricare un file che memorizza tutti i loro dati caricati più tutte le loro opzioni.

output$saveData <- downloadHandler(
    filename = function() { 
    paste0('Export_',Sys.Date(),'.sprout') 
    }, 
    content = function(file) { 
    dataToExport = list() 
    #User specified options 
    dataToExport$sproutData$transformations=sproutData$transformations #user specified transformations 
    dataToExport$sproutData$processing=sproutData$processing #user specified text processing rules 
    dataToExport$sproutData$sc=sproutData$sc #user specified option to spell check 
    dataToExport$sproutData$scOptions=sproutData$scOptions #user specified spell check options (only used if spell check is turned on) 
    dataToExport$sproutData$scLength=sproutData$scLength #user specified min word lenght for spell check (only used if spell check is turned on) 
    dataToExport$sproutData$stopwords=sproutData$stopwords #user specified stopwords 
    dataToExport$sproutData$stopwordsLastChoice=sproutData$stopwordsLastChoice #last pre-built list selected 
    dput(dataToExport,file=file) 
    } 
) 

Qui creo una lista vuota, quindi inserisco i valori che utilizzo nella mia app. Il motivo per la struttura dTE$sD$name è che ho un reactiveValues chiamato sproutData che memorizza tutte le opzioni e i dati selezionati dall'utente. Quindi, conservo la struttura nell'output.

Poi, ho una pagina caricamento di dati che esegue le seguenti:

output$loadStatusIndicator = renderUI({ 
    worked = T 
    a = tryCatch(dget(input$loadSavedData$datapath),error=function(x){worked<<-F}) 
    if(worked){ 
    #User specified options 
    a$sproutData$transformations->sproutData$transformations #user specified transformations 
    a$sproutData$processing->sproutData$processing #user specified text processing rules 
    updateCheckboxGroupInput(session,"processingOptions",selected=sproutData$processing) 
    a$sproutData$sc->sproutData$sc #user specified option to spell check 
    updateCheckboxInput(session,"spellCheck",value = sproutData$sc) 
    a$sproutData$scOptions->sproutData$scOptions #user specified spell check options (only used if spell check is turned on) 
    updateCheckboxGroupInput(session,"spellCheckOptions",selected=sproutData$scOptions) 
    a$sproutData$scLength->sproutData$scLength #user specified min word lenght for spell check (only used if spell check is turned on) 
    updateNumericInput(session,"spellCheckMinLength",value=sproutData$scLength) 
    a$sproutData$stopwords->sproutData$stopwords #user specified stopwords 
    a$sproutData$stopwordsLastChoice->sproutData$stopwordsLastChoice 
    if(sproutData$stopwordsLastChoice[1] == ""){ 
     updateSelectInput(session,"stopwordsChoice",selected="none") 
    } else if(all(sproutData$stopwordsLastChoice == stopwords('en'))){ 
     updateSelectInput(session,"stopwordsChoice",selected="en") 
    } else if(all(sproutData$stopwordsLastChoice == stopwords('SMART'))){ 
     updateSelectInput(session,"stopwordsChoice",selected="SMART") 
    } 
    HTML("<strong>Loaded data!</strong>") 
    } else if (!is.null(input$loadSavedData$datapath)) { 
    HTML(paste("<strong>Not a valid save file</strong>")) 
    } 
}) 

L'uscita effettiva è una tabella che dettaglia cosa pensa e cosa impostato.Ma poiché conosco tutti gli input e non cambiano, posso memorizzarli esplicitamente (valore predefinito o modificato) e aggiornarli esplicitamente quando il file di salvataggio viene caricato.

+0

Ho anche pensato di aggiornare "manualmente" ognuno di essi, ma questo aggiungerebbe troppe righe (attualmente, 117), che sarebbero molto difficili da maitain. L'app è quasi finita, ma direi che è in alpha e probabilmente verrà modificata, potenzialmente ricevendo più input, magari cambiandone alcuni. Ne aggiungerò un po 'alla domanda. – Molx

3

Questo è un po 'vecchio, ma penso che sia utile pubblicare un esempio completo, salvando e caricando gli input dell'utente.

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') 
    }) 
})