diff --git a/DESCRIPTION b/DESCRIPTION index f51a8ebb3..cf5061a29 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -90,8 +90,9 @@ Suggests: URL: http://shiny.rstudio.com BugReports: https://github.com/rstudio/shiny/issues Remotes: + tidyverse/ggplot2, rstudio/httpuv -Collate: +Collate: 'app.R' 'bookmark-state-local.R' 'stack.R' diff --git a/R/input-select.R b/R/input-select.R index 28117f3df..c0f457e4e 100644 --- a/R/input-select.R +++ b/R/input-select.R @@ -33,7 +33,7 @@ #' @return A select list control that can be added to a UI definition. #' #' @family input elements -#' @seealso \code{\link{updateSelectInput}} +#' @seealso \code{\link{updateSelectInput}} \code{\link{varSelectInput}} #' #' @examples #' ## Only run examples in interactive R sessions @@ -212,3 +212,121 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) { attachDependencies(select, selectizeDep) } + + + + + + + + +#' Create a select list input control from a data.frame +#' +#' Create a select list that can be used to choose a single or multiple items +#' from the column names of a data frame. +#' +#' The resulting output value will be returned as a symbol or a list of symbols +#' given the \code{multiple} is \code{FALSE} or \code{TRUE} respectively. With +#' \code{multiple} set to \code{FALSE}, the corresponding output should be used with \code{!!} within tidy evaluation. With +#' \code{multiple} set to \code{TRUE}, the corresponding output should be used with \code{!!!} within tidy evaluation. +#' +#' By default, \code{varSelectInput()} and \code{selectizeInput()} use the +#' JavaScript library \pkg{selectize.js} +#' (\url{https://github.com/selectize/selectize.js}) to instead of the basic +#' select input element. To use the standard HTML select input element, use +#' \code{selectInput()} with \code{selectize=FALSE}. +#' +#' @inheritParams selectInput +#' @param data A data frame. Used to retrieve the column names as choices for a \code{\link{selectInput}} +#' @return A variable select list control that can be added to a UI definition. +#' +#' @family input elements +#' @seealso \code{\link{updateSelectInput}} +#' @examples +#' +#' ## Only run examples in interactive R sessions +#' if (interactive()) { +#' +#' # single selection +#' shinyApp( +#' ui = fluidPage( +#' varSelectInput("variable", "Variable:", mtcars), +#' plotOutput("data") +#' ), +#' server = function(input, output) { +#' output$data <- renderPlot({ +#' ggplot2::qplot(!!input$variable, data = mtcars) +#' }) +#' } +#' ) +#' +#' +#' # multiple selections +#' \dontrun{ +#' shinyApp( +#' ui = fluidPage( +#' varSelectInput("variable", "Variable:", mtcars, multiple = TRUE), +#' tableOutput("data") +#' ), +#' server = function(input, output) { +#' output$data <- renderTable({ +#' if (length(input$variable) == 0) return(mtcars) +#' mtcars %>% dplyr::select(!!!input$variable) +#' }, rownames = TRUE) +#' } +#' )} +#' +#' } +#' @export +varSelectInput <- function( + inputId, label, data, selected = NULL, + multiple = FALSE, selectize = TRUE, width = NULL, + size = NULL +) { + # no place holders + choices <- colnames(data) + + selectInputVal <- selectInput( + inputId = inputId, + label = label, + choices = choices, + selected = selected, + multiple = multiple, + selectize = selectize, + width = width, + size = size + ) + + # set the select tag class to be "symbol" + selectAttribs <- selectInputVal$children[[2]]$children[[1]]$attribs + selectInputVal$children[[2]]$children[[1]]$attribs$class <- "symbol" + + selectInputVal +} + + + +#' @rdname varSelectInput +#' @param ... Arguments passed to \code{varSelectInput()}. +#' @param options A list of options. See the documentation of \pkg{selectize.js} +#' for possible options (character option values inside \code{\link[base]{I}()} will +#' be treated as literal JavaScript code; see \code{\link{renderDataTable}()} +#' for details). +#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'}; +#' see \code{\link{validateCssUnit}}. +#' @note The variable selectize input created from \code{varSelectizeInput()} allows +#' deletion of the selected option even in a single select input, which will +#' return an empty string as its value. This is the default behavior of +#' \pkg{selectize.js}. However, the selectize input created from +#' \code{selectInput(..., selectize = TRUE)} will ignore the empty string +#' value when it is a single choice input and the empty string is not in the +#' \code{choices} argument. This is to keep compatibility with +#' \code{selectInput(..., selectize = FALSE)}. +#' @export +varSelectizeInput <- function(inputId, ..., options = NULL, width = NULL) { + selectizeIt( + inputId, + varSelectInput(inputId, ..., selectize = FALSE, width = width), + options + ) +} diff --git a/R/server-input-handlers.R b/R/server-input-handlers.R index cc602e5b2..e1fab4431 100644 --- a/R/server-input-handlers.R +++ b/R/server-input-handlers.R @@ -142,6 +142,7 @@ registerInputHandler("shiny.matrix", function(data, ...) { return(m) }) + registerInputHandler("shiny.number", function(val, ...){ ifelse(is.null(val), NA, val) }) @@ -220,3 +221,21 @@ registerInputHandler("shiny.file", function(val, shinysession, name) { val }) + + +# to be used with !!!answer +registerInputHandler("shiny.symbolList", function(val, ...) { + if (is.null(val)) { + list() + } else { + lapply(val, as.symbol) + } +}) +# to be used with !!answer +registerInputHandler("shiny.symbol", function(val, ...) { + if (is.null(val) || identical(val, "")) { + NULL + } else { + as.symbol(val) + } +}) diff --git a/R/update-input.R b/R/update-input.R index 5c13fe41d..ed987051d 100644 --- a/R/update-input.R +++ b/R/update-input.R @@ -576,7 +576,7 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL, #' @template update-input #' @inheritParams selectInput #' -#' @seealso \code{\link{selectInput}} +#' @seealso \code{\link{selectInput}} \code{\link{varSelectInput}} #' #' @examples #' ## Only run examples in interactive R sessions @@ -673,6 +673,43 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL, )) session$sendInputMessage(inputId, message) } +#' @rdname updateSelectInput +#' @inheritParams varSelectInput +#' @export +updateVarSelectInput <- function(session, inputId, label = NULL, data = NULL, selected = NULL) { + if (is.null(data)) { + choices <- NULL + } else { + choices <- colnames(data) + } + updateSelectInput( + session = session, + inputId = inputId, + label = label, + choices = choices, + selected = selected + ) +} +#' @rdname updateSelectInput +#' @export +updateVarSelectizeInput <- function(session, inputId, label = NULL, data = NULL, selected = NULL, options = list(), server = FALSE) { + if (is.null(data)) { + choices <- NULL + } else { + choices <- colnames(data) + } + updateSelectizeInput( + session = session, + inputId = inputId, + label = label, + choices = choices, + selected = selected, + options = options, + server = server + ) +} + + selectizeJSON <- function(data, req) { query <- parseQueryString(req$QUERY_STRING) diff --git a/srcjs/input_binding_select.js b/srcjs/input_binding_select.js index 497f94de5..81cbe979a 100644 --- a/srcjs/input_binding_select.js +++ b/srcjs/input_binding_select.js @@ -3,6 +3,18 @@ $.extend(selectInputBinding, { find: function(scope) { return $(scope).find('select'); }, + getType: function(el) { + var $el = $(el); + if (!$el.hasClass("symbol")) { + // default character type + return null; + } + if ($el.attr("multiple") === "multiple") { + return 'shiny.symbolList'; + } else { + return 'shiny.symbol'; + } + }, getId: function(el) { return InputBinding.prototype.getId.call(this, el) || el.name; },