Skip to content

Commit

Permalink
Fixed handling of checkbox groups and multiple select inputs (now dep…
Browse files Browse the repository at this point in the history
…eds on shiny >= 0.12.0), and correctly restore numeric inputs when the default is integer
  • Loading branch information
aoles committed Jan 25, 2016
1 parent 09eda18 commit 77ca91a
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 60 deletions.
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Package: shinyURL
Type: Package
Title: Save and restore the state of Shiny apps
Version: 0.0.21
Version: 0.0.22
Encoding: UTF-8
Author: Andrzej Oleś
Maintainer: Andrzej Oleś <[email protected]>
Description: Save and restore the state of a Shiny app's widgets by encoding
them in an URL query string.
Imports:
RCurl,
shiny (>= 0.11.0),
shiny (>= 0.12.0),
utils
License: Artistic-2.0
RoxygenNote: 5.0.1
88 changes: 36 additions & 52 deletions R/shinyURL.R
Original file line number Diff line number Diff line change
Expand Up @@ -168,57 +168,36 @@ initFromURL = function(session, nestedDependency = FALSE, self, encode, resume =
idx = grep("_mouse_(over|out)$", names(inputValues))
if ( length(idx) > 0 ) inputValues = inputValues[-idx]

## NOTE: the default values of checkbox groups are encoded as TRUE/FALSE
## values of individual elements named as checkboxGroupNameX, where X is the
## element index; the _values_ set by user are stored as a vector under the
## checkboxGroupName

## NOTE2: The behavior described above has changed in shiny 0.12.0, so that
## the default values are no longer stored as checkboxGroupNameX elements.
## Therefore, the code below can be simplified if dependency on shiny (>=
## 0.12.0) is stated in the DESCRIPTION

## sort to catch checkboxGroupNames before individual element names
names = sort(names(inputValues))
addValues = NULL
i = 1L
while (i <= length(names) ) {
n = names[i]
idx = grep(sprintf("^%s[0-9]+", n), names)

if ( length(idx) == 0L ) {
## advance to the next input
i = i + 1L
} else {
## encountered CheckboxGroup; do not increment i because the
## corresponding element is removed from the list
value = inputValues[[n]]
names = names[-c(i, idx)]
## this is important to be able to have all checkboxes unchecked
if ( is.null(value) ) value = ""
values = as.list(value)
## expand CheckboxGroup vectors
names(values) = sprintf("%s[%s]", n, seq_along(value))
addValues = c(values, addValues)
}
}
inputValues = inputValues[names]
inputValues = c(inputValues, addValues)

inputValues = lapply(inputValues, function(x) {
if (length(x) == 1L) {
## encode TRUE/FALSE as T/F
if ( is.logical(x) ) {
if (isTRUE(x)) "T" else "F"
}
else x
}
inputValues = mapply(function(name, value) {
## this is important to be able to have all checkboxes unchecked
if (is.null(value))
""
else {
## encode vectors as comma separated lists
if (class(x)=="Date") x = as.integer(x)
paste(x, collapse=",")
if (length(value) == 1L) {
## encode TRUE/FALSE as T/F
if (is.logical(value)) {
if (isTRUE(value)) "T" else "F"
}
else value
}
else {
cl = class(value)
## expand checkbox group and multiple select vectors
if (cl=="character") {
setNames(as.list(value), sprintf("%s[%s]", name, seq_along(value)))
}
## encode range vectors as comma separated string
else {
if (cl=="Date") value = as.integer(value)
paste(value, collapse=",")
}
}
}
})
}, names(inputValues), inputValues, SIMPLIFY=FALSE)

## remove names of sublists before flattening
names(inputValues)[sapply(inputValues, is.list)] = ""
inputValues = unlist(inputValues)

url = URLencode(paste0(
session$clientData$url_protocol, "//",
Expand Down Expand Up @@ -266,19 +245,24 @@ initFromURL = function(session, nestedDependency = FALSE, self, encode, resume =
}

.initInputs = function(session, queryValues, inputValues) {


for (i in seq_along(queryValues)) {
q = queryValues[[i]]

q = if (is.list(q)) {
## checkbox group
## checkbox group or multiple select
unname(q)
}
else {
## decode vectors (ranges sliders, date ranges)
## decode range vectors (sliders and dates)
if (length(inputValues[[i]])>1L)
q = unlist(strsplit(q, ","))
## use information about the class of the inputs when initializing them
cl = class(inputValues[[i]])[1L]
cl = class(inputValues[[i]])
## promote integer to numeric because numericInputs can contain either
if (cl=="integer")
cl = "numeric"
switch(cl,
## Dates need to be handled separately
Date = format(as.Date(as.numeric(q), "1970-01-01"), "%Y-%m-%d"),
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ The state of a shiny app gets saved by encoding its input values into an URL. To

These points are especially relevant for apps with lots of controls.

### Input IDs

Avoid using input IDs which differ by appended numbers, i.e. do not use `value` along with `value2`.

### Action buttons

Unfortunately, operations performed using action buttons cannot be reliably recorded and restored.
Expand Down
8 changes: 6 additions & 2 deletions inst/examples/widgets/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ shinyUI(fluidPage(
)),

column(4, wellPanel(
sliderInput("sliderB", label = h3("Slider range"), min = 0, max = 100, value = c(25, 75))
sliderInput("sliderB", label = h3("Slider range"), min = 0, max = 10, value = c(2.5, 7.5), step = 0.1)
))

),
Expand All @@ -76,7 +76,11 @@ shinyUI(fluidPage(
selectInput("select", label = h3("Select box"), choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3), selected = 1)
)),

column(8, wellPanel(
column(4, wellPanel(
selectInput("multiselect", label = h3("Multiple select"), choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3), selected = 1, multiple = TRUE)
)),

column(4, wellPanel(
shinyURL.ui(),
style = "background-color:#428bca; color:#fff;"
))
Expand Down

0 comments on commit 77ca91a

Please sign in to comment.