【问题标题】:Export all user inputs in a Shiny app to file and load them later将 Shiny 应用程序中的所有用户输入导出到文件并稍后加载
【发布时间】:2015-09-08 14:31:28
【问题描述】:

我的 Shiny 应用程序有几个输入,用于定义生成图的多个参数。用户很可能会花几分钟时间浏览所有可能的选项,直到他对输出感到满意为止。显然,绘图可以以不同的格式导出,但用户可能希望稍后使用不同的数据重新创建相同的绘图,或者只是更改一个小细节。

因此,我需要为用户提供一种方法来导出他的所有设置并保留该文件以供以后使用。我开发了一种方法,但效果不佳。我使用reactiveValuesToList 来获取所有输入元素的名称,并以inputname=inputvalue 的格式保存为一个简单的文本文件。这是server.R上的downloadHandler

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

这工作正常,但加载并不顺利。由于我不(也不知道如何)保存输入类型,我必须盲目地更新这些值。我就是这样做的:

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

除了shinyjs::colorInput可以通过#开头识别,其他的我都得用try()。这部分可行,但某些输入未更新。手动检查导出的文件显示没有更新的输入在那里,所以我认为一次更新 100 多个输入不是一个好主意。此外,try() 部分看起来不太好,可能不是一个好主意。

应用程序即将完成,但将来可能会更新,添加/更改一些输入。如果这甚至使某些“旧”导出的输入无效,这是可以接受的,因为我将尝试保持向后兼容性。但我正在寻找一种方法,而不仅仅是编写数百行来逐一更新输入。

我曾考虑过使用save.image(),但仅使用load() 不会恢复应用输入。我还考虑了一种以某种方式一次更新所有输入的方法,而不是一个一个地更新,但没有想出任何东西。有没有更好的方法将所有用户输入导出到文件然后全部加载?不管是对这个效果更好的调整还是完全不同的方法。

【问题讨论】:

  • 您的应用程序是否具有高度依赖的输入?是很多 renderUI 块还是很多 numericInput -esque 输入?
  • @Mark 它们都是“静态”输入,没有一个是使用renderUI创建的。
  • @Molx 如果您没有找到满意的解决方案,请与我联系。如果这里的解决方案之一有效,那就太好了:)(很高兴看到我的 colourInput 被使用了!)
  • @daattali 你不知道当我找到colourInput 时我有多高兴,没有它我的应用程序就不会那么有用。 shinyjs::reset 也很整洁。我不得不说,我刚刚发现这里的主要问题是shinyjs-resettable-settings 也在被导出和加载,这使得输入值变得一团糟,因为它是一个充满"input = value" 的列表。但现在已经解决了。 :)
  • @Molx 我不完全理解正在发生的问题,您介意更详细地解释一下吗?如果我的代码有任何问题,我想知道,这样人们将来就不会遇到问题

标签: r shiny


【解决方案1】:

如果您查看闪亮输入更新函数的代码,它们以session$sendInputMessage(inputId, message) 结尾。 message 是需要在输入中更改的属性列表,例如,对于复选框输入:message &lt;- dropNulls(list(label = label, value = value))

由于大多数输入具有value 属性,您可以直接在所有输入上使用session$sendInputMessage 函数,而无需try

这里是一个例子,我创建了dummy_data,当你点击按钮时更新所有的输入,结构应该和你导出的类似:

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

})

所有update 函数还在调用session$sendInputMessage 之前预先格式化值。我没有尝试所有可能的输入,但至少对于这 3 个,您可以将字符串传递给函数以更改 numericInput 并且它仍然可以正常工作。

如果您的某些输入存在问题,您可能希望使用save 保存reactiveValuesToList(input),当您想更新输入时,请使用load 并在for 循环中运行列表(您必须将其调整为命名列表)。

【讨论】:

  • 虽然这是一个相当简单的更改,但删除丑陋的try() 和良好的知识非常有用。最终,它帮助我找到了该方法的最大问题,即导出了一些不希望的和意外的反应值,并对加载函数进行了注入攻击。
【解决方案2】:

这有点老了,但我认为发布一个完整的例子很有用,保存和加载用户输入。

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

【讨论】:

  • 这在有空值之前运行良好,例如使用复选框时。加载输入时,unlist 命令无法为任何具有空值的条目创建条目,从而导致下标错误,因为inputvalues 的长度小于savesInputs 的长度。
【解决方案3】:

除非您正在执行大量高度灵活的类型输入(renderUI 块可以是任何类型的输入),否则您可以创建一个存储所有当前值的列表,使用 dput 将它们保存到一个文件中一个相应的dget 来读取它。

在我拥有的一个应用程序中,我允许用户下载一个文件,该文件存储了他们上传的所有数据以及他们的所有选项。

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

在这里我创建了一个空列表,然后我坚持我在我的应用程序中使用的值。 dTE$sD$name 结构的原因是我有一个名为sproutDatareactiveValues,它存储所有用户选择的选项和数据。所以,我保留了输出中的结构。

然后,我有一个加载数据页面,它执行以下操作:

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

实际输出是一个表格,其中详细说明了它找到的内容和设置的内容。但是,因为我知道所有输入并且它们不会更改,所以我可以显式存储它们(默认值或更改值),然后在上传保存文件时显式更新它们。

【讨论】:

  • 我也考虑过“手动”更新每一个,但这会添加太多行(目前为 117 行),这将很难维护。该应用程序几乎完成了,但我会说它处于 alpha 阶段,可能会被修改,可能会接收更多输入,可能会更改其中一些。我会在问题中添加一些内容。
猜你喜欢
  • 2014-09-30
  • 1970-01-01
  • 2017-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多