Compare commits

..

3 Commits

Author SHA1 Message Date
Hadley Wickham
03b03f1173 Update tests and language 2021-03-08 17:49:57 -06:00
Hadley Wickham
28dc3ecd5b Merge commit 'd582e53f73ff61953ee209c870eabb3bc7124288'
Conflicts:
	R/input-slider.R
2021-03-08 17:16:00 -06:00
Hadley Wickham
c3e6fdc550 Perform type check before value check
If you don't validate the input types before validating the values, you get uninformative errors from min/max. (Which comes up in Mastering Shiny)

I also move `getSliderType()` into input-slider.R since it's the only place that it's used, and mildly improved the error message.
2021-01-28 08:16:10 -06:00
9 changed files with 181 additions and 201 deletions

View File

@@ -342,13 +342,20 @@ collapseSizes <- function(padding) {
#' Create a page with a top level navigation bar
#'
#' Create a page that contains a top level navigation bar that can be used to
#' toggle a set of [tabPanel()] elements. `navbarMenu()` can be used to create
#' an embedded menu within the navbar that in turns includes additional
#' `tabPanels`.
#' toggle a set of [tabPanel()] elements.
#'
#' @inheritParams navlistPanel
#' @inheritParams bootstrapPage
#' @param title The title to display in the navbar
#' @param ... [tabPanel()] elements to include in the page. The
#' `navbarMenu` function also accepts strings, which will be used as menu
#' section headers. If the string is a set of dashes like `"----"` a
#' horizontal separator will be displayed in the menu.
#' @param id If provided, you can use `input$`*`id`* in your
#' server logic to determine which of the current tabs is active. The value
#' will correspond to the `value` argument that is passed to
#' [tabPanel()].
#' @param selected The `value` (or, if none was supplied, the `title`)
#' of the tab that should be selected by default. If `NULL`, the first
#' tab will be selected.
#' @param position Determines whether the navbar should be displayed at the top
#' of the page with normal scrolling behavior (`"static-top"`), pinned at
#' the top (`"fixed-top"`), or pinned at the bottom
@@ -366,13 +373,26 @@ collapseSizes <- function(padding) {
#' elements into a menu when the width of the browser is less than 940 pixels
#' (useful for viewing on smaller touchscreen device)
#' @param collapsable Deprecated; use `collapsible` instead.
#' @param fluid `TRUE` to use a fluid layout. `FALSE` to use a fixed
#' layout.
#' @param responsive This option is deprecated; it is no longer optional with
#' Bootstrap 3.
#' @param windowTitle The title that should be displayed by the browser window.
#' Useful if `title` is not a string.
#' @inheritParams bootstrapPage
#' @param icon Optional icon to appear on a `navbarMenu` tab.
#'
#' @seealso [updateNavbarPage()], [insertTab()], [showTab()]
#' @return A UI defintion that can be passed to the [shinyUI] function.
#'
#' @details The `navbarMenu` function can be used to create an embedded
#' menu within the navbar that in turns includes additional tabPanels (see
#' example below).
#'
#' @seealso [tabPanel()], [tabsetPanel()],
#' [updateNavbarPage()], [insertTab()],
#' [showTab()]
#'
#' @family layout functions
#' @family tab layouts
#'
#' @examples
#' navbarPage("App Title",
@@ -603,10 +623,6 @@ helpText <- function(...) {
#' Create a tab panel
#'
#' `tabPanel()` creates a tab panel that can be included within a
#' [tabsetPanel()], [navListPanel()], or [navbarPage()]. `tabPanelBody()`
#' drops the `title`, making it most suitable for use within
#' `tabsetPanel(type = "hidden")`.
#'
#' @param title Display title for tab
#' @param ... UI elements to include within the tab
@@ -615,8 +631,9 @@ helpText <- function(...) {
#' `id`, then the title will be used.
#' @param icon Optional icon to appear on the tab. This attribute is only
#' valid when using a `tabPanel` within a [navbarPage()].
#' @return A tab that can be passed to [tabsetPanel()]
#'
#' @family tab layouts
#' @seealso [tabsetPanel()]
#'
#' @examples
#' # Show a tabset that includes a plot, summary, and
@@ -629,6 +646,7 @@ helpText <- function(...) {
#' )
#' )
#' @export
#' @describeIn tabPanel Create a tab panel that can be included within a [tabsetPanel()] or a [navbarPage()].
tabPanel <- function(title, ..., value = title, icon = NULL) {
div(
class = "tab-pane",
@@ -639,7 +657,8 @@ tabPanel <- function(title, ..., value = title, icon = NULL) {
)
}
#' @export
#' @rdname tabPanel
#' @describeIn tabPanel Create a tab panel that drops the title argument.
#' This function should be used within `tabsetPanel(type = "hidden")`. See [tabsetPanel()] for example usage.
tabPanelBody <- function(value, ..., icon = NULL) {
if (
!is.character(value) ||
@@ -674,8 +693,10 @@ tabPanelBody <- function(value, ..., icon = NULL) {
#' }
#' @param position This argument is deprecated; it has been discontinued in
#' Bootstrap 3.
#' @seealso [updateTabsetPanel()], [insertTab()], [showTab()]
#' @family tab layouts
#' @return A tabset that can be passed to [mainPanel()]
#'
#' @seealso [tabPanel()], [updateTabsetPanel()],
#' [insertTab()], [showTab()]
#'
#' @examples
#' # Show a tabset that includes a plot, summary, and
@@ -751,20 +772,29 @@ tabsetPanel <- function(...,
#' Create a navigation list panel that provides a list of links on the left
#' which navigate to a set of tabPanels displayed to the right.
#'
#' @inheritParams tabsetPanel
#' @param ... [tabPanel()] elements to include in the navbar.
#' Plain strings will be converted to headers.
#' @param ... [tabPanel()] elements to include in the navlist
#' @param id If provided, you can use `input$`*`id`* in your
#' server logic to determine which of the current navlist items is active. The
#' value will correspond to the `value` argument that is passed to
#' [tabPanel()].
#' @param selected The `value` (or, if none was supplied, the `title`)
#' of the navigation item that should be selected by default. If `NULL`,
#' the first navigation will be selected.
#' @param well `TRUE` to place a well (gray rounded rectangle) around the
#' navigation list.
#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed
#' layout.
#' @param widths Column widths of the navigation list and tabset content areas
#' @param widths Column withs of the navigation list and tabset content areas
#' respectively.
#'
#' @details
#' @details You can include headers within the `navlistPanel` by including
#' plain text elements in the list. Versions of Shiny before 0.11 supported
#' separators with "------", but as of 0.11, separators were no longer
#' supported. This is because version 0.11 switched to Bootstrap 3, which
#' doesn't support separators.
#'
#' @seealso [updateTabsetPanel()], [insertTab()], [showTab()]
#' @family tab layouts
#' @seealso [tabPanel()], [updateNavlistPanel()],
#' [insertTab()], [showTab()]
#'
#' @examples
#' fluidPage(

View File

@@ -79,9 +79,12 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
round = FALSE, ticks = TRUE, animate = FALSE,
width = NULL, sep = ",", pre = NULL, post = NULL,
timeFormat = NULL, timezone = NULL, dragRange = TRUE) {
validate_slider_value(min, max, value, "sliderInput")
dataType <- getSliderType(min, max, value)
# Force required arguments for maximally informative errors
inputId; label; min; max; value
validate_slider_value(min, max, value, "sliderInput")
dataType <- slider_type(value)
if (is.null(timeFormat)) {
timeFormat <- switch(dataType, date = "%F", datetime = "%F %T", number = NULL)
@@ -288,37 +291,45 @@ findStepSize <- function(min, max, step) {
}
}
# Throw a warning if ever `value` is not in the [`min`, `max`] range
validate_slider_value <- function(min, max, value, fun) {
if (length(min) != 1 || is_na(min) ||
length(max) != 1 || is_na(max) ||
length(value) < 1 || length(value) > 2 || any(is.na(value)))
{
stop(call. = FALSE,
sprintf("In %s(): `min`, `max`, and `value` cannot be NULL, NA, or empty.", fun)
if (!is_slider_type(min) || length(min) != 1 || is_na(min)) {
rlang::abort("sliderInput(min) must be a single number, Date, or POSIXct")
}
if (!is_slider_type(min) || length(max) != 1 || is_na(max)) {
rlang::abort("sliderInput(value) must be a single number, Date, or POSIXct")
}
if (!is_slider_type(value) || !length(value) %in% c(1, 2) || any(is_na(value))) {
rlang::abort(
"sliderInput(value) must be a single or pair of numbers, Dates, or POSIXcts"
)
}
if (min(value) < min) {
warning(call. = FALSE,
sprintf(
"In %s(): `value` should be greater than or equal to `min` (value = %s, min = %s).",
fun, paste(value, collapse = ", "), min
)
)
if (!identical(class(min), class(value)) || !identical(class(max), class(value))) {
rlang::abort(c(
"Type mismatch for `min`, `max`, and `value`.",
i = "All values must have same type: either numeric, Date, or POSIXt."
))
}
if (max(value) > max) {
warning(
noBreaks. = TRUE, call. = FALSE,
sprintf(
"In %s(): `value` should be less than or equal to `max` (value = %s, max = %s).",
fun, paste(value, collapse = ", "), max
)
)
if (min(value) < min || max(value) > max) {
rlang::abort("`value` does not lie within [min, max]")
}
}
is_slider_type <- function(x) {
is.numeric(x) || inherits(x, "Date") || inherits(x, "POSIXct")
}
slider_type <- function(x) {
if (is.numeric(x)) {
"number"
} else if (inherits(x, "Date")) {
"date"
} else if (inherits(x, "POSIXct")) {
"datetime"
}
}
#' @rdname sliderInput
#'

View File

@@ -1751,23 +1751,6 @@ createVarPromiseDomain <- function(env, name, value) {
)
}
getSliderType <- function(min, max, value) {
vals <- dropNulls(list(value, min, max))
if (length(vals) == 0) return("")
type <- unique(lapply(vals, function(x) {
if (inherits(x, "Date")) "date"
else if (inherits(x, "POSIXt")) "datetime"
else "number"
}))
if (length(type) > 1) {
rlang::abort(c(
"Type mismatch for `min`, `max`, and `value`.",
"All values must either be numeric, Date, or POSIXt."
))
}
type[[1]]
}
# Reads the `shiny.sharedSecret` global option, and returns a function that can
# be used to test header values for a match.
loadSharedSecret <- function() {

View File

@@ -28,8 +28,10 @@ navbarMenu(title, ..., menuName = title, icon = NULL)
\arguments{
\item{title}{The title to display in the navbar}
\item{...}{\code{\link[=tabPanel]{tabPanel()}} elements to include in the navbar.
Plain strings will be converted to headers.}
\item{...}{\code{\link[=tabPanel]{tabPanel()}} elements to include in the page. The
\code{navbarMenu} function also accepts strings, which will be used as menu
section headers. If the string is a set of dashes like \code{"----"} a
horizontal separator will be displayed in the menu.}
\item{id}{If provided, you can use \verb{input$}\emph{\code{id}} in your
server logic to determine which of the current tabs is active. The value
@@ -63,7 +65,7 @@ elements into a menu when the width of the browser is less than 940 pixels
\item{collapsable}{Deprecated; use \code{collapsible} instead.}
\item{fluid}{\code{TRUE} to use fluid layout; \code{FALSE} to use fixed
\item{fluid}{\code{TRUE} to use a fluid layout. \code{FALSE} to use a fixed
layout.}
\item{responsive}{This option is deprecated; it is no longer optional with
@@ -91,11 +93,17 @@ is needed if you want to insert/remove or show/hide an entire
\item{icon}{Optional icon to appear on a \code{navbarMenu} tab.}
}
\value{
A UI defintion that can be passed to the \link{shinyUI} function.
}
\description{
Create a page that contains a top level navigation bar that can be used to
toggle a set of \code{\link[=tabPanel]{tabPanel()}} elements. \code{navbarMenu()} can be used to create
an embedded menu within the navbar that in turns includes additional
\code{tabPanels}.
toggle a set of \code{\link[=tabPanel]{tabPanel()}} elements.
}
\details{
The \code{navbarMenu} function can be used to create an embedded
menu within the navbar that in turns includes additional tabPanels (see
example below).
}
\examples{
navbarPage("App Title",
@@ -115,7 +123,9 @@ navbarPage("App Title",
)
}
\seealso{
\code{\link[=updateNavbarPage]{updateNavbarPage()}}, \code{\link[=insertTab]{insertTab()}}, \code{\link[=showTab]{showTab()}}
\code{\link[=tabPanel]{tabPanel()}}, \code{\link[=tabsetPanel]{tabsetPanel()}},
\code{\link[=updateNavbarPage]{updateNavbarPage()}}, \code{\link[=insertTab]{insertTab()}},
\code{\link[=showTab]{showTab()}}
Other layout functions:
\code{\link{fillPage}()},
@@ -125,11 +135,5 @@ Other layout functions:
\code{\link{sidebarLayout}()},
\code{\link{splitLayout}()},
\code{\link{verticalLayout}()}
Other tab layouts:
\code{\link{navlistPanel}()},
\code{\link{tabPanel}()},
\code{\link{tabsetPanel}()}
}
\concept{layout functions}
\concept{tab layouts}

View File

@@ -14,17 +14,16 @@ navlistPanel(
)
}
\arguments{
\item{...}{\code{\link[=tabPanel]{tabPanel()}} elements to include in the navbar.
Plain strings will be converted to headers.}
\item{...}{\code{\link[=tabPanel]{tabPanel()}} elements to include in the navlist}
\item{id}{If provided, you can use \verb{input$}\emph{\code{id}} in your
server logic to determine which of the current tabs is active. The value
will correspond to the \code{value} argument that is passed to
server logic to determine which of the current navlist items is active. The
value will correspond to the \code{value} argument that is passed to
\code{\link[=tabPanel]{tabPanel()}}.}
\item{selected}{The \code{value} (or, if none was supplied, the \code{title})
of the tab that should be selected by default. If \code{NULL}, the first
tab will be selected.}
of the navigation item that should be selected by default. If \code{NULL},
the first navigation will be selected.}
\item{well}{\code{TRUE} to place a well (gray rounded rectangle) around the
navigation list.}
@@ -32,7 +31,7 @@ navigation list.}
\item{fluid}{\code{TRUE} to use fluid layout; \code{FALSE} to use fixed
layout.}
\item{widths}{Column widths of the navigation list and tabset content areas
\item{widths}{Column withs of the navigation list and tabset content areas
respectively.}
}
\description{
@@ -40,7 +39,11 @@ Create a navigation list panel that provides a list of links on the left
which navigate to a set of tabPanels displayed to the right.
}
\details{
You can include headers within the \code{navlistPanel} by including
plain text elements in the list. Versions of Shiny before 0.11 supported
separators with "------", but as of 0.11, separators were no longer
supported. This is because version 0.11 switched to Bootstrap 3, which
doesn't support separators.
}
\examples{
fluidPage(
@@ -56,11 +59,6 @@ fluidPage(
)
}
\seealso{
\code{\link[=updateTabsetPanel]{updateTabsetPanel()}}, \code{\link[=insertTab]{insertTab()}}, \code{\link[=showTab]{showTab()}}
Other tab layouts:
\code{\link{navbarPage}()},
\code{\link{tabPanel}()},
\code{\link{tabsetPanel}()}
\code{\link[=tabPanel]{tabPanel()}}, \code{\link[=updateNavlistPanel]{updateNavlistPanel()}},
\code{\link[=insertTab]{insertTab()}}, \code{\link[=showTab]{showTab()}}
}
\concept{tab layouts}

View File

@@ -21,12 +21,20 @@ that this tab is selected. If omitted and \code{tabsetPanel} has an
\item{icon}{Optional icon to appear on the tab. This attribute is only
valid when using a \code{tabPanel} within a \code{\link[=navbarPage]{navbarPage()}}.}
}
\description{
\code{tabPanel()} creates a tab panel that can be included within a
\code{\link[=tabsetPanel]{tabsetPanel()}}, \code{\link[=navListPanel]{navListPanel()}}, or \code{\link[=navbarPage]{navbarPage()}}. \code{tabPanelBody()}
drops the \code{title}, making it most suitable for use within
\code{tabsetPanel(type = "hidden")}.
\value{
A tab that can be passed to \code{\link[=tabsetPanel]{tabsetPanel()}}
}
\description{
Create a tab panel
}
\section{Functions}{
\itemize{
\item \code{tabPanel}: Create a tab panel that can be included within a \code{\link[=tabsetPanel]{tabsetPanel()}} or a \code{\link[=navbarPage]{navbarPage()}}.
\item \code{tabPanelBody}: Create a tab panel that drops the title argument.
This function should be used within \code{tabsetPanel(type = "hidden")}. See \code{\link[=tabsetPanel]{tabsetPanel()}} for example usage.
}}
\examples{
# Show a tabset that includes a plot, summary, and
# table view of the generated distribution
@@ -39,9 +47,5 @@ mainPanel(
)
}
\seealso{
Other tab layouts:
\code{\link{navbarPage}()},
\code{\link{navlistPanel}()},
\code{\link{tabsetPanel}()}
\code{\link[=tabsetPanel]{tabsetPanel()}}
}
\concept{tab layouts}

View File

@@ -35,6 +35,9 @@ active tab via other input controls. (See example below)}
\item{position}{This argument is deprecated; it has been discontinued in
Bootstrap 3.}
}
\value{
A tabset that can be passed to \code{\link[=mainPanel]{mainPanel()}}
}
\description{
Create a tabset that contains \code{\link[=tabPanel]{tabPanel()}} elements. Tabsets are
useful for dividing output into multiple independently viewable sections.
@@ -80,11 +83,6 @@ if (interactive()) {
}
}
\seealso{
\code{\link[=updateTabsetPanel]{updateTabsetPanel()}}, \code{\link[=insertTab]{insertTab()}}, \code{\link[=showTab]{showTab()}}
Other tab layouts:
\code{\link{navbarPage}()},
\code{\link{navlistPanel}()},
\code{\link{tabPanel}()}
\code{\link[=tabPanel]{tabPanel()}}, \code{\link[=updateTabsetPanel]{updateTabsetPanel()}},
\code{\link[=insertTab]{insertTab()}}, \code{\link[=showTab]{showTab()}}
}
\concept{tab layouts}

View File

@@ -0,0 +1,28 @@
# sliderInput gives informative errors for bad inputs
Code
sliderInput("x", "x")
Error <simpleError>
argument "min" is missing, with no default
Code
sliderInput("x", "x", min = NULL, max = 3, value = 2)
Error <rlang_error>
sliderInput(min) must be a single number, Date, or POSIXct
Code
sliderInput("x", "x", min = 1, max = NULL, value = 2)
Error <rlang_error>
sliderInput(value) must be a single number, Date, or POSIXct
Code
sliderInput("x", "x", min = 1, max = 3, value = NULL)
Error <rlang_error>
sliderInput(value) must be a single or pair of numbers, Dates, or POSIXcts
Code
sliderInput("x", "x", min = Sys.Date(), max = Sys.Date(), value = 1)
Error <rlang_error>
Type mismatch for `min`, `max`, and `value`.
i All values must have same type: either numeric, Date, or POSIXt.
Code
sliderInput("x", "x", min = 1, max = 3, value = 0)
Error <rlang_error>
`value` does not lie within [min, max]

View File

@@ -4,99 +4,23 @@ test_that("sliderInput steps don't have rounding errors", {
expect_identical(findStepSize(-5.5, 4, NULL), 0.1)
})
test_that("sliderInput can use numbers, dates, or POSIXct", {
n <- 1
d <- Sys.Date()
dt <- Sys.time()
test_that("sliderInput validation", {
# Number
x <- 10
expect_silent(sliderInput('s', 's', x-1, x+1, x))
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
expect_error(sliderInput('s', 's', x-1, x+1))
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
expect_error(sliderInput('s', 's', x-1, NULL, x))
expect_error(sliderInput('s', 's', NULL, x+1, x))
expect_error(sliderInput('s', 's', NULL, NULL, x))
expect_error(sliderInput('s', 's', x-1, x+1, NA_real_))
expect_error(sliderInput('s', 's', x-1, x+1, c(x, NA_real_)))
# Date
x <- Sys.Date()
expect_silent(sliderInput('s', 's', x-1, x+1, x))
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
expect_error(sliderInput('s', 's', x-1, x+1))
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
expect_error(sliderInput('s', 's', x-1, NULL, x))
expect_error(sliderInput('s', 's', NULL, x+1, x))
expect_error(sliderInput('s', 's', NULL, NULL, x))
expect_error(sliderInput('s', 's', x-1, x+1, as.Date(NA)))
# POSIXct
x <- Sys.time()
expect_silent(sliderInput('s', 's', x-1, x+1, x))
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
expect_error(sliderInput('s', 's', x-1, x+1))
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
expect_error(sliderInput('s', 's', x-1, NULL, x))
expect_error(sliderInput('s', 's', NULL, x+1, x))
expect_error(sliderInput('s', 's', NULL, NULL, x))
# POSIXLt
x <- as.POSIXlt(Sys.time())
expect_silent(sliderInput('s', 's', x-1, x+1, x))
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
if (getRversion() >= "4.0") {
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
} else {
skip("c() doesn't work sensibly on POSIXlt objects with this version of R")
}
expect_error(sliderInput('s', 's', x-1, x+1))
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
expect_error(sliderInput('s', 's', x-1, NULL, x))
expect_error(sliderInput('s', 's', NULL, x+1, x))
expect_error(sliderInput('s', 's', NULL, NULL, x))
# Size
x <- 10
## length 0
expect_error(sliderInput('s', 's', x-1, x+1, numeric(0)))
expect_error(sliderInput('s', 's', x-1, numeric(0), x))
expect_error(sliderInput('s', 's', numeric(0), x+1, x))
## length 1
expect_silent(sliderInput('s', 's', x-1, x+1, x))
## length 2
expect_silent(sliderInput('s', 's', x-1, x+1, c(x, x)))
## length 3+
expect_error(sliderInput('s', 's', x-1, x+1, c(x, x, x)))
expect_error(sliderInput('s', 's', x-1, c(x, x, x), x))
expect_error(sliderInput('s', 's', c(x, x, x), x+1, x))
expect_error(sliderInput("x", "x", n - 1, n + 1, n), NA)
expect_error(sliderInput("x", "x", d - 1, d + 1, d), NA)
expect_error(sliderInput("x", "x", dt - 1, dt + 1, dt), NA)
})
test_that("sliderInput gives informative errors for bad inputs", {
expect_snapshot(error = TRUE, {
sliderInput("x", "x")
sliderInput("x", "x", min = NULL, max = 3, value = 2)
sliderInput("x", "x", min = 1, max = NULL, value = 2)
sliderInput("x", "x", min = 1, max = 3, value = NULL)
sliderInput("x", "x", min = Sys.Date(), max = Sys.Date(), value = 1)
sliderInput("x", "x", min = 1, max = 3, value = 0)
})
})