Chapter 15 Modules
Exercise 15.6.1
Example passing input$foo to reactive and it not working.
Solution.
Solution
I don’t really know what this question is asking, but I think the point is to remember:
The main challenge with this sort of code is remembering when you use the reactive (e.g. x$value) vs. when you use its value (e.g. x$value()). Just remember that when passing an argument to a module, you want the module to react to the value changing which means that you have to pass the reactive, not it’s current value.
Where in this scenario, input$foo
is analogous to x$value
.
Exercise 15.6.2
Rewrite selectVarServer()
so that both data and filter are reactive. Pair it with a app function that lets the user pick the dataset with the dataset module, a function with an inputSelect()
that lets the user filter for numeric, character, or factor variables.
Solution.
Solution
The modules datasetInput
, datasetServer
, and selectVarInput
are the same, as well as the find_vars
function.
We can start by creating selectFilterInput
which has the filtering options as choices, and selectFilterServer
which returns the filtering function given the selected choice string.
# create a filter selection input
<- function(id) {
selectFilterInput selectInput(NS(id, "filter"), "Filter",
choices = c("Numeric", "Character", "Factor"),
selected = "Numeric")
}
# switch the function to be applied within the server
<- function(id) {
selectFilterServer moduleServer(id, function(input, output, session) {
eventReactive(input$filter, {
switch(input$filter,
"Numeric" = is.numeric,
"Character" = is.character,
"Factor" = is.factor
)
})
}) }
Now we can update the selectVarServer
to take on an additional filter
argument, and change the update function to not only observe when the data
reactive changes but also our new filter
widget changes. Lastly we pass in the filter reactive to the find_vars
function.
<- function(id, data, filter) { # filter argument
selectVarServer moduleServer(id, function(input, output, session) {
observeEvent({
data()
filter() #observe changes in filter reactive
}, {updateSelectInput(session, "var", choices = find_vars(data(), filter())) # filter as reactive
})reactive(data()[[input$var]])
}) }
Putting it together, we add our new module to the UI and server, and by saving the result of the selectFilterServer
to filt
we can pass that to the selectVarServer
<- function() {
selectVarApp <- fluidPage(
ui datasetInput("data", is.data.frame),
# call the new filter UI
selectFilterInput("filter"),
selectVarInput("var"),
verbatimTextOutput("out")
)<- function(input, output, session) {
server <- datasetServer("data")
data # store the filtering function as a reactive
<- selectFilterServer("filter")
filt # pass the reactive to the select module
<- selectVarServer("var", data, filter = filt)
var $out <- renderPrint(var())
output
}
shinyApp(ui, server)
}
Exercise 15.6.3
The following code defines output and server components of a module that takes a numeric input and produces a bulleted list of three summary statistics. Create an app function that allows you to experiment with it. The app function should take a data frame as input, and use numericVarSelectInput()
to pick the variable to summarise.
<- function(id) {
summaryOuput $ul(
tags$li("Min: ", textOutput(NS(id, "min"), inline = TRUE)),
tags$li("Max: ", textOutput(NS(id, "max"), inline = TRUE)),
tags$li("Missing: ", textOutput(NS(id, "n_na"), inline = TRUE))
tags
)
}
<- function(id, var) {
summaryServer moduleServer(id, function(input, output, session) {
<- reactive({
rng req(var())
range(var(), na.rm = TRUE)
})
$min <- renderText(rng()[[1]])
output$max <- renderText(rng()[[2]])
output$n_na <- renderText(sum(is.na(var())))
output
}) }
Solution.
Solution
We only need to add the code above to the selectVarApp()
example in the book, and adapt the app code to include the summaryOutput
instead of the verbatimTextOutput
, and on the server side pass var
to the summaryServer
function instead of to the text output.
<- function(filter = is.numeric) {
selectVarApp <- fluidPage(
ui datasetInput("data", is.data.frame),
selectVarInput("var"),
summaryOutput("summary")
)<- function(input, output, session) {
server <- datasetServer("data")
data <- selectVarServer("var", data, filter = filter)
var summaryServer("summary", var)
}
shinyApp(ui, server)
}
selectVarApp()
Exercise 15.6.4
The following module input provides a text control that lets you type a date in ISO8601 format (yyyy-mm-dd). Complete the module by providing a server function that uses output$error to display a message if the entered value is not a valid date. The module should return a Date object for valid dates. (Hint: use strptime(x, “%Y-%m-%d”) to parse the string; it will return NA if the value isn’t a valid date.)
<- function(id, label) {
ymdDateUI <- paste0(label, " (yyyy-mm-dd)")
label
fluidRow(
textInput(NS(id, "date"), label),
textOutput(NS(id, "error"))
) }
Solution.
Solution
We create a ymdDateServer
function that renders the error if strptime(input$date, "%Y-%m-%d")
is NA
.
<- function(id, label) {
ymdDateServer moduleServer(id, function(input, output, session) {
$error <- renderText({
outputprint(input$date)
print(strptime(input$date, "%Y-%m-%d"))
if (!is.na(strptime(input$date, "%Y-%m-%d")) | input$date == "") {
NULL
else {
} "Entered value is not a proper date"
}
})
}) }
We put the UI
and Server
code together in the ymdApp
function below:
<- function(filter = is.numeric) {
ymdApp <- fluidPage(
ui ymdDateUI("ymd", "Time")
)<- function(input, output, session) {
server ymdDateServer("ymd")
}shinyApp(ui, server)
}ymdApp()
Exercise 15.6.5
In radioExtraServer()
, return a list that contains both the value and whether or not it came from other.
Solution.
Solution
We can adapt the reactive we return from radioExtraServer
to return both the reactive and whether it came from the primary button choices or not as a list.
<- function(id) {
radioExtraServer moduleServer(id, function(input, output, session) {
observeEvent(input$other, ignoreInit = TRUE, {
updateRadioButtons(session, "primary", selected = "other")
})
<- reactive({
selected if (input$primary == "other") {
$other
inputelse {
} $primary
input
}
})
# return the selected reactive inside a list
# adding whether it came from primary or not
list(selected =
reactive({
if (input$primary == "other") {
$other
inputelse {
} $primary
input
}
}),primary =
reactive(input$primary != "other")
)
}) }
In doing so, we need to adapt the radioExtraApp
code to return extra$selected()
rather than extra
.
<- function(...) {
radioExtraApp <- fluidPage(
ui radioExtraUI("extra", NULL, ...),
textOutput("value")
)<- function(input, output, server) {
server <- radioExtraServer("extra")
extra $value <- renderText({
outputpaste0("Selected: ", extra$selected())
})
}
shinyApp(ui, server)
}radioExtraApp(c("a", "b", "c"))