Just wanted to add a note here because I spent awhile figuring out how to make this work with saved values. My version is very much derived from @ismirsehregal. I also created bookmarking modules that might be helpful to others. This was needed because shinyFiles inputs caused an error and needed to be excluded from bookmarks so I saved the value in a reactive and then saved it in onBookmark. This was the error when they were not excluded:
Error in writeImpl: Text to be written must be a length-one character vector
library(shiny)
library(shinyFiles)
# source these functions
#Saving #=======================================================================
save_bookmark_ui <- function(id){
actionButton(NS(id, "start_save"), "Save")
}
save_bookmark_server <- function(id, latestBookmarkURL, volumes){
moduleServer(id, function(input, output, session) {
shinyDirChoose(input, "save_dir", root = volumes)
save_dir_pth <- reactive(parseDirPath(volumes, input$save_dir))
onRestored(function(state) {
showNotification(paste("Restored session:", basename(state$dir)),
duration = 10, type = "message")
})
setBookmarkExclude(c("save_dir", "start_save", "save_action", "new_dir_name"))
observeEvent(input$start_save, {
showModal(
modalDialog(
p("The app session is saved using two files, input.rds and values.rds",
"You will provide a location and name for a new folder that will",
" be created to store these files. Make sure you choose a name",
"and location that will be easy to find when you want to load the ",
"saved inputs."),
strong("Choose location to save progess"),
br(),
shinyDirButton(NS(id, "save_dir"), "Location to create folder",
"Location to create folder"),
br(),
textInput(NS(id, "new_dir_name"),
"Choose a name for the new folder that will be created"),
br(),
footer = tagList(
actionButton(NS(id, "save_action"), "Save"),
modalButton("Cancel")
),
title = "Save assessment progress"
)
)
})
iv <- shinyvalidate::InputValidator$new()
iv$add_rule("new_dir_name", shinyvalidate::sv_optional())
iv$add_rule("new_dir_name",
shinyvalidate::sv_regex("[^[:alnum:]]",
paste0("Please choose a name with only",
" letters or numbers and no spaces"),
invert = TRUE))
observeEvent(input$save_action, {
if (!iv$is_valid()) {
iv$enable()
} else {
removeModal()
session$doBookmark()
if (input$new_dir_name != "") {
# "Error: Invalid state id" when using special characters - removing them:
tmp_session_name <- stringr::str_replace_all(input$new_dir_name,
"[^[:alnum:]]", "")
} else {
tmp_session_name <- paste(req(latestBookmarkURL))
}
# create the new directory in the chosen location
new_dir <- fs::dir_create(fs::path(save_dir_pth(), tmp_session_name))
message("Saving session")
# move the files from where shiny saves them to where the user can find them
fs::dir_copy(path = fs::path(".", "shiny_bookmarks", req(latestBookmarkURL)),
new_path = new_dir,
overwrite = TRUE)
}
}, ignoreInit = TRUE)
})
}
# Load #=======================================================================
load_bookmark_ui <- function(id){
actionButton(NS(id, "start_load"), "Load")
}
load_bookmark_server <- function(id, volumes){
moduleServer(id, function(input, output, session){
shinyDirChoose(input, "load_dir", root = volumes)
load_dir_pth <- reactive(parseDirPath(volumes, input$load_dir))
setBookmarkExclude(c("load_dir", "load_action", "start_load"))
observeEvent(input$start_load, {
showModal(
modalDialog(
strong("Select the folder where the app was saved"),
br(),
shinyDirButton(NS(id, "load_dir"), "Select Folder",
"Location of folder with previous state"),
footer = tagList(
actionButton(NS(id, "load_action"), "Load"),
modalButton("Cancel")
),
title = "Load existing assessment"
)
)
})
# LOAD SESSION
observeEvent(input$load_action, {
sessionName <- fs::path_file(load_dir_pth())
targetPath <- file.path(".", "shiny_bookmarks", sessionName)
if (!dir.exists(dirname(targetPath))) {
dir.create(dirname(targetPath), recursive = TRUE)
}
# copy the bookmark to where shiny expects it to be
fs::dir_copy(path = load_dir_pth(),
new_path = targetPath,
overwrite = TRUE)
restoreURL <- paste0(session$clientData$url_protocol, "//",
session$clientData$url_hostname, ":",
session$clientData$url_port, "/?_state_id_=",
sessionName)
removeModal()
# redirect user to restoreURL
shinyjs::runjs(sprintf("window.location = '%s';", restoreURL))
# showModal instead of redirecting the user
# showModal(modalDialog(
# title = "Restore Session",
# "The session data was uploaded to the server. Please visit:",
# tags$a(restoreURL, href = restoreURL),
# "to restore the session"
# ))
})
})
}
# Input options
valueNms <- c("Greatly increase", "Increase", "Somewhat increase", "Neutral")
valueOpts <- c(3, 2, 1, 0)
ui <- function(request) {
fluidPage(
shinyjs::useShinyjs(),
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")),
checkboxGroupInput("inChk","Checkbox:", choiceNames = valueNms,
choiceValues = valueOpts),
shinyFilesButton("range_poly_pth", "Choose file",
"Range polygon shapefile", multiple = FALSE),
verbatimTextOutput("range_poly_pth_out", placeholder = TRUE),
save_bookmark_ui("save"),
load_bookmark_ui("load"),
tableOutput("table")
)
}
server <- function(input, output, session) {
shinyFileChoose("range_poly_pth", root = volumes, input = input)
file_pth <- reactive({
if(is.integer(input$range_poly_pth)){
if(!is.null(restored$yes)){
return(file_pth_restore())
}
return(NULL)
} else{
return(parseFilePaths(volumes, input$range_poly_pth)$datapath)
}
})
output$table <- renderTable({
req(file_pth())
read.csv(file_pth())
})
output$range_poly_pth_out <- renderText({
file_pth()
})
setBookmarkExclude(c("range_poly_pth",
"range_poly_pth_out"))
# this part is not allowed to be inside the module
latestBookmarkURL <- reactiveVal()
onBookmarked(
fun = function(url) {
latestBookmarkURL(parseQueryString(url))
showNotification("Session saved",
duration = 10, type = "message")
}
)
R.utils::withTimeout({
volumes <- c(wd = getShinyOption("file_dir"),
Home = fs::path_home(),
getVolumes()())
}, timeout = 10, onTimeout = "error")
save_bookmark_server("save", latestBookmarkURL(), volumes)
load_bookmark_server("load", volumes)
# Need to explicitly save and restore reactive values.
onBookmark(function(state) {
val2 <- Sys.Date()
state$values$date <- val2
state$values$file <- file_pth()
})
restored <- reactiveValues()
file_pth_restore <- reactiveVal()
onRestore(fun = function(state){
file_pth_restore(state$values$file)
print(file_pth_restore())
restored$yes <- TRUE
})
}
shinyApp(ui, server, enableBookmarking = "server")
readRDS("...myAppPath/shiny_bookmarks/cad39269e2348a09/input.rds")
the input list - even though restoring via URL for sure is more convenient. Here is a related answer I gave. – Nomenclature