From 00276abfa73bc7b4ef740ca2fccac8cb09dd6dd2 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Thu, 29 Dec 2016 16:31:03 +0000 Subject: [PATCH 01/22] working but ugly --- R/input-radiobuttons.R | 60 ++++++++++++++++++++++++++++++++++-------- R/input-utils.R | 49 +++++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index 72407f443b..b978953cd0 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -11,11 +11,15 @@ #' #' @inheritParams textInput #' @param choices List of values to select from (if elements of the list are -#' named then that name rather than the value is displayed to the user) +#' named then that name rather than the value is displayed to the user). If +#' this argument is provided, then \code{choiceNames} and \code{choiceValues} +#' must not be provided, and vice-versa. #' @param selected The initially selected value (if not specified then -#' defaults to the first value) +#' defaults to the first value) #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally) #' @return A set of radio buttons that can be added to a UI definition. +#' @param choicesValues +#' @param choicesNames List #' #' @family input elements #' @seealso \code{\link{updateRadioButtons}} @@ -49,25 +53,59 @@ #' shinyApp(ui, server) #' } #' @export -radioButtons <- function(inputId, label, choices, selected = NULL, - inline = FALSE, width = NULL) { +radioButtons <- function(inputId, label, choices = NULL, selected = NULL, + inline = FALSE, width = NULL, choicesValues = NULL, choicesNames = NULL) { - # resolve names - choices <- choicesWithNames(choices) + lenNames <- length(choicesNames) + lenVals <- length(choicesValues) + useChoices <- FALSE + + if (is.null(choices)) { + if (lenNames == 0 || lenVals == 0) { + stop("Please specify a non-empty vector for `choices` (or, + alternatively, for both `choicesNames` and `choicesValues`).") + } + if (lenNames != lenVals) { + stop("`choicesNames` and `choicesValues` must have the same length.") + } + if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { + stop("`choicesNames` and `choicesValues` must not be named.") + } + } else { + if (lenNames != 0 || lenVals != 0) { + warning("Using `choices` argument; ignoring `choicesNames` and + `choicesValues`.") + } + # resolve names + useChoices <- TRUE + choices <- choicesWithNames(choices) + } selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified - selected <- if (is.null(selected)) choices[[1]] else { - validateSelected(selected, choices, inputId) + selected <- if (is.null(selected)) { + if (useChoices) choices[[1]] else choicesValues[[1]] + } else { + if (useChoices) validateSelected(selected, choices, inputId) + else validateSelected2(selected, choicesNames, choicesValues, inputId) } + if (length(selected) > 1) stop("The 'selected' argument must be of length 1") - options <- generateOptions(inputId, choices, selected, inline, type = 'radio') + #print(choicesNames) + #print(choicesValues) + + options <- if (useChoices) + generateOptions(inputId, choices, selected, inline, type = 'radio') + else + generateOptions(inputId, NULL, selected, inline, type = 'radio', + choicesNames, choicesValues) + + #print(options) divClass <- "form-group shiny-input-radiogroup shiny-input-container" - if (inline) - divClass <- paste(divClass, "shiny-input-container-inline") + if (inline) divClass <- paste(divClass, "shiny-input-container-inline") tags$div(id = inputId, style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"), diff --git a/R/input-utils.R b/R/input-utils.R index 65eb70bd1f..81dd8a906d 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -35,12 +35,49 @@ validateSelected <- function(selected, choices, inputId) { } +validateSelected2 <- function(selected, choicesValues, choicesNames, inputId) { + selected <- as.character(selected) + if (needOptgroup(choicesValues)) return(selected) + + if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) + if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) + + # labels and values are identical, no need to validate + if (identical(choicesNames, choicesValues)) return(selected) + # when selected labels instead of values + i <- (selected %in% choicesNames) & !(selected %in% choicesValues) + if (any(i)) { + warnFun <- if (all(i)) { + # replace names with values + selected <- choicesValues[[which(choicesNames == selected)]] + warning + } else stop # stop when it is ambiguous (some labels == values) + warnFun("'selected' must be the values instead of names of 'choices' ", + "for the input '", inputId, "'") + } + selected +} + + # generate options for radio buttons and checkbox groups (type = 'checkbox' or # 'radio') -generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox') { +generateOptions <- function(inputId, choices = NULL, selected, inline, + type = 'checkbox', choicesNames = NULL, + choicesValues = NULL) { + + session <- getDefaultReactiveDomain() + + if (is.null(choices)) { + nms <- choicesNames + vals <- choicesValues + } else { + nms <- names(choices) + vals <- choices + } + # generate a list of options <- mapply( - choices, names(choices), + vals, nms, FUN = function(value, name) { inputTag <- tags$input( type = type, name = inputId, value = value @@ -50,12 +87,12 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox # If inline, there's no wrapper div, and the label needs a class like # checkbox-inline. + nm <- processDeps(name, session)$html + if (inline) { - tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name)) + tags$label(class = paste0(type, "-inline"), inputTag, tags$span(nm)) } else { - tags$div(class = type, - tags$label(inputTag, tags$span(name)) - ) + tags$div(class = type, tags$label(inputTag, tags$span(nm))) } }, SIMPLIFY = FALSE, USE.NAMES = FALSE From 2c44437e73326cb7e118c0918a0faba97a4f4b15 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Fri, 30 Dec 2016 23:48:48 +0000 Subject: [PATCH 02/22] handle dependencies --- R/input-utils.R | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/R/input-utils.R b/R/input-utils.R index 81dd8a906d..ebe8807af9 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -85,14 +85,16 @@ generateOptions <- function(inputId, choices = NULL, selected, inline, if (value %in% selected) inputTag$attribs$checked <- "checked" + pd <- processDeps(name, session) + # If inline, there's no wrapper div, and the label needs a class like # checkbox-inline. - nm <- processDeps(name, session)$html - if (inline) { - tags$label(class = paste0(type, "-inline"), inputTag, tags$span(nm)) + tags$label(class = paste0(type, "-inline"), inputTag, + tagList(tags$span(pd$html), pd$dep)) } else { - tags$div(class = type, tags$label(inputTag, tags$span(nm))) + tags$div(class = type, tags$label(inputTag, + tagList(tags$span(pd$html), pd$dep))) } }, SIMPLIFY = FALSE, USE.NAMES = FALSE From 9571285f017b7b30b555528e29a705ceb30986ed Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sat, 31 Dec 2016 00:31:42 +0000 Subject: [PATCH 03/22] better --- R/input-radiobuttons.R | 21 ++------ R/input-utils.R | 112 +++++++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 58 deletions(-) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index b978953cd0..6364a1ca8e 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -76,7 +76,7 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, warning("Using `choices` argument; ignoring `choicesNames` and `choicesValues`.") } - # resolve names + # resolve names if not specified useChoices <- TRUE choices <- choicesWithNames(choices) } @@ -84,25 +84,14 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified - selected <- if (is.null(selected)) { - if (useChoices) choices[[1]] else choicesValues[[1]] - } else { - if (useChoices) validateSelected(selected, choices, inputId) - else validateSelected2(selected, choicesNames, choicesValues, inputId) + selected <- if (is.null(selected)) choices[[1]] %OR% choicesValues[[1]] else { + validateSelected(selected, choices, inputId, choicesNames, choicesValues) } if (length(selected) > 1) stop("The 'selected' argument must be of length 1") - #print(choicesNames) - #print(choicesValues) - - options <- if (useChoices) - generateOptions(inputId, choices, selected, inline, type = 'radio') - else - generateOptions(inputId, NULL, selected, inline, type = 'radio', - choicesNames, choicesValues) - - #print(options) + options <- generateOptions(inputId, choices, selected, inline, type = 'radio', + choicesNames, choicesValues) divClass <- "form-group shiny-input-radiogroup shiny-input-container" if (inline) divClass <- paste(divClass, "shiny-input-container-inline") diff --git a/R/input-utils.R b/R/input-utils.R index ebe8807af9..36924ff09f 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -3,53 +3,86 @@ controlLabel <- function(controlName, label) { } -# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it -# refers to values. Below is a function for backward compatibility. It also -# coerces the value to `character`. -validateSelected <- function(selected, choices, inputId) { +# # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it +# # refers to values. Below is a function for backward compatibility. It also +# # coerces the value to `character`. +# validateSelected <- function(selected, choices, inputId) { +# # this line accomplishes two tings: +# # - coerces selected to character +# # - drops name, otherwise toJSON() keeps it too +# selected <- as.character(selected) +# # if you are using optgroups, you're using shiny > 0.10.0, and you should +# # already know that `selected` must be a value instead of a label +# if (needOptgroup(choices)) return(selected) +# +# if (is.list(choices)) choices <- unlist(choices) +# +# nms <- names(choices) +# # labels and values are identical, no need to validate +# if (identical(nms, unname(choices))) return(selected) +# # when selected labels instead of values +# i <- (selected %in% nms) & !(selected %in% choices) +# if (any(i)) { +# warnFun <- if (all(i)) { +# # replace names with values +# selected <- unname(choices[selected]) +# warning +# } else stop # stop when it is ambiguous (some labels == values) +# warnFun("'selected' must be the values instead of names of 'choices' ", +# "for the input '", inputId, "'") +# } +# selected +# } +# +# +# validateSelected2 <- function(selected, choicesValues, choicesNames, inputId) { +# selected <- as.character(selected) +# if (needOptgroup(choicesValues)) return(selected) +# +# if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) +# if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) +# +# # labels and values are identical, no need to validate +# if (identical(choicesNames, choicesValues)) return(selected) +# # when selected labels instead of values +# i <- (selected %in% choicesNames) & !(selected %in% choicesValues) +# if (any(i)) { +# warnFun <- if (all(i)) { +# # replace names with values +# selected <- choicesValues[[which(choicesNames == selected)]] +# warning +# } else stop # stop when it is ambiguous (some labels == values) +# warnFun("'selected' must be the values instead of names of 'choices' ", +# "for the input '", inputId, "'") +# } +# selected +# } + + +validateSelected <- function(selected, choices, inputId, choicesNames, choicesValues) { # this line accomplishes two tings: # - coerces selected to character # - drops name, otherwise toJSON() keeps it too selected <- as.character(selected) + # if you are using optgroups, you're using shiny > 0.10.0, and you should # already know that `selected` must be a value instead of a label - if (needOptgroup(choices)) return(selected) + if (needOptgroup(choices %OR% choicesValues)) return(selected) if (is.list(choices)) choices <- unlist(choices) - - nms <- names(choices) - # labels and values are identical, no need to validate - if (identical(nms, unname(choices))) return(selected) - # when selected labels instead of values - i <- (selected %in% nms) & !(selected %in% choices) - if (any(i)) { - warnFun <- if (all(i)) { - # replace names with values - selected <- unname(choices[selected]) - warning - } else stop # stop when it is ambiguous (some labels == values) - warnFun("'selected' must be the values instead of names of 'choices' ", - "for the input '", inputId, "'") - } - selected -} - - -validateSelected2 <- function(selected, choicesValues, choicesNames, inputId) { - selected <- as.character(selected) - if (needOptgroup(choicesValues)) return(selected) - if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) + nms <- names(choices) %OR% choicesNames + # labels and values are identical, no need to validate - if (identical(choicesNames, choicesValues)) return(selected) + if (identical(nms, unname(choices) %OR% choicesValues)) return(selected) # when selected labels instead of values - i <- (selected %in% choicesNames) & !(selected %in% choicesValues) + i <- (selected %in% nms) & !(selected %in% (choices %OR% choicesValues)) if (any(i)) { warnFun <- if (all(i)) { # replace names with values - selected <- choicesValues[[which(choicesNames == selected)]] + selected <- unname(choices[selected]) %OR% choicesValues[[which(choicesNames == selected)]] warning } else stop # stop when it is ambiguous (some labels == values) warnFun("'selected' must be the values instead of names of 'choices' ", @@ -58,22 +91,15 @@ validateSelected2 <- function(selected, choicesValues, choicesNames, inputId) { selected } - # generate options for radio buttons and checkbox groups (type = 'checkbox' or # 'radio') generateOptions <- function(inputId, choices = NULL, selected, inline, type = 'checkbox', choicesNames = NULL, - choicesValues = NULL) { + choicesValues = NULL, + session = getDefaultReactiveDomain()) { - session <- getDefaultReactiveDomain() - - if (is.null(choices)) { - nms <- choicesNames - vals <- choicesValues - } else { - nms <- names(choices) - vals <- choices - } + nms <- names(choices) %OR% choicesNames + vals <- choices %OR% choicesValues # generate a list of options <- mapply( @@ -85,6 +111,8 @@ generateOptions <- function(inputId, choices = NULL, selected, inline, if (value %in% selected) inputTag$attribs$checked <- "checked" + # in case, the options include UI code other than text + # (arbitrary HTML using the tags() function or equivalent) pd <- processDeps(name, session) # If inline, there's no wrapper div, and the label needs a class like From 4ea3990b0d40589dba4bc0cb97fb724e13952dd9 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sat, 31 Dec 2016 02:24:18 +0000 Subject: [PATCH 04/22] added documentation and example --- R/input-radiobuttons.R | 36 +++++++++++++++++++++---- R/input-utils.R | 55 +-------------------------------------- man/radioButtons.Rd | 40 +++++++++++++++++++++++++--- man/updateRadioButtons.Rd | 4 ++- 4 files changed, 72 insertions(+), 63 deletions(-) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index 6364a1ca8e..73f085e598 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -18,8 +18,15 @@ #' defaults to the first value) #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally) #' @return A set of radio buttons that can be added to a UI definition. -#' @param choicesValues -#' @param choicesNames List +#' @param choicesNames,choicesValues List of names and values, respectively, +#' that are displayed to the user in the app and correspond to the each +#' choice (for this reason, \code{choicesNames} and \code{choicesValues} +#' must have the same length). If either of these arguments is +#' provided, then the other \emph{must} be provided and \code{choices} +#' \emph{must not} be provided. The advantage of using both of these over +#' a named list for \code{choices} is that \code{choicesNames} allows any +#' type of UI object to be passed through (tag objects, icons, HTML code, +#' ...), instead of just simple text. See Examples. #' #' @family input elements #' @seealso \code{\link{updateRadioButtons}} @@ -51,14 +58,34 @@ #' } #' #' shinyApp(ui, server) +#' +#' ui <- fluidPage( +#' radioButtons("rb", "Choose one:", +#' choicesNames = list( +#' icon("calendar"), +#' HTML("

Red Text

"), +#' "Normal text" +#' ), +#' choicesValues = list( +#' "icon", "html", "text" +#' )), +#' textOutput("txt") +#' ) +#' +#' server <- function(input, output) { +#' output$txt <- renderText({ +#' paste("You chose", input$rb) +#' }) +#' } +#' +#' shinyApp(ui, server) #' } #' @export radioButtons <- function(inputId, label, choices = NULL, selected = NULL, - inline = FALSE, width = NULL, choicesValues = NULL, choicesNames = NULL) { + inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { lenNames <- length(choicesNames) lenVals <- length(choicesValues) - useChoices <- FALSE if (is.null(choices)) { if (lenNames == 0 || lenVals == 0) { @@ -77,7 +104,6 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, `choicesValues`.") } # resolve names if not specified - useChoices <- TRUE choices <- choicesWithNames(choices) } diff --git a/R/input-utils.R b/R/input-utils.R index 36924ff09f..51e49f3728 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -6,59 +6,6 @@ controlLabel <- function(controlName, label) { # # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it # # refers to values. Below is a function for backward compatibility. It also # # coerces the value to `character`. -# validateSelected <- function(selected, choices, inputId) { -# # this line accomplishes two tings: -# # - coerces selected to character -# # - drops name, otherwise toJSON() keeps it too -# selected <- as.character(selected) -# # if you are using optgroups, you're using shiny > 0.10.0, and you should -# # already know that `selected` must be a value instead of a label -# if (needOptgroup(choices)) return(selected) -# -# if (is.list(choices)) choices <- unlist(choices) -# -# nms <- names(choices) -# # labels and values are identical, no need to validate -# if (identical(nms, unname(choices))) return(selected) -# # when selected labels instead of values -# i <- (selected %in% nms) & !(selected %in% choices) -# if (any(i)) { -# warnFun <- if (all(i)) { -# # replace names with values -# selected <- unname(choices[selected]) -# warning -# } else stop # stop when it is ambiguous (some labels == values) -# warnFun("'selected' must be the values instead of names of 'choices' ", -# "for the input '", inputId, "'") -# } -# selected -# } -# -# -# validateSelected2 <- function(selected, choicesValues, choicesNames, inputId) { -# selected <- as.character(selected) -# if (needOptgroup(choicesValues)) return(selected) -# -# if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) -# if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) -# -# # labels and values are identical, no need to validate -# if (identical(choicesNames, choicesValues)) return(selected) -# # when selected labels instead of values -# i <- (selected %in% choicesNames) & !(selected %in% choicesValues) -# if (any(i)) { -# warnFun <- if (all(i)) { -# # replace names with values -# selected <- choicesValues[[which(choicesNames == selected)]] -# warning -# } else stop # stop when it is ambiguous (some labels == values) -# warnFun("'selected' must be the values instead of names of 'choices' ", -# "for the input '", inputId, "'") -# } -# selected -# } - - validateSelected <- function(selected, choices, inputId, choicesNames, choicesValues) { # this line accomplishes two tings: # - coerces selected to character @@ -70,8 +17,8 @@ validateSelected <- function(selected, choices, inputId, choicesNames, choicesVa if (needOptgroup(choices %OR% choicesValues)) return(selected) if (is.list(choices)) choices <- unlist(choices) - if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) + if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) nms <- names(choices) %OR% choicesNames diff --git a/man/radioButtons.Rd b/man/radioButtons.Rd index affafbf467..0d6274c0eb 100644 --- a/man/radioButtons.Rd +++ b/man/radioButtons.Rd @@ -4,8 +4,9 @@ \alias{radioButtons} \title{Create radio buttons} \usage{ -radioButtons(inputId, label, choices, selected = NULL, inline = FALSE, - width = NULL) +radioButtons(inputId, label, choices = NULL, selected = NULL, + inline = FALSE, width = NULL, choicesNames = NULL, + choicesValues = NULL) } \arguments{ \item{inputId}{The \code{input} slot that will be used to access the value.} @@ -13,7 +14,9 @@ radioButtons(inputId, label, choices, selected = NULL, inline = FALSE, \item{label}{Display label for the control, or \code{NULL} for no label.} \item{choices}{List of values to select from (if elements of the list are -named then that name rather than the value is displayed to the user)} +named then that name rather than the value is displayed to the user). If +this argument is provided, then \code{choiceNames} and \code{choiceValues} +must not be provided, and vice-versa.} \item{selected}{The initially selected value (if not specified then defaults to the first value)} @@ -22,6 +25,16 @@ defaults to the first value)} \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'}; see \code{\link{validateCssUnit}}.} + +\item{choicesNames, choicesValues}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choicesNames} and \code{choicesValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. The advantage of using both of these over +a named list for \code{choices} is that \code{choicesNames} allows any +type of UI object to be passed through (tag objects, icons, HTML code, +...), instead of just simple text. See Examples.} } \value{ A set of radio buttons that can be added to a UI definition. @@ -63,6 +76,27 @@ server <- function(input, output) { }) } +shinyApp(ui, server) + +ui <- fluidPage( + radioButtons("rb", "Choose one:", + choicesNames = list( + icon("calendar"), + HTML("

Red Text

"), + "Normal text" + ), + choicesValues = list( + "icon", "html", "text" + )), + textOutput("txt") +) + +server <- function(input, output) { + output$txt <- renderText({ + paste("You chose", input$rb) + }) +} + shinyApp(ui, server) } } diff --git a/man/updateRadioButtons.Rd b/man/updateRadioButtons.Rd index 222f74a578..1c1edabbe6 100644 --- a/man/updateRadioButtons.Rd +++ b/man/updateRadioButtons.Rd @@ -16,7 +16,9 @@ updateRadioButtons(session, inputId, label = NULL, choices = NULL, \item{label}{The label to set for the input object.} \item{choices}{List of values to select from (if elements of the list are -named then that name rather than the value is displayed to the user)} +named then that name rather than the value is displayed to the user). If +this argument is provided, then \code{choiceNames} and \code{choiceValues} +must not be provided, and vice-versa.} \item{selected}{The initially selected value (if not specified then defaults to the first value)} From cc67907fc7bcb3d897cdaff34bda6520adcf99f0 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sat, 31 Dec 2016 02:25:51 +0000 Subject: [PATCH 05/22] uncommenting --- R/input-utils.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/input-utils.R b/R/input-utils.R index 51e49f3728..6aa9015331 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -3,9 +3,9 @@ controlLabel <- function(controlName, label) { } -# # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it -# # refers to values. Below is a function for backward compatibility. It also -# # coerces the value to `character`. +# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it +# refers to values. Below is a function for backward compatibility. It also +# coerces the value to `character`. validateSelected <- function(selected, choices, inputId, choicesNames, choicesValues) { # this line accomplishes two tings: # - coerces selected to character From b6bbd11c8dec20a7d5b063196938994f6a1d34b3 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sat, 31 Dec 2016 03:05:51 +0000 Subject: [PATCH 06/22] fix tests --- R/input-radiobuttons.R | 2 ++ R/input-utils.R | 4 ++-- R/update-input.R | 33 +++++++++++++++++++++++++++------ tests/testthat/test-bootstrap.r | 6 +++--- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index 73f085e598..fc015e1759 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -102,6 +102,8 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, if (lenNames != 0 || lenVals != 0) { warning("Using `choices` argument; ignoring `choicesNames` and `choicesValues`.") + choicesNames = NULL + choicesValues = NULL } # resolve names if not specified choices <- choicesWithNames(choices) diff --git a/R/input-utils.R b/R/input-utils.R index 6aa9015331..a6896f1811 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -66,10 +66,10 @@ generateOptions <- function(inputId, choices = NULL, selected, inline, # checkbox-inline. if (inline) { tags$label(class = paste0(type, "-inline"), inputTag, - tagList(tags$span(pd$html), pd$dep)) + tags$span(pd$html, pd$dep)) } else { tags$div(class = type, tags$label(inputTag, - tagList(tags$span(pd$html), pd$dep))) + tags$span(pd$html, pd$dep))) } }, SIMPLIFY = FALSE, USE.NAMES = FALSE diff --git a/R/update-input.R b/R/update-input.R index fa58f683b3..1851894d71 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -452,16 +452,37 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL, updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE, - type = 'checkbox') { - if (!is.null(choices)) + selected = NULL, inline = FALSE, type = 'checkbox', + choicesNames = NULL, choicesValues = NULL) { + lenNames <- length(choicesNames) + lenVals <- length(choicesValues) + + if (lenNames != 0 || lenVals != 0) { + if (lenNames != lenVals) { + stop("`choicesNames` and `choicesValues` must have the same length.") + } + if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { + stop("`choicesNames` and `choicesValues` must not be named.") + } + } + if (!is.null(choices)) { + if (lenNames != 0 || lenVals != 0) { + warning("Using `choices` argument; ignoring `choicesNames` and + `choicesValues`.") + choicesNames = NULL + choicesValues = NULL + } choices <- choicesWithNames(choices) + } + if (!is.null(selected)) - selected <- validateSelected(selected, choices, session$ns(inputId)) + selected <- validateSelected(selected, choices, session$ns(inputId), + choicesNames, choicesValues) - options <- if (!is.null(choices)) { + options <- if (!is.null(choices) || !is.null(choicesValues)) { format(tagList( - generateOptions(session$ns(inputId), choices, selected, inline, type = type) + generateOptions(session$ns(inputId), choices, selected, inline, type = type, + choicesNames, choicesValues) )) } diff --git a/tests/testthat/test-bootstrap.r b/tests/testthat/test-bootstrap.r index 29c9201e63..ea8a37fa3e 100644 --- a/tests/testthat/test-bootstrap.r +++ b/tests/testthat/test-bootstrap.r @@ -33,15 +33,15 @@ test_that("Repeated names for selectInput and radioButtons choices", { x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3')) choices <- x$children - expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], 'a') + expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], HTML('a')) expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'x1') expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked') - expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], 'a') + expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], HTML('a')) expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'x2') expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL) - expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], 'b') + expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], HTML('b')) expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'x3') expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL) }) From 35136b9ab6c7c4b366aeade59f45df07a76a70ae Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sun, 1 Jan 2017 21:27:19 +0000 Subject: [PATCH 07/22] update funs --- R/input-checkboxgroup.R | 75 ++++++++++++++++++++++++++++++--- R/input-radiobuttons.R | 4 +- R/update-input.R | 21 +++++---- man/checkboxGroupInput.Rd | 42 +++++++++++++++--- man/updateCheckboxGroupInput.Rd | 18 ++++++-- man/updateRadioButtons.Rd | 23 +++++++++- 6 files changed, 156 insertions(+), 27 deletions(-) diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R index 24664729ce..93caa36bec 100644 --- a/R/input-checkboxgroup.R +++ b/R/input-checkboxgroup.R @@ -6,9 +6,21 @@ #' #' @inheritParams textInput #' @param choices List of values to show checkboxes for. If elements of the list -#' are named then that name rather than the value is displayed to the user. +#' are named then that name rather than the value is displayed to the user. If +#' this argument is provided, then \code{choiceNames} and \code{choiceValues} +#' must not be provided, and vice-versa. #' @param selected The values that should be initially selected, if any. #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally) +#' @param choicesNames,choicesValues List of names and values, respectively, +#' that are displayed to the user in the app and correspond to the each +#' choice (for this reason, \code{choicesNames} and \code{choicesValues} +#' must have the same length). If either of these arguments is +#' provided, then the other \emph{must} be provided and \code{choices} +#' \emph{must not} be provided. The advantage of using both of these over +#' a named list for \code{choices} is that \code{choicesNames} allows any +#' type of UI object to be passed through (tag objects, icons, HTML code, +#' ...), instead of just simple text. See Examples. +#' #' @return A list of HTML elements that can be added to a UI definition. #' #' @family input elements @@ -26,26 +38,75 @@ #' tableOutput("data") #' ) #' -#' server <- function(input, output) { +#' server <- function(input, output, session) { #' output$data <- renderTable({ #' mtcars[, c("mpg", input$variable), drop = FALSE] #' }, rownames = TRUE) #' } #' #' shinyApp(ui, server) +#' +#' ui <- fluidPage( +#' checkboxGroupInput("icons", "Choose icons:", +#' choicesNames = +#' list(icon("calendar"), icon("bed"), +#' icon("cog"), icon("bug")), +#' choicesValues = +#' list("calendar", "bed", "cog", "bug") +#' ), +#' textOutput("txt") +#' ) +#' +#' server <- function(input, output, session) { +#' output$txt <- renderText({ +#' icons <- paste(input$icons, collapse = ", ") +#' paste("You chose", icons) +#' }) +#' } +#' +#' shinyApp(ui, server) #' } #' @export -checkboxGroupInput <- function(inputId, label, choices, selected = NULL, - inline = FALSE, width = NULL) { +checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL, + inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { + + lenNames <- length(choicesNames) + lenVals <- length(choicesValues) + + if (is.null(choices)) { + if (lenNames == 0 || lenVals == 0) { + stop("Please specify a non-empty vector for `choices` (or, + alternatively, for both `choicesNames` and `choicesValues`).") + } + if (lenNames != lenVals) { + stop("`choicesNames` and `choicesValues` must have the same length.") + } + if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { + stop("`choicesNames` and `choicesValues` must not be named.") + } + } else { + if (lenNames != 0 || lenVals != 0) { + warning("Using `choices` argument; ignoring `choicesNames` and + `choicesValues`.") + choicesNames = NULL + choicesValues = NULL + } + # resolve names if not specified + choices <- choicesWithNames(choices) + } selected <- restoreInput(id = inputId, default = selected) - # resolve names - choices <- choicesWithNames(choices) + # default value if it's not specified + if (!is.null(selected)) + selected <- validateSelected(selected, choices, inputId, + choicesNames, choicesValues) + if (!is.null(selected)) selected <- validateSelected(selected, choices, inputId) - options <- generateOptions(inputId, choices, selected, inline) + options <- generateOptions(inputId, choices, selected, inline, + 'checkbox', choicesNames, choicesValues) divClass <- "form-group shiny-input-checkboxgroup shiny-input-container" if (inline) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index fc015e1759..a3d8e228a1 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -118,8 +118,8 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, if (length(selected) > 1) stop("The 'selected' argument must be of length 1") - options <- generateOptions(inputId, choices, selected, inline, type = 'radio', - choicesNames, choicesValues) + options <- generateOptions(inputId, choices, selected, inline, + 'radio', choicesNames, choicesValues) divClass <- "form-group shiny-input-radiogroup shiny-input-container" if (inline) divClass <- paste(divClass, "shiny-input-container-inline") diff --git a/R/update-input.R b/R/update-input.R index 1851894d71..32156897b4 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -452,7 +452,7 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL, updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE, type = 'checkbox', + selected = NULL, inline = FALSE, type = NULL, choicesNames = NULL, choicesValues = NULL) { lenNames <- length(choicesNames) lenVals <- length(choicesValues) @@ -464,7 +464,7 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { stop("`choicesNames` and `choicesValues` must not be named.") } - } + } if (!is.null(choices)) { if (lenNames != 0 || lenVals != 0) { warning("Using `choices` argument; ignoring `choicesNames` and @@ -481,8 +481,8 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, options <- if (!is.null(choices) || !is.null(choicesValues)) { format(tagList( - generateOptions(session$ns(inputId), choices, selected, inline, type = type, - choicesNames, choicesValues) + generateOptions(session$ns(inputId), choices, selected, inline, + type, choicesNames, choicesValues) )) } @@ -531,9 +531,10 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, #' } #' @export updateCheckboxGroupInput <- function(session, inputId, label = NULL, - choices = NULL, selected = NULL, - inline = FALSE) { - updateInputOptions(session, inputId, label, choices, selected, inline) + choices = NULL, selected = NULL, inline = FALSE, + choicesNames = NULL, choicesValues = NULL) { + updateInputOptions(session, inputId, label, choices, selected, + inline, "checkbox", choicesNames, choicesValues) } @@ -573,10 +574,12 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL, #' } #' @export updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE) { + selected = NULL, inline = FALSE, + choicesNames = NULL, choicesValues = NULL) { # you must select at least one radio button if (is.null(selected) && !is.null(choices)) selected <- choices[[1]] - updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio') + updateInputOptions(session, inputId, label, choices, selected, + inline, 'radio', choicesNames, choicesValues) } diff --git a/man/checkboxGroupInput.Rd b/man/checkboxGroupInput.Rd index 624344b41d..1fab891934 100644 --- a/man/checkboxGroupInput.Rd +++ b/man/checkboxGroupInput.Rd @@ -4,8 +4,9 @@ \alias{checkboxGroupInput} \title{Checkbox Group Input Control} \usage{ -checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE, - width = NULL) +checkboxGroupInput(inputId, label, choices = NULL, selected = NULL, + inline = FALSE, width = NULL, choicesNames = NULL, + choicesValues = NULL) } \arguments{ \item{inputId}{The \code{input} slot that will be used to access the value.} @@ -13,11 +14,22 @@ checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE, \item{label}{Display label for the control, or \code{NULL} for no label.} \item{choices}{List of values to show checkboxes for. If elements of the list -are named then that name rather than the value is displayed to the user.} +are named then that name rather than the value is displayed to the user. If +this argument is provided, then \code{choiceNames} and \code{choiceValues} +must not be provided, and vice-versa.} \item{selected}{The values that should be initially selected, if any.} -\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} +\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally) +@param choicesNames,choicesValues List of names and values, respectively, + that are displayed to the user in the app and correspond to the each + choice (for this reason, \code{choicesNames} and \code{choicesValues} + must have the same length). If either of these arguments is + provided, then the other \emph{must} be provided and \code{choices} + \emph{must not} be provided. The advantage of using both of these over + a named list for \code{choices} is that \code{choicesNames} allows any + type of UI object to be passed through (tag objects, icons, HTML code, + ...), instead of just simple text. See Examples.} \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'}; see \code{\link{validateCssUnit}}.} @@ -42,12 +54,32 @@ ui <- fluidPage( tableOutput("data") ) -server <- function(input, output) { +server <- function(input, output, session) { output$data <- renderTable({ mtcars[, c("mpg", input$variable), drop = FALSE] }, rownames = TRUE) } +shinyApp(ui, server) + +ui <- fluidPage( + checkboxGroupInput("icons", "Choose icons:", + choicesNames = + list(icon("calendar"), icon("bed"), + icon("cog"), icon("bug")), + choicesValues = + list("calendar", "bed", "cog", "bug") + ), + textOutput("txt") +) + +server <- function(input, output, session) { + output$txt <- renderText({ + icons <- paste(input$icons, collapse = ", ") + paste("You chose", icons) + }) +} + shinyApp(ui, server) } } diff --git a/man/updateCheckboxGroupInput.Rd b/man/updateCheckboxGroupInput.Rd index b1efc47065..0d357f90f4 100644 --- a/man/updateCheckboxGroupInput.Rd +++ b/man/updateCheckboxGroupInput.Rd @@ -5,7 +5,8 @@ \title{Change the value of a checkbox group input on the client} \usage{ updateCheckboxGroupInput(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE) + selected = NULL, inline = FALSE, choicesNames = NULL, + choicesValues = NULL) } \arguments{ \item{session}{The \code{session} object passed to function given to @@ -16,11 +17,22 @@ updateCheckboxGroupInput(session, inputId, label = NULL, choices = NULL, \item{label}{The label to set for the input object.} \item{choices}{List of values to show checkboxes for. If elements of the list -are named then that name rather than the value is displayed to the user.} +are named then that name rather than the value is displayed to the user. If +this argument is provided, then \code{choiceNames} and \code{choiceValues} +must not be provided, and vice-versa.} \item{selected}{The values that should be initially selected, if any.} -\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} +\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally) +@param choicesNames,choicesValues List of names and values, respectively, + that are displayed to the user in the app and correspond to the each + choice (for this reason, \code{choicesNames} and \code{choicesValues} + must have the same length). If either of these arguments is + provided, then the other \emph{must} be provided and \code{choices} + \emph{must not} be provided. The advantage of using both of these over + a named list for \code{choices} is that \code{choicesNames} allows any + type of UI object to be passed through (tag objects, icons, HTML code, + ...), instead of just simple text. See Examples.} } \description{ Change the value of a checkbox group input on the client diff --git a/man/updateRadioButtons.Rd b/man/updateRadioButtons.Rd index 1c1edabbe6..6ef23c8c12 100644 --- a/man/updateRadioButtons.Rd +++ b/man/updateRadioButtons.Rd @@ -5,7 +5,8 @@ \title{Change the value of a radio input on the client} \usage{ updateRadioButtons(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE) + selected = NULL, inline = FALSE, choicesNames = NULL, + choicesValues = NULL) } \arguments{ \item{session}{The \code{session} object passed to function given to @@ -24,6 +25,26 @@ must not be provided, and vice-versa.} defaults to the first value)} \item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} + +\item{choicesNames}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choicesNames} and \code{choicesValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. The advantage of using both of these over +a named list for \code{choices} is that \code{choicesNames} allows any +type of UI object to be passed through (tag objects, icons, HTML code, +...), instead of just simple text. See Examples.} + +\item{choicesValues}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choicesNames} and \code{choicesValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. The advantage of using both of these over +a named list for \code{choices} is that \code{choicesNames} allows any +type of UI object to be passed through (tag objects, icons, HTML code, +...), instead of just simple text. See Examples.} } \description{ Change the value of a radio input on the client From f982be242cad49c359b6f0479b738343b0089797 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sun, 1 Jan 2017 21:31:45 +0000 Subject: [PATCH 08/22] fix docs --- R/input-checkboxgroup.R | 2 +- man/checkboxGroupInput.Rd | 21 +++++++++++---------- man/updateCheckboxGroupInput.Rd | 31 +++++++++++++++++++++---------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R index 93caa36bec..6990205be4 100644 --- a/R/input-checkboxgroup.R +++ b/R/input-checkboxgroup.R @@ -11,7 +11,7 @@ #' must not be provided, and vice-versa. #' @param selected The values that should be initially selected, if any. #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally) -#' @param choicesNames,choicesValues List of names and values, respectively, +#' @param choicesNames,choicesValues List of names and values, respectively, #' that are displayed to the user in the app and correspond to the each #' choice (for this reason, \code{choicesNames} and \code{choicesValues} #' must have the same length). If either of these arguments is diff --git a/man/checkboxGroupInput.Rd b/man/checkboxGroupInput.Rd index 1fab891934..66bdefdbd5 100644 --- a/man/checkboxGroupInput.Rd +++ b/man/checkboxGroupInput.Rd @@ -20,19 +20,20 @@ must not be provided, and vice-versa.} \item{selected}{The values that should be initially selected, if any.} -\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally) -@param choicesNames,choicesValues List of names and values, respectively, - that are displayed to the user in the app and correspond to the each - choice (for this reason, \code{choicesNames} and \code{choicesValues} - must have the same length). If either of these arguments is - provided, then the other \emph{must} be provided and \code{choices} - \emph{must not} be provided. The advantage of using both of these over - a named list for \code{choices} is that \code{choicesNames} allows any - type of UI object to be passed through (tag objects, icons, HTML code, - ...), instead of just simple text. See Examples.} +\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'}; see \code{\link{validateCssUnit}}.} + +\item{choicesNames, choicesValues}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choicesNames} and \code{choicesValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. The advantage of using both of these over +a named list for \code{choices} is that \code{choicesNames} allows any +type of UI object to be passed through (tag objects, icons, HTML code, +...), instead of just simple text. See Examples.} } \value{ A list of HTML elements that can be added to a UI definition. diff --git a/man/updateCheckboxGroupInput.Rd b/man/updateCheckboxGroupInput.Rd index 0d357f90f4..f49ea70690 100644 --- a/man/updateCheckboxGroupInput.Rd +++ b/man/updateCheckboxGroupInput.Rd @@ -23,16 +23,27 @@ must not be provided, and vice-versa.} \item{selected}{The values that should be initially selected, if any.} -\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally) -@param choicesNames,choicesValues List of names and values, respectively, - that are displayed to the user in the app and correspond to the each - choice (for this reason, \code{choicesNames} and \code{choicesValues} - must have the same length). If either of these arguments is - provided, then the other \emph{must} be provided and \code{choices} - \emph{must not} be provided. The advantage of using both of these over - a named list for \code{choices} is that \code{choicesNames} allows any - type of UI object to be passed through (tag objects, icons, HTML code, - ...), instead of just simple text. See Examples.} +\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} + +\item{choicesNames}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choicesNames} and \code{choicesValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. The advantage of using both of these over +a named list for \code{choices} is that \code{choicesNames} allows any +type of UI object to be passed through (tag objects, icons, HTML code, +...), instead of just simple text. See Examples.} + +\item{choicesValues}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choicesNames} and \code{choicesValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. The advantage of using both of these over +a named list for \code{choices} is that \code{choicesNames} allows any +type of UI object to be passed through (tag objects, icons, HTML code, +...), instead of just simple text. See Examples.} } \description{ Change the value of a checkbox group input on the client From 4055c6b8bfd752992fac3ccd0baa39080b7886c5 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sun, 1 Jan 2017 22:13:21 +0000 Subject: [PATCH 09/22] make checkChoicesArgs fun --- R/input-checkboxgroup.R | 28 ++++------------------------ R/input-radiobuttons.R | 28 ++++------------------------ R/input-utils.R | 29 +++++++++++++++++++++++++++++ R/update-input.R | 24 ++++-------------------- 4 files changed, 41 insertions(+), 68 deletions(-) diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R index 6990205be4..343e62977d 100644 --- a/R/input-checkboxgroup.R +++ b/R/input-checkboxgroup.R @@ -70,30 +70,10 @@ checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL, inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { - lenNames <- length(choicesNames) - lenVals <- length(choicesValues) - - if (is.null(choices)) { - if (lenNames == 0 || lenVals == 0) { - stop("Please specify a non-empty vector for `choices` (or, - alternatively, for both `choicesNames` and `choicesValues`).") - } - if (lenNames != lenVals) { - stop("`choicesNames` and `choicesValues` must have the same length.") - } - if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { - stop("`choicesNames` and `choicesValues` must not be named.") - } - } else { - if (lenNames != 0 || lenVals != 0) { - warning("Using `choices` argument; ignoring `choicesNames` and - `choicesValues`.") - choicesNames = NULL - choicesValues = NULL - } - # resolve names if not specified - choices <- choicesWithNames(choices) - } + args <- checkChoicesArgs(choices, choicesNames, choicesValues) + choices <- args$choices + choicesNames <- args$choicesNames + choicesValues <- args$choicesValues selected <- restoreInput(id = inputId, default = selected) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index a3d8e228a1..456040ab12 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -84,30 +84,10 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { - lenNames <- length(choicesNames) - lenVals <- length(choicesValues) - - if (is.null(choices)) { - if (lenNames == 0 || lenVals == 0) { - stop("Please specify a non-empty vector for `choices` (or, - alternatively, for both `choicesNames` and `choicesValues`).") - } - if (lenNames != lenVals) { - stop("`choicesNames` and `choicesValues` must have the same length.") - } - if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { - stop("`choicesNames` and `choicesValues` must not be named.") - } - } else { - if (lenNames != 0 || lenVals != 0) { - warning("Using `choices` argument; ignoring `choicesNames` and - `choicesValues`.") - choicesNames = NULL - choicesValues = NULL - } - # resolve names if not specified - choices <- choicesWithNames(choices) - } + args <- checkChoicesArgs(choices, choicesNames, choicesValues) + choices <- args$choices + choicesNames <- args$choicesNames + choicesValues <- args$choicesValues selected <- restoreInput(id = inputId, default = selected) diff --git a/R/input-utils.R b/R/input-utils.R index a6896f1811..7aaa50de9a 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -2,6 +2,35 @@ controlLabel <- function(controlName, label) { label %AND% tags$label(class = "control-label", `for` = controlName, label) } +checkChoicesArgs <- function(choices, choicesNames, choicesValues) { + + lenNames <- length(choicesNames) + lenVals <- length(choicesValues) + + if (is.null(choices)) { + if (lenNames == 0 || lenVals == 0) { + stop("Please specify a non-empty vector for `choices` (or, + alternatively, for both `choicesNames` and `choicesValues`).") + } + if (lenNames != lenVals) { + stop("`choicesNames` and `choicesValues` must have the same length.") + } + if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { + stop("`choicesNames` and `choicesValues` must not be named.") + } + } else { + if (lenNames != 0 || lenVals != 0) { + warning("Using `choices` argument; ignoring `choicesNames` and + `choicesValues`.") + choicesNames = NULL + choicesValues = NULL + } + # resolve names if not specified + choices <- choicesWithNames(choices) + } + + return(list(choices, choicesNames, choicesValues)) +} # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it # refers to values. Below is a function for backward compatibility. It also diff --git a/R/update-input.R b/R/update-input.R index 32156897b4..f2886f81df 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -454,26 +454,10 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL, updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, type = NULL, choicesNames = NULL, choicesValues = NULL) { - lenNames <- length(choicesNames) - lenVals <- length(choicesValues) - - if (lenNames != 0 || lenVals != 0) { - if (lenNames != lenVals) { - stop("`choicesNames` and `choicesValues` must have the same length.") - } - if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { - stop("`choicesNames` and `choicesValues` must not be named.") - } - } - if (!is.null(choices)) { - if (lenNames != 0 || lenVals != 0) { - warning("Using `choices` argument; ignoring `choicesNames` and - `choicesValues`.") - choicesNames = NULL - choicesValues = NULL - } - choices <- choicesWithNames(choices) - } + args <- checkChoicesArgs(choices, choicesNames, choicesValues) + choices <- args$choices + choicesNames <- args$choicesNames + choicesValues <- args$choicesValues if (!is.null(selected)) selected <- validateSelected(selected, choices, session$ns(inputId), From e909e3500c4329a4c2569e0918ad54375fe1c558 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Sun, 1 Jan 2017 22:23:50 +0000 Subject: [PATCH 10/22] name elements of return list --- R/input-utils.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/input-utils.R b/R/input-utils.R index 7aaa50de9a..ab8cef4725 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -29,7 +29,9 @@ checkChoicesArgs <- function(choices, choicesNames, choicesValues) { choices <- choicesWithNames(choices) } - return(list(choices, choicesNames, choicesValues)) + return(list(choices = choices, + choicesNames = choicesNames, + choicesValues = choicesValues)) } # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it From f14dd693d42f63269a00302f124a22abde2cee0d Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Tue, 21 Mar 2017 21:29:00 +0000 Subject: [PATCH 11/22] minor updates --- R/input-utils.R | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/R/input-utils.R b/R/input-utils.R index ab8cef4725..80d3dbfa06 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -3,35 +3,30 @@ controlLabel <- function(controlName, label) { } checkChoicesArgs <- function(choices, choicesNames, choicesValues) { - - lenNames <- length(choicesNames) - lenVals <- length(choicesValues) - + # if-else to check that either choices OR (choicesNames + choicesValues) + # were correctly provided if (is.null(choices)) { - if (lenNames == 0 || lenVals == 0) { + if (length(choicesNames) == 0 || length(choicesValues) == 0) { stop("Please specify a non-empty vector for `choices` (or, alternatively, for both `choicesNames` and `choicesValues`).") } - if (lenNames != lenVals) { + if (length(choicesNames) != length(choicesValues)) { stop("`choicesNames` and `choicesValues` must have the same length.") } - if (!is.null(names(choicesNames)) || !is.null(names(choicesValues))) { + if (anyNamed(choicesNames) || anyNamed(choicesValues)) { stop("`choicesNames` and `choicesValues` must not be named.") } } else { - if (lenNames != 0 || lenVals != 0) { + if (!is.null(choicesNames) || !is.null(choicesValues)) { warning("Using `choices` argument; ignoring `choicesNames` and - `choicesValues`.") - choicesNames = NULL - choicesValues = NULL + `choicesValues`.") } - # resolve names if not specified - choices <- choicesWithNames(choices) + choices <- choicesWithNames(choices) # resolve names if not specified + choicesNames <- names(choices) + choicesValues <- unname(choices) } - return(list(choices = choices, - choicesNames = choicesNames, - choicesValues = choicesValues)) + return(list(choicesNames = choicesNames, choicesValues = choicesValues)) } # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it From c6e1e408963d403b2df1a808045308d61e29212f Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Wed, 22 Mar 2017 00:30:02 +0000 Subject: [PATCH 12/22] don't propagate `choices` beyond normalizeCheckArgs() (only keep `choicesNames` and `choicesValues`) --- R/input-checkboxgroup.R | 16 +++++----------- R/input-radiobuttons.R | 13 +++++-------- R/input-select.R | 2 +- R/input-utils.R | 26 ++++++++------------------ R/update-input.R | 17 +++++++---------- 5 files changed, 26 insertions(+), 48 deletions(-) diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R index 343e62977d..5b815327e9 100644 --- a/R/input-checkboxgroup.R +++ b/R/input-checkboxgroup.R @@ -70,23 +70,17 @@ checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL, inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { - args <- checkChoicesArgs(choices, choicesNames, choicesValues) - choices <- args$choices - choicesNames <- args$choicesNames - choicesValues <- args$choicesValues + args <- normalizeChoicesArgs(choices, choicesNames, choicesValues) selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified if (!is.null(selected)) - selected <- validateSelected(selected, choices, inputId, - choicesNames, choicesValues) + selected <- normalizeSelected(selected, inputId, + args$choicesNames, args$choicesValues) - if (!is.null(selected)) - selected <- validateSelected(selected, choices, inputId) - - options <- generateOptions(inputId, choices, selected, inline, - 'checkbox', choicesNames, choicesValues) + options <- generateOptions(inputId, selected, inline, + 'checkbox', args$choicesNames, args$choicesValues) divClass <- "form-group shiny-input-checkboxgroup shiny-input-container" if (inline) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index 456040ab12..150fdfb9a9 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -84,22 +84,19 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { - args <- checkChoicesArgs(choices, choicesNames, choicesValues) - choices <- args$choices - choicesNames <- args$choicesNames - choicesValues <- args$choicesValues + args <- normalizeChoicesArgs(choices, choicesNames, choicesValues) selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified - selected <- if (is.null(selected)) choices[[1]] %OR% choicesValues[[1]] else { - validateSelected(selected, choices, inputId, choicesNames, choicesValues) + selected <- if (is.null(selected)) args$choicesValues[[1]] else { + normalizeSelected(selected, inputId, args$choicesNames, args$choicesValues) } if (length(selected) > 1) stop("The 'selected' argument must be of length 1") - options <- generateOptions(inputId, choices, selected, inline, - 'radio', choicesNames, choicesValues) + options <- generateOptions(inputId, selected, inline, 'radio', + args$choicesNames, args$choicesValues) divClass <- "form-group shiny-input-radiogroup shiny-input-container" if (inline) divClass <- paste(divClass, "shiny-input-container-inline") diff --git a/R/input-select.R b/R/input-select.R index 72e584de8f..d6554091dd 100644 --- a/R/input-select.R +++ b/R/input-select.R @@ -85,7 +85,7 @@ selectInput <- function(inputId, label, choices, selected = NULL, # default value if it's not specified if (is.null(selected)) { if (!multiple) selected <- firstChoice(choices) - } else selected <- validateSelected(selected, choices, inputId) + } else selected <- normalizeSelected(selected, inputId, names(choices), unname(choices)) if (!is.null(size) && selectize) { stop("'size' argument is incompatible with 'selectize=TRUE'.") diff --git a/R/input-utils.R b/R/input-utils.R index 80d3dbfa06..1805acdf43 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -2,7 +2,7 @@ controlLabel <- function(controlName, label) { label %AND% tags$label(class = "control-label", `for` = controlName, label) } -checkChoicesArgs <- function(choices, choicesNames, choicesValues) { +normalizeChoicesArgs <- function(choices, choicesNames, choicesValues) { # if-else to check that either choices OR (choicesNames + choicesValues) # were correctly provided if (is.null(choices)) { @@ -32,7 +32,7 @@ checkChoicesArgs <- function(choices, choicesNames, choicesValues) { # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it # refers to values. Below is a function for backward compatibility. It also # coerces the value to `character`. -validateSelected <- function(selected, choices, inputId, choicesNames, choicesValues) { +normalizeSelected <- function(selected, inputId, choicesNames, choicesValues) { # this line accomplishes two tings: # - coerces selected to character # - drops name, otherwise toJSON() keeps it too @@ -40,22 +40,17 @@ validateSelected <- function(selected, choices, inputId, choicesNames, choicesVa # if you are using optgroups, you're using shiny > 0.10.0, and you should # already know that `selected` must be a value instead of a label - if (needOptgroup(choices %OR% choicesValues)) return(selected) + if (needOptgroup(choicesValues)) return(selected) - if (is.list(choices)) choices <- unlist(choices) if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) - nms <- names(choices) %OR% choicesNames - - # labels and values are identical, no need to validate - if (identical(nms, unname(choices) %OR% choicesValues)) return(selected) # when selected labels instead of values - i <- (selected %in% nms) & !(selected %in% (choices %OR% choicesValues)) + i <- (selected %in% choicesNames) & !(selected %in% choicesValues) if (any(i)) { warnFun <- if (all(i)) { # replace names with values - selected <- unname(choices[selected]) %OR% choicesValues[[which(choicesNames == selected)]] + selected <- choicesValues[[which(choicesNames == selected)]] warning } else stop # stop when it is ambiguous (some labels == values) warnFun("'selected' must be the values instead of names of 'choices' ", @@ -66,17 +61,12 @@ validateSelected <- function(selected, choices, inputId, choicesNames, choicesVa # generate options for radio buttons and checkbox groups (type = 'checkbox' or # 'radio') -generateOptions <- function(inputId, choices = NULL, selected, inline, - type = 'checkbox', choicesNames = NULL, - choicesValues = NULL, +generateOptions <- function(inputId, selected, inline, type = 'checkbox', + choicesNames, choicesValues, session = getDefaultReactiveDomain()) { - - nms <- names(choices) %OR% choicesNames - vals <- choices %OR% choicesValues - # generate a list of options <- mapply( - vals, nms, + choicesValues, choicesNames, FUN = function(value, name) { inputTag <- tags$input( type = type, name = inputId, value = value diff --git a/R/update-input.R b/R/update-input.R index f2886f81df..467c4d9d93 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -454,19 +454,16 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL, updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, type = NULL, choicesNames = NULL, choicesValues = NULL) { - args <- checkChoicesArgs(choices, choicesNames, choicesValues) - choices <- args$choices - choicesNames <- args$choicesNames - choicesValues <- args$choicesValues + args <- normalizeChoicesArgs(choices, choicesNames, choicesValues) if (!is.null(selected)) - selected <- validateSelected(selected, choices, session$ns(inputId), - choicesNames, choicesValues) + selected <- normalizeSelected(selected, session$ns(inputId), + args$choicesNames, args$choicesValues) - options <- if (!is.null(choices) || !is.null(choicesValues)) { + options <- if (!is.null(args$choicesValues)) { format(tagList( - generateOptions(session$ns(inputId), choices, selected, inline, - type, choicesNames, choicesValues) + generateOptions(session$ns(inputId), selected, inline, type, + args$choicesNames, args$choicesValues) )) } @@ -610,7 +607,7 @@ updateSelectInput <- function(session, inputId, label = NULL, choices = NULL, selected = NULL) { choices <- if (!is.null(choices)) choicesWithNames(choices) if (!is.null(selected)) - selected <- validateSelected(selected, choices, inputId) + selected <- normalizeSelected(selected, inputId, names(choices), unname(choices)) options <- if (!is.null(choices)) selectOptions(choices, selected) message <- dropNulls(list(label = label, options = options, value = selected)) session$sendInputMessage(inputId, message) From 2b28ea2da4ac401b9f73c046186468ea0f3e35c1 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Wed, 22 Mar 2017 00:33:50 +0000 Subject: [PATCH 13/22] replace `choicesNames` with `choiceNames`, and `choicesValues` with `choiceValues` --- R/input-checkboxgroup.R | 18 +++++++------- R/input-radiobuttons.R | 20 +++++++-------- R/input-utils.R | 44 ++++++++++++++++----------------- R/update-input.R | 18 +++++++------- man/checkboxGroupInput.Rd | 13 +++++----- man/radioButtons.Rd | 13 +++++----- man/updateCheckboxGroupInput.Rd | 16 ++++++------ man/updateRadioButtons.Rd | 16 ++++++------ 8 files changed, 78 insertions(+), 80 deletions(-) diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R index 5b815327e9..611c1ea1a8 100644 --- a/R/input-checkboxgroup.R +++ b/R/input-checkboxgroup.R @@ -11,13 +11,13 @@ #' must not be provided, and vice-versa. #' @param selected The values that should be initially selected, if any. #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally) -#' @param choicesNames,choicesValues List of names and values, respectively, +#' @param choiceNames,choiceValues List of names and values, respectively, #' that are displayed to the user in the app and correspond to the each -#' choice (for this reason, \code{choicesNames} and \code{choicesValues} +#' choice (for this reason, \code{choiceNames} and \code{choiceValues} #' must have the same length). If either of these arguments is #' provided, then the other \emph{must} be provided and \code{choices} #' \emph{must not} be provided. The advantage of using both of these over -#' a named list for \code{choices} is that \code{choicesNames} allows any +#' a named list for \code{choices} is that \code{choiceNames} allows any #' type of UI object to be passed through (tag objects, icons, HTML code, #' ...), instead of just simple text. See Examples. #' @@ -48,10 +48,10 @@ #' #' ui <- fluidPage( #' checkboxGroupInput("icons", "Choose icons:", -#' choicesNames = +#' choiceNames = #' list(icon("calendar"), icon("bed"), #' icon("cog"), icon("bug")), -#' choicesValues = +#' choiceValues = #' list("calendar", "bed", "cog", "bug") #' ), #' textOutput("txt") @@ -68,19 +68,19 @@ #' } #' @export checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL, - inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { + inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) { - args <- normalizeChoicesArgs(choices, choicesNames, choicesValues) + args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified if (!is.null(selected)) selected <- normalizeSelected(selected, inputId, - args$choicesNames, args$choicesValues) + args$choiceNames, args$choiceValues) options <- generateOptions(inputId, selected, inline, - 'checkbox', args$choicesNames, args$choicesValues) + 'checkbox', args$choiceNames, args$choiceValues) divClass <- "form-group shiny-input-checkboxgroup shiny-input-container" if (inline) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index 150fdfb9a9..02a03c0d1b 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -18,13 +18,13 @@ #' defaults to the first value) #' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally) #' @return A set of radio buttons that can be added to a UI definition. -#' @param choicesNames,choicesValues List of names and values, respectively, +#' @param choiceNames,choiceValues List of names and values, respectively, #' that are displayed to the user in the app and correspond to the each -#' choice (for this reason, \code{choicesNames} and \code{choicesValues} +#' choice (for this reason, \code{choiceNames} and \code{choiceValues} #' must have the same length). If either of these arguments is #' provided, then the other \emph{must} be provided and \code{choices} #' \emph{must not} be provided. The advantage of using both of these over -#' a named list for \code{choices} is that \code{choicesNames} allows any +#' a named list for \code{choices} is that \code{choiceNames} allows any #' type of UI object to be passed through (tag objects, icons, HTML code, #' ...), instead of just simple text. See Examples. #' @@ -61,12 +61,12 @@ #' #' ui <- fluidPage( #' radioButtons("rb", "Choose one:", -#' choicesNames = list( +#' choiceNames = list( #' icon("calendar"), #' HTML("

Red Text

"), #' "Normal text" #' ), -#' choicesValues = list( +#' choiceValues = list( #' "icon", "html", "text" #' )), #' textOutput("txt") @@ -82,21 +82,21 @@ #' } #' @export radioButtons <- function(inputId, label, choices = NULL, selected = NULL, - inline = FALSE, width = NULL, choicesNames = NULL, choicesValues = NULL) { + inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) { - args <- normalizeChoicesArgs(choices, choicesNames, choicesValues) + args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified - selected <- if (is.null(selected)) args$choicesValues[[1]] else { - normalizeSelected(selected, inputId, args$choicesNames, args$choicesValues) + selected <- if (is.null(selected)) args$choiceValues[[1]] else { + normalizeSelected(selected, inputId, args$choiceNames, args$choiceValues) } if (length(selected) > 1) stop("The 'selected' argument must be of length 1") options <- generateOptions(inputId, selected, inline, 'radio', - args$choicesNames, args$choicesValues) + args$choiceNames, args$choiceValues) divClass <- "form-group shiny-input-radiogroup shiny-input-container" if (inline) divClass <- paste(divClass, "shiny-input-container-inline") diff --git a/R/input-utils.R b/R/input-utils.R index 1805acdf43..74eefabe11 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -2,37 +2,37 @@ controlLabel <- function(controlName, label) { label %AND% tags$label(class = "control-label", `for` = controlName, label) } -normalizeChoicesArgs <- function(choices, choicesNames, choicesValues) { - # if-else to check that either choices OR (choicesNames + choicesValues) +normalizeChoicesArgs <- function(choices, choiceNames, choiceValues) { + # if-else to check that either choices OR (choiceNames + choiceValues) # were correctly provided if (is.null(choices)) { - if (length(choicesNames) == 0 || length(choicesValues) == 0) { + if (length(choiceNames) == 0 || length(choiceValues) == 0) { stop("Please specify a non-empty vector for `choices` (or, - alternatively, for both `choicesNames` and `choicesValues`).") + alternatively, for both `choiceNames` and `choiceValues`).") } - if (length(choicesNames) != length(choicesValues)) { - stop("`choicesNames` and `choicesValues` must have the same length.") + if (length(choiceNames) != length(choiceValues)) { + stop("`choiceNames` and `choiceValues` must have the same length.") } - if (anyNamed(choicesNames) || anyNamed(choicesValues)) { - stop("`choicesNames` and `choicesValues` must not be named.") + if (anyNamed(choiceNames) || anyNamed(choiceValues)) { + stop("`choiceNames` and `choiceValues` must not be named.") } } else { - if (!is.null(choicesNames) || !is.null(choicesValues)) { - warning("Using `choices` argument; ignoring `choicesNames` and - `choicesValues`.") + if (!is.null(choiceNames) || !is.null(choiceValues)) { + warning("Using `choices` argument; ignoring `choiceNames` and + `choiceValues`.") } choices <- choicesWithNames(choices) # resolve names if not specified - choicesNames <- names(choices) - choicesValues <- unname(choices) + choiceNames <- names(choices) + choiceValues <- unname(choices) } - return(list(choicesNames = choicesNames, choicesValues = choicesValues)) + return(list(choiceNames = choiceNames, choiceValues = choiceValues)) } # Before shiny 0.9, `selected` refers to names/labels of `choices`; now it # refers to values. Below is a function for backward compatibility. It also # coerces the value to `character`. -normalizeSelected <- function(selected, inputId, choicesNames, choicesValues) { +normalizeSelected <- function(selected, inputId, choiceNames, choiceValues) { # this line accomplishes two tings: # - coerces selected to character # - drops name, otherwise toJSON() keeps it too @@ -40,17 +40,17 @@ normalizeSelected <- function(selected, inputId, choicesNames, choicesValues) { # if you are using optgroups, you're using shiny > 0.10.0, and you should # already know that `selected` must be a value instead of a label - if (needOptgroup(choicesValues)) return(selected) + if (needOptgroup(choiceValues)) return(selected) - if (is.list(choicesNames)) choicesNames <- unlist(choicesNames) - if (is.list(choicesValues)) choicesValues <- unlist(choicesValues) + if (is.list(choiceNames)) choiceNames <- unlist(choiceNames) + if (is.list(choiceValues)) choiceValues <- unlist(choiceValues) # when selected labels instead of values - i <- (selected %in% choicesNames) & !(selected %in% choicesValues) + i <- (selected %in% choiceNames) & !(selected %in% choiceValues) if (any(i)) { warnFun <- if (all(i)) { # replace names with values - selected <- choicesValues[[which(choicesNames == selected)]] + selected <- choiceValues[[which(choiceNames == selected)]] warning } else stop # stop when it is ambiguous (some labels == values) warnFun("'selected' must be the values instead of names of 'choices' ", @@ -62,11 +62,11 @@ normalizeSelected <- function(selected, inputId, choicesNames, choicesValues) { # generate options for radio buttons and checkbox groups (type = 'checkbox' or # 'radio') generateOptions <- function(inputId, selected, inline, type = 'checkbox', - choicesNames, choicesValues, + choiceNames, choiceValues, session = getDefaultReactiveDomain()) { # generate a list of options <- mapply( - choicesValues, choicesNames, + choiceValues, choiceNames, FUN = function(value, name) { inputTag <- tags$input( type = type, name = inputId, value = value diff --git a/R/update-input.R b/R/update-input.R index 467c4d9d93..f5ab062a80 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -453,17 +453,17 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL, updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, type = NULL, - choicesNames = NULL, choicesValues = NULL) { - args <- normalizeChoicesArgs(choices, choicesNames, choicesValues) + choiceNames = NULL, choiceValues = NULL) { + args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) if (!is.null(selected)) selected <- normalizeSelected(selected, session$ns(inputId), - args$choicesNames, args$choicesValues) + args$choiceNames, args$choiceValues) - options <- if (!is.null(args$choicesValues)) { + options <- if (!is.null(args$choiceValues)) { format(tagList( generateOptions(session$ns(inputId), selected, inline, type, - args$choicesNames, args$choicesValues) + args$choiceNames, args$choiceValues) )) } @@ -513,9 +513,9 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, #' @export updateCheckboxGroupInput <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, - choicesNames = NULL, choicesValues = NULL) { + choiceNames = NULL, choiceValues = NULL) { updateInputOptions(session, inputId, label, choices, selected, - inline, "checkbox", choicesNames, choicesValues) + inline, "checkbox", choiceNames, choiceValues) } @@ -556,11 +556,11 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL, #' @export updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, - choicesNames = NULL, choicesValues = NULL) { + choiceNames = NULL, choiceValues = NULL) { # you must select at least one radio button if (is.null(selected) && !is.null(choices)) selected <- choices[[1]] updateInputOptions(session, inputId, label, choices, selected, - inline, 'radio', choicesNames, choicesValues) + inline, 'radio', choiceNames, choiceValues) } diff --git a/man/checkboxGroupInput.Rd b/man/checkboxGroupInput.Rd index 66bdefdbd5..ff67999083 100644 --- a/man/checkboxGroupInput.Rd +++ b/man/checkboxGroupInput.Rd @@ -5,8 +5,7 @@ \title{Checkbox Group Input Control} \usage{ checkboxGroupInput(inputId, label, choices = NULL, selected = NULL, - inline = FALSE, width = NULL, choicesNames = NULL, - choicesValues = NULL) + inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) } \arguments{ \item{inputId}{The \code{input} slot that will be used to access the value.} @@ -25,13 +24,13 @@ must not be provided, and vice-versa.} \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'}; see \code{\link{validateCssUnit}}.} -\item{choicesNames, choicesValues}{List of names and values, respectively, +\item{choiceNames, choiceValues}{List of names and values, respectively, that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choicesNames} and \code{choicesValues} +choice (for this reason, \code{choiceNames} and \code{choiceValues} must have the same length). If either of these arguments is provided, then the other \emph{must} be provided and \code{choices} \emph{must not} be provided. The advantage of using both of these over -a named list for \code{choices} is that \code{choicesNames} allows any +a named list for \code{choices} is that \code{choiceNames} allows any type of UI object to be passed through (tag objects, icons, HTML code, ...), instead of just simple text. See Examples.} } @@ -65,10 +64,10 @@ shinyApp(ui, server) ui <- fluidPage( checkboxGroupInput("icons", "Choose icons:", - choicesNames = + choiceNames = list(icon("calendar"), icon("bed"), icon("cog"), icon("bug")), - choicesValues = + choiceValues = list("calendar", "bed", "cog", "bug") ), textOutput("txt") diff --git a/man/radioButtons.Rd b/man/radioButtons.Rd index 0d6274c0eb..60e4fe1c4d 100644 --- a/man/radioButtons.Rd +++ b/man/radioButtons.Rd @@ -5,8 +5,7 @@ \title{Create radio buttons} \usage{ radioButtons(inputId, label, choices = NULL, selected = NULL, - inline = FALSE, width = NULL, choicesNames = NULL, - choicesValues = NULL) + inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) } \arguments{ \item{inputId}{The \code{input} slot that will be used to access the value.} @@ -26,13 +25,13 @@ defaults to the first value)} \item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'}; see \code{\link{validateCssUnit}}.} -\item{choicesNames, choicesValues}{List of names and values, respectively, +\item{choiceNames, choiceValues}{List of names and values, respectively, that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choicesNames} and \code{choicesValues} +choice (for this reason, \code{choiceNames} and \code{choiceValues} must have the same length). If either of these arguments is provided, then the other \emph{must} be provided and \code{choices} \emph{must not} be provided. The advantage of using both of these over -a named list for \code{choices} is that \code{choicesNames} allows any +a named list for \code{choices} is that \code{choiceNames} allows any type of UI object to be passed through (tag objects, icons, HTML code, ...), instead of just simple text. See Examples.} } @@ -80,12 +79,12 @@ shinyApp(ui, server) ui <- fluidPage( radioButtons("rb", "Choose one:", - choicesNames = list( + choiceNames = list( icon("calendar"), HTML("

Red Text

"), "Normal text" ), - choicesValues = list( + choiceValues = list( "icon", "html", "text" )), textOutput("txt") diff --git a/man/updateCheckboxGroupInput.Rd b/man/updateCheckboxGroupInput.Rd index f49ea70690..115ec6dfc3 100644 --- a/man/updateCheckboxGroupInput.Rd +++ b/man/updateCheckboxGroupInput.Rd @@ -5,8 +5,8 @@ \title{Change the value of a checkbox group input on the client} \usage{ updateCheckboxGroupInput(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE, choicesNames = NULL, - choicesValues = NULL) + selected = NULL, inline = FALSE, choiceNames = NULL, + choiceValues = NULL) } \arguments{ \item{session}{The \code{session} object passed to function given to @@ -25,23 +25,23 @@ must not be provided, and vice-versa.} \item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} -\item{choicesNames}{List of names and values, respectively, +\item{choiceNames}{List of names and values, respectively, that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choicesNames} and \code{choicesValues} +choice (for this reason, \code{choiceNames} and \code{choiceValues} must have the same length). If either of these arguments is provided, then the other \emph{must} be provided and \code{choices} \emph{must not} be provided. The advantage of using both of these over -a named list for \code{choices} is that \code{choicesNames} allows any +a named list for \code{choices} is that \code{choiceNames} allows any type of UI object to be passed through (tag objects, icons, HTML code, ...), instead of just simple text. See Examples.} -\item{choicesValues}{List of names and values, respectively, +\item{choiceValues}{List of names and values, respectively, that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choicesNames} and \code{choicesValues} +choice (for this reason, \code{choiceNames} and \code{choiceValues} must have the same length). If either of these arguments is provided, then the other \emph{must} be provided and \code{choices} \emph{must not} be provided. The advantage of using both of these over -a named list for \code{choices} is that \code{choicesNames} allows any +a named list for \code{choices} is that \code{choiceNames} allows any type of UI object to be passed through (tag objects, icons, HTML code, ...), instead of just simple text. See Examples.} } diff --git a/man/updateRadioButtons.Rd b/man/updateRadioButtons.Rd index 6ef23c8c12..44fa867b03 100644 --- a/man/updateRadioButtons.Rd +++ b/man/updateRadioButtons.Rd @@ -5,8 +5,8 @@ \title{Change the value of a radio input on the client} \usage{ updateRadioButtons(session, inputId, label = NULL, choices = NULL, - selected = NULL, inline = FALSE, choicesNames = NULL, - choicesValues = NULL) + selected = NULL, inline = FALSE, choiceNames = NULL, + choiceValues = NULL) } \arguments{ \item{session}{The \code{session} object passed to function given to @@ -26,23 +26,23 @@ defaults to the first value)} \item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)} -\item{choicesNames}{List of names and values, respectively, +\item{choiceNames}{List of names and values, respectively, that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choicesNames} and \code{choicesValues} +choice (for this reason, \code{choiceNames} and \code{choiceValues} must have the same length). If either of these arguments is provided, then the other \emph{must} be provided and \code{choices} \emph{must not} be provided. The advantage of using both of these over -a named list for \code{choices} is that \code{choicesNames} allows any +a named list for \code{choices} is that \code{choiceNames} allows any type of UI object to be passed through (tag objects, icons, HTML code, ...), instead of just simple text. See Examples.} -\item{choicesValues}{List of names and values, respectively, +\item{choiceValues}{List of names and values, respectively, that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choicesNames} and \code{choicesValues} +choice (for this reason, \code{choiceNames} and \code{choiceValues} must have the same length). If either of these arguments is provided, then the other \emph{must} be provided and \code{choices} \emph{must not} be provided. The advantage of using both of these over -a named list for \code{choices} is that \code{choicesNames} allows any +a named list for \code{choices} is that \code{choiceNames} allows any type of UI object to be passed through (tag objects, icons, HTML code, ...), instead of just simple text. See Examples.} } From 81fe6a1a726867a51aaf9846a988aae592a5bbd1 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Wed, 22 Mar 2017 02:13:38 +0000 Subject: [PATCH 14/22] a bunch of tests --- R/input-utils.R | 5 ++- tests/testthat/test-bootstrap.r | 58 +++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/R/input-utils.R b/R/input-utils.R index 74eefabe11..412bda1c3e 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -18,8 +18,7 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues) { } } else { if (!is.null(choiceNames) || !is.null(choiceValues)) { - warning("Using `choices` argument; ignoring `choiceNames` and - `choiceValues`.") + warning("Using `choices` argument; ignoring `choiceNames` and `choiceValues`.") } choices <- choicesWithNames(choices) # resolve names if not specified choiceNames <- names(choices) @@ -42,7 +41,7 @@ normalizeSelected <- function(selected, inputId, choiceNames, choiceValues) { # already know that `selected` must be a value instead of a label if (needOptgroup(choiceValues)) return(selected) - if (is.list(choiceNames)) choiceNames <- unlist(choiceNames) + if (is.list(choiceNames)) choiceNames <- unlist(as.character(choiceNames)) if (is.list(choiceValues)) choiceValues <- unlist(choiceValues) # when selected labels instead of values diff --git a/tests/testthat/test-bootstrap.r b/tests/testthat/test-bootstrap.r index ea8a37fa3e..d8b4900783 100644 --- a/tests/testthat/test-bootstrap.r +++ b/tests/testthat/test-bootstrap.r @@ -28,8 +28,7 @@ test_that("Repeated names for selectInput and radioButtons choices", { format(x) )) - - # Radio buttons + # Radio buttons using choices x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3')) choices <- x$children @@ -44,6 +43,28 @@ test_that("Repeated names for selectInput and radioButtons choices", { expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], HTML('b')) expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'x3') expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL) + + # Radio buttons using choiceNames and choiceValues + x <- radioButtons('id','label', + choiceNames = list(icon('calendar'), HTML('

Red

'), 'Normal'), + choiceValues = list('icon', 'html', 'text') + ) + choices <- x$children + + expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], + HTML('')) + expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'icon') + expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked') + + expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], + HTML('

Red

')) + expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'html') + expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL) + + expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], + HTML('Normal')) + expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'text') + expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL) }) @@ -167,3 +188,36 @@ test_that("selectInput selects items by default", { selectInput('x', 'x', list("a", "b"), multiple = TRUE) )) }) + +test_that("normalizeChoicesArgs does its job", { + + # Unnamed vectors and lists + expected <- list(choiceNames = c("a", "b"), choiceValues = list("a", "b")) + expect_equal(normalizeChoicesArgs(c("a", "b"), NULL, NULL), expected) + expect_equal(normalizeChoicesArgs(list("a", "b"), NULL, NULL), expected) + + # Named list + expected <- list(choiceNames = c("one", "two"), choiceValues = list("a", "b")) + x <- list(one = "a", two = "b") + expect_equal(normalizeChoicesArgs(x, NULL, NULL), expected) + expect_equal(normalizeChoicesArgs(NULL, names(x), unname(x)), expected) + + # Using unnamed `choiceNames` and `choiceValues` vectors/lists directly + expected <- list(choiceNames = c("one", "two"), choiceValues = c("a", "b")) + expect_equal(normalizeChoicesArgs(NULL, c("one", "two"), c("a", "b")), expected) + + expected <- list(choiceNames = list("one", "two"), choiceValues = list("a", "b")) + expect_equal(normalizeChoicesArgs(NULL, list("one", "two"), list("a", "b")), expected) + + # Using choiceNames with HTML and choiceValues + nms <- list(icon("calendar"), HTML("

Red Text

")) + vals <- list("a", "b") + expected <- list(choiceNames = nms, choiceValues = vals) + expect_equal(normalizeChoicesArgs(NULL, nms, vals), expected) + + # Attempt to use choices, AND choiceNames + choiceValues + x <- list("a", "b") + expect_warning(res <- normalizeChoicesArgs(x, nms, vals), + "Using `choices` argument; ignoring `choiceNames` and `choiceValues`.") + expect_equal(res, list(choiceNames = c("a", "b"), choiceValues = list("a", "b"))) +}) From 89617788670fc99a64e31954ce008729641b0527 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Wed, 22 Mar 2017 02:31:57 +0000 Subject: [PATCH 15/22] news item and comment --- NEWS.md | 2 ++ R/input-utils.R | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/NEWS.md b/NEWS.md index 6f7164f421..0d05d43ea2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,8 @@ shiny 1.0.0.9001 ### Minor new features and improvements +* Addressed [#1348](https://github.com/rstudio/shiny/issues/1348) and [#1437](https://github.com/rstudio/shiny/issues/1437) by adding two new arguments to `radioButtons()` and `checkboxGroupInput()`: `choiceNames` (list or vector) and `choiceValues` (list or vector). These can be passed in as an alternative to `choices`, with the added benefit that the elements in `choiceNames` can be arbitrary UI (i.e. anything created by `HTML()` and the `tags()` functions, like icons and images). While the underlying values for each choice (passed in through `choiceValues`) must still be simple text, their visual representation on the app (what the user actually clicks to select a different option) can be any valid HTML element. See `?radioButtons` for a small example. ([#1521](https://github.com/rstudio/shiny/pull/1521)) + * Updated `tools/README.md` with more detailed instructions. ([##1616](https://github.com/rstudio/shiny/pull/1616)) * Fixed [#1565](https://github.com/rstudio/shiny/issues/1565), which meant that resources with spaces in their names return HTTP 404. ([#1566](https://github.com/rstudio/shiny/pull/1566)) diff --git a/R/input-utils.R b/R/input-utils.R index 412bda1c3e..03728994eb 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -2,6 +2,14 @@ controlLabel <- function(controlName, label) { label %AND% tags$label(class = "control-label", `for` = controlName, label) } +# This function takes in either a list or vector for `choices` (and +# `choiceNames` and `choiceValues` are passed in as NULL) OR it takes +# in a list or vector for both `choiceNames` and `choiceValues` (and +# `choices` is passed as NULL) and returns a list of two elements: +# - `choiceNames` is a vector or list that holds the options names +# (each element can be arbitrary UI, or simple text) +# - `choiceValues` is a vector or list that holds the options values +# (each element must be simple text) normalizeChoicesArgs <- function(choices, choiceNames, choiceValues) { # if-else to check that either choices OR (choiceNames + choiceValues) # were correctly provided From bcd8fce19931e2ff3d1a0c2d66758703e8fee35e Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Fri, 24 Mar 2017 17:50:15 +0000 Subject: [PATCH 16/22] some changes after feedback from Winston --- R/input-checkboxgroup.R | 4 +--- R/input-radiobuttons.R | 8 +++---- R/input-select.R | 2 +- R/input-utils.R | 40 +++++---------------------------- R/update-input.R | 14 +++++++----- tests/testthat/test-bootstrap.r | 25 ++++++++++++++++----- 6 files changed, 38 insertions(+), 55 deletions(-) diff --git a/R/input-checkboxgroup.R b/R/input-checkboxgroup.R index 611c1ea1a8..2122b039a3 100644 --- a/R/input-checkboxgroup.R +++ b/R/input-checkboxgroup.R @@ -75,9 +75,7 @@ checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL, selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified - if (!is.null(selected)) - selected <- normalizeSelected(selected, inputId, - args$choiceNames, args$choiceValues) + if (!is.null(selected)) selected <- as.character(selected) options <- generateOptions(inputId, selected, inline, 'checkbox', args$choiceNames, args$choiceValues) diff --git a/R/input-radiobuttons.R b/R/input-radiobuttons.R index 02a03c0d1b..a9279b7044 100644 --- a/R/input-radiobuttons.R +++ b/R/input-radiobuttons.R @@ -89,14 +89,12 @@ radioButtons <- function(inputId, label, choices = NULL, selected = NULL, selected <- restoreInput(id = inputId, default = selected) # default value if it's not specified - selected <- if (is.null(selected)) args$choiceValues[[1]] else { - normalizeSelected(selected, inputId, args$choiceNames, args$choiceValues) - } + selected <- if (is.null(selected)) args$choiceValues[[1]] else as.character(selected) if (length(selected) > 1) stop("The 'selected' argument must be of length 1") - options <- generateOptions(inputId, selected, inline, 'radio', - args$choiceNames, args$choiceValues) + options <- generateOptions(inputId, selected, inline, + 'radio', args$choiceNames, args$choiceValues) divClass <- "form-group shiny-input-radiogroup shiny-input-container" if (inline) divClass <- paste(divClass, "shiny-input-container-inline") diff --git a/R/input-select.R b/R/input-select.R index d6554091dd..e87ab40fca 100644 --- a/R/input-select.R +++ b/R/input-select.R @@ -85,7 +85,7 @@ selectInput <- function(inputId, label, choices, selected = NULL, # default value if it's not specified if (is.null(selected)) { if (!multiple) selected <- firstChoice(choices) - } else selected <- normalizeSelected(selected, inputId, names(choices), unname(choices)) + } else selected <- as.character(selected) if (!is.null(size) && selectize) { stop("'size' argument is incompatible with 'selectize=TRUE'.") diff --git a/R/input-utils.R b/R/input-utils.R index 03728994eb..aecc234020 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -14,9 +14,10 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues) { # if-else to check that either choices OR (choiceNames + choiceValues) # were correctly provided if (is.null(choices)) { - if (length(choiceNames) == 0 || length(choiceValues) == 0) { - stop("Please specify a non-empty vector for `choices` (or, - alternatively, for both `choiceNames` and `choiceValues`).") + if (is.null(choiceNames) || is.null(choiceValues)) { + return(list(choiceNames = NULL, choiceValues = NULL)) + # stop("Please specify a non-empty vector for `choices` (or, + # alternatively, for both `choiceNames` and `choiceValues`).") } if (length(choiceNames) != length(choiceValues)) { stop("`choiceNames` and `choiceValues` must have the same length.") @@ -33,37 +34,8 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues) { choiceValues <- unname(choices) } - return(list(choiceNames = choiceNames, choiceValues = choiceValues)) -} - -# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it -# refers to values. Below is a function for backward compatibility. It also -# coerces the value to `character`. -normalizeSelected <- function(selected, inputId, choiceNames, choiceValues) { - # this line accomplishes two tings: - # - coerces selected to character - # - drops name, otherwise toJSON() keeps it too - selected <- as.character(selected) - - # if you are using optgroups, you're using shiny > 0.10.0, and you should - # already know that `selected` must be a value instead of a label - if (needOptgroup(choiceValues)) return(selected) - - if (is.list(choiceNames)) choiceNames <- unlist(as.character(choiceNames)) - if (is.list(choiceValues)) choiceValues <- unlist(choiceValues) - - # when selected labels instead of values - i <- (selected %in% choiceNames) & !(selected %in% choiceValues) - if (any(i)) { - warnFun <- if (all(i)) { - # replace names with values - selected <- choiceValues[[which(choiceNames == selected)]] - warning - } else stop # stop when it is ambiguous (some labels == values) - warnFun("'selected' must be the values instead of names of 'choices' ", - "for the input '", inputId, "'") - } - selected + return(list(choiceNames = as.list(choiceNames), + choiceValues = as.list(choiceValues))) } # generate options for radio buttons and checkbox groups (type = 'checkbox' or diff --git a/R/update-input.R b/R/update-input.R index f5ab062a80..9db8c4ea39 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -454,11 +454,11 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL, updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, type = NULL, choiceNames = NULL, choiceValues = NULL) { + if (is.null(type)) stop("Please specify the type ('checkbox' or 'radio')") + args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) - if (!is.null(selected)) - selected <- normalizeSelected(selected, session$ns(inputId), - args$choiceNames, args$choiceValues) + if (!is.null(selected)) selected <- as.character(selected) options <- if (!is.null(args$choiceValues)) { format(tagList( @@ -558,7 +558,10 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL, selected = NULL, inline = FALSE, choiceNames = NULL, choiceValues = NULL) { # you must select at least one radio button - if (is.null(selected) && !is.null(choices)) selected <- choices[[1]] + if (is.null(selected)) { + if (!is.null(choices)) selected <- choices[[1]] + else if (!is.null(choiceValues)) selected <- choiceValues[[1]] + } updateInputOptions(session, inputId, label, choices, selected, inline, 'radio', choiceNames, choiceValues) } @@ -606,8 +609,7 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL, updateSelectInput <- function(session, inputId, label = NULL, choices = NULL, selected = NULL) { choices <- if (!is.null(choices)) choicesWithNames(choices) - if (!is.null(selected)) - selected <- normalizeSelected(selected, inputId, names(choices), unname(choices)) + if (!is.null(selected)) selected <- as.character(selected) options <- if (!is.null(choices)) selectOptions(choices, selected) message <- dropNulls(list(label = label, options = options, value = selected)) session$sendInputMessage(inputId, message) diff --git a/tests/testthat/test-bootstrap.r b/tests/testthat/test-bootstrap.r index d8b4900783..db8a93239d 100644 --- a/tests/testthat/test-bootstrap.r +++ b/tests/testthat/test-bootstrap.r @@ -192,21 +192,18 @@ test_that("selectInput selects items by default", { test_that("normalizeChoicesArgs does its job", { # Unnamed vectors and lists - expected <- list(choiceNames = c("a", "b"), choiceValues = list("a", "b")) + expected <- list(choiceNames = list("a", "b"), choiceValues = list("a", "b")) expect_equal(normalizeChoicesArgs(c("a", "b"), NULL, NULL), expected) expect_equal(normalizeChoicesArgs(list("a", "b"), NULL, NULL), expected) # Named list - expected <- list(choiceNames = c("one", "two"), choiceValues = list("a", "b")) + expected <- list(choiceNames = list("one", "two"), choiceValues = list("a", "b")) x <- list(one = "a", two = "b") expect_equal(normalizeChoicesArgs(x, NULL, NULL), expected) expect_equal(normalizeChoicesArgs(NULL, names(x), unname(x)), expected) # Using unnamed `choiceNames` and `choiceValues` vectors/lists directly - expected <- list(choiceNames = c("one", "two"), choiceValues = c("a", "b")) expect_equal(normalizeChoicesArgs(NULL, c("one", "two"), c("a", "b")), expected) - - expected <- list(choiceNames = list("one", "two"), choiceValues = list("a", "b")) expect_equal(normalizeChoicesArgs(NULL, list("one", "two"), list("a", "b")), expected) # Using choiceNames with HTML and choiceValues @@ -219,5 +216,21 @@ test_that("normalizeChoicesArgs does its job", { x <- list("a", "b") expect_warning(res <- normalizeChoicesArgs(x, nms, vals), "Using `choices` argument; ignoring `choiceNames` and `choiceValues`.") - expect_equal(res, list(choiceNames = c("a", "b"), choiceValues = list("a", "b"))) + expect_equal(res, list(choiceNames = list("a", "b"), choiceValues = list("a", "b"))) + + # Set possibilities to character(0) + expected <- list(choiceNames = list(), choiceValues = list()) + expect_equal(normalizeChoicesArgs(character(0), NULL, NULL), expected) + expect_equal(normalizeChoicesArgs(NULL, character(0), character(0)), expected) + expect_warning(res <- normalizeChoicesArgs(character(0), character(0), character(0)), + "Using `choices` argument; ignoring `choiceNames` and `choiceValues`.") + expect_equal(res, expected) + + # Set possibilities to character(0) in an inconsistent way + expected <- list(choiceNames = NULL, choiceValues = NULL) + expect_equal(normalizeChoicesArgs(NULL, character(0), NULL), expected) + expect_equal(normalizeChoicesArgs(NULL, NULL, character(0)), expected) + + # Set all possibilities to NULL + expect_equal(normalizeChoicesArgs(NULL, NULL, NULL), expected) }) From 362ff9fb6f4655958be5118e10910a86c0dde8c2 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Fri, 24 Mar 2017 19:52:08 +0000 Subject: [PATCH 17/22] try to adapt things for selectInput --- R/input-select.R | 36 +++++++++++++++++++++--------------- R/input-utils.R | 15 +++++++++++---- man/selectInput.Rd | 17 +++++++++++++++-- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/R/input-select.R b/R/input-select.R index e87ab40fca..13e7836614 100644 --- a/R/input-select.R +++ b/R/input-select.R @@ -30,6 +30,17 @@ #' will result in a taller box. Not compatible with \code{selectize=TRUE}. #' Normally, when \code{multiple=FALSE}, a select input will be a drop-down #' list, but when \code{size} is set, it will be a box instead. +#' @param choiceNames,choiceValues List of names and values, respectively, +#' that are displayed to the user in the app and correspond to the each +#' choice (for this reason, \code{choiceNames} and \code{choiceValues} +#' must have the same length). If either of these arguments is +#' provided, then the other \emph{must} be provided and \code{choices} +#' \emph{must not} be provided. For \code{selectInput} and \code{selectizeInput}, +#' there isn't any particular advantage to using both of these over +#' a named list for \code{choices} and it is simply a matter of preference +#' (this is not always the case; for example, \code{\link{radioButtons}} and +#' \code{\link{checkboxGroupInput}} can use \code{choiceNames} to pass HTML +#' code, instead of simple text). #' @return A select list control that can be added to a UI definition. #' #' @family input elements @@ -73,18 +84,18 @@ #' ) #' } #' @export -selectInput <- function(inputId, label, choices, selected = NULL, - multiple = FALSE, selectize = TRUE, width = NULL, - size = NULL) { +selectInput <- function(inputId, label, choices = NULL, selected = NULL, + multiple = FALSE, selectize = TRUE, width = NULL, size = NULL, + choiceNames = NULL, choiceValues = NULL) { selected <- restoreInput(id = inputId, default = selected) - # resolve names - choices <- choicesWithNames(choices) + # resolve choices + args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) # default value if it's not specified if (is.null(selected)) { - if (!multiple) selected <- firstChoice(choices) + if (!multiple) selected <- firstChoice(args$choiceValues) } else selected <- as.character(selected) if (!is.null(size) && selectize) { @@ -96,7 +107,7 @@ selectInput <- function(inputId, label, choices, selected = NULL, id = inputId, class = if (!selectize) "form-control", size = size, - selectOptions(choices, selected) + selectOptions(args$choiceNames, args$choiceValues, selected) ) if (multiple) selectTag$attribs$multiple <- "multiple" @@ -111,7 +122,7 @@ selectInput <- function(inputId, label, choices, selected = NULL, if (!selectize) return(res) - selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% choices)) + selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% args$choiceValues)) } firstChoice <- function(choices) { @@ -123,8 +134,8 @@ firstChoice <- function(choices) { # Create tags for each of the options; use if necessary. # This returns a HTML string instead of tags, because of the 'selected' # attribute. -selectOptions <- function(choices, selected = NULL) { - html <- mapply(choices, names(choices), FUN = function(choice, label) { +selectOptions <- function(choiceNames, choiceValues, selected = NULL) { + html <- mapply(choiceValues, choiceNames, FUN = function(choice, label) { if (is.list(choice)) { # If sub-list, create an optgroup and recurse into the sublist sprintf( @@ -147,11 +158,6 @@ selectOptions <- function(choices, selected = NULL) { HTML(paste(html, collapse = '\n')) } -# need when choices contains sub-lists -needOptgroup <- function(choices) { - any(vapply(choices, is.list, logical(1))) -} - #' @rdname selectInput #' @param ... Arguments passed to \code{selectInput()}. #' @param options A list of options. See the documentation of \pkg{selectize.js} diff --git a/R/input-utils.R b/R/input-utils.R index aecc234020..7df1c6f93f 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -10,14 +10,21 @@ controlLabel <- function(controlName, label) { # (each element can be arbitrary UI, or simple text) # - `choiceValues` is a vector or list that holds the options values # (each element must be simple text) -normalizeChoicesArgs <- function(choices, choiceNames, choiceValues) { +normalizeChoicesArgs <- function(choices, choiceNames, choiceValues, + mustExist = TRUE) { # if-else to check that either choices OR (choiceNames + choiceValues) # were correctly provided if (is.null(choices)) { if (is.null(choiceNames) || is.null(choiceValues)) { - return(list(choiceNames = NULL, choiceValues = NULL)) - # stop("Please specify a non-empty vector for `choices` (or, - # alternatively, for both `choiceNames` and `choiceValues`).") + if (mustExist) { + stop("Please specify a non-empty vector for `choices` (or,", + "alternatively, for both `choiceNames` and `choiceValues`).") + } else { + # this is useful when we call this function from `updateInputOptions()` + # in which case, all three `choices`, `choiceNames` and `choiceValues` + # may legitimately be NULL + return(list(choiceNames = NULL, choiceValues = NULL)) + } } if (length(choiceNames) != length(choiceValues)) { stop("`choiceNames` and `choiceValues` must have the same length.") diff --git a/man/selectInput.Rd b/man/selectInput.Rd index f9a509bd29..7ff4d44b11 100644 --- a/man/selectInput.Rd +++ b/man/selectInput.Rd @@ -5,8 +5,9 @@ \alias{selectizeInput} \title{Create a select list input control} \usage{ -selectInput(inputId, label, choices, selected = NULL, multiple = FALSE, - selectize = TRUE, width = NULL, size = NULL) +selectInput(inputId, label, choices = NULL, selected = NULL, + multiple = FALSE, selectize = TRUE, width = NULL, size = NULL, + choiceNames = NULL, choiceValues = NULL) selectizeInput(inputId, ..., options = NULL, width = NULL) } @@ -39,6 +40,18 @@ will result in a taller box. Not compatible with \code{selectize=TRUE}. Normally, when \code{multiple=FALSE}, a select input will be a drop-down list, but when \code{size} is set, it will be a box instead.} +\item{choiceNames, choiceValues}{List of names and values, respectively, +that are displayed to the user in the app and correspond to the each +choice (for this reason, \code{choiceNames} and \code{choiceValues} +must have the same length). If either of these arguments is +provided, then the other \emph{must} be provided and \code{choices} +\emph{must not} be provided. For \code{selectInput} and \code{selectizeInput}, +there isn't any particular advantage to using both of these over +a named list for \code{choices} and it is simply a matter of preference +(this is not always the case; for example, \code{\link{radioButtons}} and +\code{\link{checkboxGroupInput}} can use \code{choiceNames} to pass HTML +code, instead of simple text).} + \item{...}{Arguments passed to \code{selectInput()}.} \item{options}{A list of options. See the documentation of \pkg{selectize.js} From 2488690fdd65b5f31fcf45e2d8bf854d455ccc44 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Fri, 24 Mar 2017 20:15:35 +0000 Subject: [PATCH 18/22] add mustExist to update-input --- R/update-input.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/update-input.R b/R/update-input.R index 9db8c4ea39..e434aca11e 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -456,7 +456,7 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL, choiceNames = NULL, choiceValues = NULL) { if (is.null(type)) stop("Please specify the type ('checkbox' or 'radio')") - args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) + args <- normalizeChoicesArgs(choices, choiceNames, choiceValues, mustExist = FALSE) if (!is.null(selected)) selected <- as.character(selected) From 0687f04aec3a53e18ca955d346d263748566d237 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Fri, 24 Mar 2017 23:01:39 +0000 Subject: [PATCH 19/22] revert selectInput adaptation (and add a space in error message) --- R/input-select.R | 38 ++++++++++++++++---------------------- R/input-utils.R | 2 +- man/selectInput.Rd | 17 ++--------------- 3 files changed, 19 insertions(+), 38 deletions(-) diff --git a/R/input-select.R b/R/input-select.R index 13e7836614..bcd829a2e5 100644 --- a/R/input-select.R +++ b/R/input-select.R @@ -30,17 +30,6 @@ #' will result in a taller box. Not compatible with \code{selectize=TRUE}. #' Normally, when \code{multiple=FALSE}, a select input will be a drop-down #' list, but when \code{size} is set, it will be a box instead. -#' @param choiceNames,choiceValues List of names and values, respectively, -#' that are displayed to the user in the app and correspond to the each -#' choice (for this reason, \code{choiceNames} and \code{choiceValues} -#' must have the same length). If either of these arguments is -#' provided, then the other \emph{must} be provided and \code{choices} -#' \emph{must not} be provided. For \code{selectInput} and \code{selectizeInput}, -#' there isn't any particular advantage to using both of these over -#' a named list for \code{choices} and it is simply a matter of preference -#' (this is not always the case; for example, \code{\link{radioButtons}} and -#' \code{\link{checkboxGroupInput}} can use \code{choiceNames} to pass HTML -#' code, instead of simple text). #' @return A select list control that can be added to a UI definition. #' #' @family input elements @@ -84,19 +73,19 @@ #' ) #' } #' @export -selectInput <- function(inputId, label, choices = NULL, selected = NULL, - multiple = FALSE, selectize = TRUE, width = NULL, size = NULL, - choiceNames = NULL, choiceValues = NULL) { +selectInput <- function(inputId, label, choices, selected = NULL, + multiple = FALSE, selectize = TRUE, width = NULL, + size = NULL) { selected <- restoreInput(id = inputId, default = selected) - # resolve choices - args <- normalizeChoicesArgs(choices, choiceNames, choiceValues) + # resolve names + choices <- choicesWithNames(choices) # default value if it's not specified if (is.null(selected)) { - if (!multiple) selected <- firstChoice(args$choiceValues) - } else selected <- as.character(selected) + if (!multiple) selected <- firstChoice(choices) + } else selected <- validateSelected(selected, choices, inputId) if (!is.null(size) && selectize) { stop("'size' argument is incompatible with 'selectize=TRUE'.") @@ -107,7 +96,7 @@ selectInput <- function(inputId, label, choices = NULL, selected = NULL, id = inputId, class = if (!selectize) "form-control", size = size, - selectOptions(args$choiceNames, args$choiceValues, selected) + selectOptions(choices, selected) ) if (multiple) selectTag$attribs$multiple <- "multiple" @@ -122,7 +111,7 @@ selectInput <- function(inputId, label, choices = NULL, selected = NULL, if (!selectize) return(res) - selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% args$choiceValues)) + selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% choices)) } firstChoice <- function(choices) { @@ -134,8 +123,8 @@ firstChoice <- function(choices) { # Create tags for each of the options; use if necessary. # This returns a HTML string instead of tags, because of the 'selected' # attribute. -selectOptions <- function(choiceNames, choiceValues, selected = NULL) { - html <- mapply(choiceValues, choiceNames, FUN = function(choice, label) { +selectOptions <- function(choices, selected = NULL) { + html <- mapply(choices, names(choices), FUN = function(choice, label) { if (is.list(choice)) { # If sub-list, create an optgroup and recurse into the sublist sprintf( @@ -158,6 +147,11 @@ selectOptions <- function(choiceNames, choiceValues, selected = NULL) { HTML(paste(html, collapse = '\n')) } +# need when choices contains sub-lists +needOptgroup <- function(choices) { + any(vapply(choices, is.list, logical(1))) +} + #' @rdname selectInput #' @param ... Arguments passed to \code{selectInput()}. #' @param options A list of options. See the documentation of \pkg{selectize.js} diff --git a/R/input-utils.R b/R/input-utils.R index 7df1c6f93f..d7e443d356 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -17,7 +17,7 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues, if (is.null(choices)) { if (is.null(choiceNames) || is.null(choiceValues)) { if (mustExist) { - stop("Please specify a non-empty vector for `choices` (or,", + stop("Please specify a non-empty vector for `choices` (or, ", "alternatively, for both `choiceNames` and `choiceValues`).") } else { # this is useful when we call this function from `updateInputOptions()` diff --git a/man/selectInput.Rd b/man/selectInput.Rd index 7ff4d44b11..f9a509bd29 100644 --- a/man/selectInput.Rd +++ b/man/selectInput.Rd @@ -5,9 +5,8 @@ \alias{selectizeInput} \title{Create a select list input control} \usage{ -selectInput(inputId, label, choices = NULL, selected = NULL, - multiple = FALSE, selectize = TRUE, width = NULL, size = NULL, - choiceNames = NULL, choiceValues = NULL) +selectInput(inputId, label, choices, selected = NULL, multiple = FALSE, + selectize = TRUE, width = NULL, size = NULL) selectizeInput(inputId, ..., options = NULL, width = NULL) } @@ -40,18 +39,6 @@ will result in a taller box. Not compatible with \code{selectize=TRUE}. Normally, when \code{multiple=FALSE}, a select input will be a drop-down list, but when \code{size} is set, it will be a box instead.} -\item{choiceNames, choiceValues}{List of names and values, respectively, -that are displayed to the user in the app and correspond to the each -choice (for this reason, \code{choiceNames} and \code{choiceValues} -must have the same length). If either of these arguments is -provided, then the other \emph{must} be provided and \code{choices} -\emph{must not} be provided. For \code{selectInput} and \code{selectizeInput}, -there isn't any particular advantage to using both of these over -a named list for \code{choices} and it is simply a matter of preference -(this is not always the case; for example, \code{\link{radioButtons}} and -\code{\link{checkboxGroupInput}} can use \code{choiceNames} to pass HTML -code, instead of simple text).} - \item{...}{Arguments passed to \code{selectInput()}.} \item{options}{A list of options. See the documentation of \pkg{selectize.js} From e1a2752256b582fc06240a2bab4bffce34206863 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Mon, 27 Mar 2017 12:16:06 +0100 Subject: [PATCH 20/22] added breaking change NEWS item --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 0d05d43ea2..ae87ee1a38 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,8 @@ shiny 1.0.0.9001 ### Breaking changes +* The functions `radioButtons()`, `checkboxGroupInput()` and `selectInput()` (and the corresponding `updateXXX()` functions) no longer accept a `selected` argument whose value is the name of a choice, instead of the value of the choice. This feature had been soft-deprecated since Shiny 0.10 (printing a warning message, but still trying to match the name to the right choice) and it's now been completely deprecated. + ### New features ### Minor new features and improvements From 724fa36352c3e911d1ee624bf314613833325003 Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Mon, 27 Mar 2017 16:33:50 +0100 Subject: [PATCH 21/22] edge cases and numbers --- NEWS.md | 2 +- R/input-utils.R | 17 +++++++++++------ tests/testthat/test-bootstrap.r | 27 ++++++++++++++++++++------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index ae87ee1a38..fc136be65b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,7 @@ shiny 1.0.0.9001 ### Breaking changes -* The functions `radioButtons()`, `checkboxGroupInput()` and `selectInput()` (and the corresponding `updateXXX()` functions) no longer accept a `selected` argument whose value is the name of a choice, instead of the value of the choice. This feature had been soft-deprecated since Shiny 0.10 (printing a warning message, but still trying to match the name to the right choice) and it's now been completely deprecated. +* The functions `radioButtons()`, `checkboxGroupInput()` and `selectInput()` (and the corresponding `updateXXX()` functions) no longer accept a `selected` argument whose value is the name of a choice, instead of the value of the choice. This feature had been deprecated since Shiny 0.10 (it printed a warning message, but still tried to match the name to the right choice) and it's now completely unsupported. ### New features diff --git a/R/input-utils.R b/R/input-utils.R index d7e443d356..4d30238f2c 100644 --- a/R/input-utils.R +++ b/R/input-utils.R @@ -18,12 +18,17 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues, if (is.null(choiceNames) || is.null(choiceValues)) { if (mustExist) { stop("Please specify a non-empty vector for `choices` (or, ", - "alternatively, for both `choiceNames` and `choiceValues`).") + "alternatively, for both `choiceNames` AND `choiceValues`).") } else { - # this is useful when we call this function from `updateInputOptions()` - # in which case, all three `choices`, `choiceNames` and `choiceValues` - # may legitimately be NULL - return(list(choiceNames = NULL, choiceValues = NULL)) + if (is.null(choiceNames) && is.null(choiceValues)) { + # this is useful when we call this function from `updateInputOptions()` + # in which case, all three `choices`, `choiceNames` and `choiceValues` + # may legitimately be NULL + return(list(choiceNames = NULL, choiceValues = NULL)) + } else { + stop("One of `choiceNames` or `choiceValues` was set to ", + "NULL, but either both or none should be NULL.") + } } } if (length(choiceNames) != length(choiceValues)) { @@ -42,7 +47,7 @@ normalizeChoicesArgs <- function(choices, choiceNames, choiceValues, } return(list(choiceNames = as.list(choiceNames), - choiceValues = as.list(choiceValues))) + choiceValues = as.list(as.character(choiceValues)))) } # generate options for radio buttons and checkbox groups (type = 'checkbox' or diff --git a/tests/testthat/test-bootstrap.r b/tests/testthat/test-bootstrap.r index db8a93239d..004218fbbc 100644 --- a/tests/testthat/test-bootstrap.r +++ b/tests/testthat/test-bootstrap.r @@ -204,7 +204,14 @@ test_that("normalizeChoicesArgs does its job", { # Using unnamed `choiceNames` and `choiceValues` vectors/lists directly expect_equal(normalizeChoicesArgs(NULL, c("one", "two"), c("a", "b")), expected) - expect_equal(normalizeChoicesArgs(NULL, list("one", "two"), list("a", "b")), expected) + expect_equal(normalizeChoicesArgs(NULL, list("one", "two"), list("a", "b")), expected) + + # Numbers + expected <- list(choiceNames = list("a", "b"), choiceValues = list("1", "2")) + expect_equal(normalizeChoicesArgs(c("a" = 1, "b" = 2), NULL, NULL), expected) + expect_equal(normalizeChoicesArgs(list("a" = 1, "b" = 2), NULL, NULL), expected) + expect_equal(normalizeChoicesArgs(NULL, c("a", "b"), c(1, 2)), expected) + expect_equal(normalizeChoicesArgs(NULL, list("a", "b"), list("1", "2")), expected) # Using choiceNames with HTML and choiceValues nms <- list(icon("calendar"), HTML("

Red Text

")) @@ -226,11 +233,17 @@ test_that("normalizeChoicesArgs does its job", { "Using `choices` argument; ignoring `choiceNames` and `choiceValues`.") expect_equal(res, expected) - # Set possibilities to character(0) in an inconsistent way + # Set possibilities to NULL in an inconsistent way + expected <- paste("One of `choiceNames` or `choiceValues` was set to NULL,", + "but either both or none should be NULL.") + expect_error(normalizeChoicesArgs(NULL, character(0), NULL, FALSE), expected, fixed = TRUE) + expect_error(normalizeChoicesArgs(NULL, NULL, character(0), FALSE), expected, fixed = TRUE) + expected <- paste("Please specify a non-empty vector for `choices` (or,", + "alternatively, for both `choiceNames` AND `choiceValues`).") + expect_error(normalizeChoicesArgs(NULL, character(0), NULL), expected, fixed = TRUE) + expect_error(normalizeChoicesArgs(NULL, NULL, character(0)), expected, fixed = TRUE) + + # Set all possibilities to NULL (and mustExist = FALSE) expected <- list(choiceNames = NULL, choiceValues = NULL) - expect_equal(normalizeChoicesArgs(NULL, character(0), NULL), expected) - expect_equal(normalizeChoicesArgs(NULL, NULL, character(0)), expected) - - # Set all possibilities to NULL - expect_equal(normalizeChoicesArgs(NULL, NULL, NULL), expected) + expect_equal(normalizeChoicesArgs(NULL, NULL, NULL, FALSE), expected) }) From c0902f831c309f338961b9faa91849421a21f4bc Mon Sep 17 00:00:00 2001 From: Barbara Borges Ribeiro Date: Mon, 27 Mar 2017 16:39:47 +0100 Subject: [PATCH 22/22] remove call to validateselected --- R/input-select.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/input-select.R b/R/input-select.R index bcd829a2e5..28117f3df1 100644 --- a/R/input-select.R +++ b/R/input-select.R @@ -85,7 +85,7 @@ selectInput <- function(inputId, label, choices, selected = NULL, # default value if it's not specified if (is.null(selected)) { if (!multiple) selected <- firstChoice(choices) - } else selected <- validateSelected(selected, choices, inputId) + } else selected <- as.character(selected) if (!is.null(size) && selectize) { stop("'size' argument is incompatible with 'selectize=TRUE'.")