Capture selectize Input value in R shiny module - r

I am building a shiny app with a selectize input.
The choices in the input are dependent upon the ids in the underlying data.
In my real app, the data updates with a call to an API.
I would like the selected id choice in the selectize input to hold constant when I hit the "update data" button.
I was able to do this prior to using shiny modules. However, when I tried to transform my code to use a shiny module, it fails to hold the selected id value, and resets the selectize input each time I update the underlying data.
The following example was helpful without the module, but when I use the module it doesn't seem to work...link here
Below is a reprex. Thanks for any help.
library(shiny)
library(tidyverse)
# module UI
mymod_ui <- function(id){
ns <- NS(id)
tagList(
uiOutput(ns("ids_lookup")),
)
}
# module server
mymod_server <- function(input, output, session, data, actionb){
ns <-session$ns
ids <- reactive(
data() %>%
filter(!is.na(first_name) & !is.na(last_name) & !is.na(ages)) %>%
mutate(ids = paste(first_name, last_name, sep = " ")) %>%
select(ids)
)
output$ids_lookup <- renderUI({
selectizeInput(ns("lookup"),
label = "Enter id:",
choices = c("Type here ...", ids()), multiple = FALSE)
})
# here is where I would like to hold on to the selected ids when updating the table
# when I click the "reload_data" button I don't want the name to change
# I pass the button from the main server section into the module
current_id_selection <- reactiveVal("NULL")
observeEvent(actionb(), {
current_id_selection(ns(input$ids_lookup))
updateSelectizeInput(session,
inputId = ns("lookup"),
choices = ids(),
selected = current_id_selection())
})
}
ui <- fluidPage(
titlePanel("Test module app"),
br(),
# this button reloads the data
actionButton(
inputId = "reload_data",
label = "Reload data"
),
br(),
br(),
# have a look at the data
h4("Raw data"),
tableOutput("mytable"),
br(),
# now select a single id for further analysis in a much larger app
mymod_ui("mymod"),
)
server <- function(input, output, session) {
df <- eventReactive(input$reload_data, {
# in reality, df is a dataframe which is updated from an API call everytime you press the action button
df <- tibble(
first_name = c("john", "james", "jenny", "steph"),
last_name = c("x", "y", "z", NA),
ages = runif(4, 30, 60)
)
return(df)
}
)
output$mytable <- renderTable({
df()
})
# make the reload data button a reactive val that can be passed to the module for the selectize Input
mybutton <- reactive(input$reload_data)
callModule(mymod_server, "mymod", data = df, actionb = mybutton)
}
shinyApp(ui, server)

Just using inputId = "lookup" instead of inputId = ns("lookup") in updateSelectizeInput() will do it. Also, you had another typo in there. Try this
library(shiny)
library(tidyverse)
# module UI
mymod_ui <- function(id){
ns <- NS(id)
tagList(
uiOutput(ns("ids_lookup")),
verbatimTextOutput(ns("t1"))
)
}
# module server
mymod_server <- function(input, output, session, data, actionb){
ns <-session$ns
ids <- reactive(
data() %>%
filter(!is.na(first_name) & !is.na(last_name) & !is.na(ages)) %>%
mutate(ids = paste(first_name, last_name, sep = " ")) %>%
select(ids)
)
output$ids_lookup <- renderUI({
selectizeInput(ns("lookup"),
label = "Enter id:",
choices = c("Type here ...", ids()), multiple = FALSE)
})
# here is where I would like to hold on to the selected ids when updating the table
# when I click the "reload_data" button I don't want the name to change
# I pass the button from the main server section into the module
current_id_selection <- reactiveValues(v=NULL)
observeEvent(actionb(), {
req(input$lookup)
current_id_selection$v <- input$lookup
output$t1 <- renderPrint(paste0("Current select is ",current_id_selection$v))
updateSelectizeInput(session,
inputId = "lookup",
choices = ids(),
selected = current_id_selection$v )
})
}
ui <- fluidPage(
titlePanel("Test module app"),
br(),
# this button reloads the data
actionButton(inputId = "reload_data", label = "Reload data"
),
br(),
br(),
# have a look at the data
h4("Raw data"),
tableOutput("mytable"),
br(),
# now select a single id for further analysis in a much larger app
mymod_ui("mymod")
)
server <- function(input, output, session) {
df <- eventReactive(input$reload_data, {
# in reality, df is a dataframe which is updated from an API call everytime you press the action button
df <- tibble(
first_name = c("john", "james", "jenny", "steph"),
last_name = c("x", "y", "z", NA),
ages = runif(4, 30, 60)
)
return(df)
})
output$mytable <- renderTable({
df()
})
# make the reload data button a reactive val that can be passed to the module for the selectize Input
mybutton <- reactive(input$reload_data)
callModule(mymod_server, "mymod", data = df, actionb = mybutton)
}
shinyApp(ui, server)

Related

Rshiny Server Side Modularity Using SQL: Filtered Data Table Not Rendering

So far I made a Shiny app with the following procedures/features:
global.R: Connects to the database using pool in R and retrieves min and max date which will be used in the server side
ui.R: I created two tabs but will only include tab2 here. tab2 has three dropdown inputs and a filtered data table based on these inputs
ui_tab2.R: Defined the three inputs explained in ui.R:
var_lab_tab2: A static dropdown input with only two choices Choice1 and Choice2
daterange_tab2_ui: A date range
subid_dropdown_tab2_ui: The last dropdown input that depends on the first two
server_tab2.R:
Function1 dropdownTab2Server:
Defined the date range logic with id daterange_tab2
Defined the last input dropdown logic with id var_list_tab2
Function2 filteredDataTableTab2Server (This part is not working):
Fetch the filtered data using SQL based on the three inputs
So far everything is working except for filteredDataTableTab2Server which is returning an empty data table. I think the problem is related to the dynamic sql part inside glue_sql. Any help would be of great help.
##### 1st module: global.R
#### Libraries
library(pool)
library(dplyr)
library(shiny)
library(shinyjs)
library(shinydashboard)
library(shinycssloaders)
library(glue)
library(tidyr)
library(DBI)
library(reactable)
library(tidyverse)
#### Source
source("ui_tab2.R", local = T)
source("server_tab2.R", local = T)
# Assume we made our pooled object and saved it as "pool"
min_max_date <- pool %>%
tbl("table1") %>%
summarise(
max_date = max(timestamp, na.rm = T)
)
min_max_date_df <- as.data.frame(min_max_date) %>%
mutate(
min_date = as.Date("2022-01-01"),
max_date = as.Date(max_date)
) %>%
select(c(min_date, max_date))
##### 2nd module: ui.R
dashboardPage(dashboardHeader(
title = "title",
),
dashboardSidebar(
collapsed = F,
sidebarMenu(
menuItem("tab1_title", tabName = "tab1"),
menuItem("tab2_title", tabName = "tab2")
)
),
dashboardBody(
useShinyjs(),
tabItems(
tabItem(
tabName = "tab2",
dropdownTab2UI("dropdown_ui_tab2"),
reactableOutput("table1_tab2"),
)
)
)
)
##### 3rd module: ui_tab2.R
dropdownTab2UI <- function(id) {
ns <- NS(id)
tagList(
div(
shinyWidgets::pickerInput(
ns("var_lab_tab2"),
"ID:",
choices = c("Choice1", "Choice2"),
options = shinyWidgets::pickerOptions(
actionsBox = T,
header = "Close",
liveSearch = T
),
multiple = T
)
),
uiOutput(ns("daterange_tab2_ui")),
uiOutput(ns("subid_dropdown_tab2_ui"))
)
}
###### 4th module: server.R
function(input, output, session) {
dropdownTab2Server("dropdown_ui_tab2")
myvars <- dropdownTab2Server("dropdown_ui_tab2")
# This part is not working. The error message is "Error in as.vector:
# cannot coerce type 'closure' to vector of type 'character'".
# If I remove ```reactive```, then it works but it returns an empty data table.
data_tab2 <- filteredDataTableTab2Server(
id = "table1_tab2",
input1 = reactive(myvars$var1),
input2 = reactive(myvars$var2),
input3 = reactive(myvars$var3)
)
### renderDataTable
output$table1_tab2 <- renderReactable({
reactable(
req(data_tab2())
)
})
}
###### 5th module: server_tab2.R
#### 5-1. A dropdown input dependent on the date range
dropdownTab2Server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
rv <- reactiveValues()
output$daterange_tab2_ui <- renderUI({
req(input$var_lab_tab2)
dateRangeInput(
ns("daterange_tab2"),
"Date Range:",
start = min_max_date_df$min_date,
end = min_max_date_df$max_date
) # Retrieved from "global.R"
})
unique_lists_tab2 <- reactive({
sql <- glue_sql("
SELECT
DISTINCT list AS unique_list
FROM table1
WHERE date BETWEEN date ({dateid1_tab2*}) AND date ({dateid2_tab2*})
",
dateid1_tab2 = input$daterange_tab2[1],
dateid2_tab2 = input$daterange_tab2[2],
.con = pool
)
dbGetQuery(pool, sql)
})
output$subid_dropdown_tab2_ui <- renderUI({
req(input$daterange_tab2[1], input$daterange_tab2[2])
shinyWidgets::pickerInput(
ns("var_list_tab2"),
"Stations:",
choices = unique_lists_tab2(),
options = shinyWidgets::pickerOptions(
actionsBox = T,
header = "Close",
liveSearch = T
),
multiple = T
)
})
observe({
rv$var1 <- input$daterange_tab2[1]
rv$var2 <- input$daterange_tab2[2]
rv$var3 <- input$var_list_tab2
})
return(rv)
}
)
}
#### 5-2. Filtered data based on all inputs => This part is returning an empty data table
filteredDataTableTab2Server <- function(id, input1, input2, input3) {
moduleServer(id, function(input, output, session) {
reactive({
sql <- glue_sql("
SELECT
col1,
col2,
col3
FROM table1
WHERE date BETWEEN date ({dateid_tab2*}) AND date ({dateid_tab2*})
AND system IN ({listid_tab2*})
",
dateid1_tab2 = input1,
dateid2_tab2 = input2,
listid_tab2 = input3,
.con = pool
)
dbGetQuery(pool, sql)
})
}
)
}
You don't evaluate your reactive inputs to the filteredDataTableTab2Server module.
Try:
dateid1_tab2 = input1(),
dateid2_tab2 = input2(),
listid_tab2 = input3(),

How to connect Action Button with Material Switch Shiny

I have a shiny module where I want to read one data frame or another based on the user's selection. After the user selects one data frame or another, I want to give the user the option to plot one variable or another using a material switch. Mexico button means that it will be a data frame grouped by states. Municipal button means that it is a data frame grouped by municipality.
I am struggling to connect the material switch with the df selected with the action button. I have just tried with one action button.
Here is the code
require(shiny)
require(ggplot2)
require(dplyr)
require(tidyr)
require(readr)
require(data.table)
require(shinyWidgets)
require(ggplot2)
mod_dfSelector_ui <- function(id){
ns <- NS(id)
fluidRow(
column(2,
actionButton(inputId = ns("mexico_df"),
label = "Mexico")),
column(2,
actionButton(inputId = ns("municipal_df"),
label = "Municipal")),
column(4,
tags$div(
materialSwitch(inputId = "cumdeath", label = "Cumulative sum of covid deaths",
inline = TRUE),
tags$div(
materialSwitch(inputId = "exdeath", label = "Excess mortality",
inline = TRUE)
)
)
),
column(2,
plotOutput(
outputId = ns("plot"))
)
)
}
mod_dfSelector_server <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
# ----- buttons
countryDF <- eventReactive(input$mexico_df,{
fread("../data-raw/mexico-covid-final.csv", header = T) %>%
group_by(id_ent, month_def, year_def) %>%
summarise(tot_covid_deaths = sum(tot_covid_deaths, na.rm = T),
excess_mortality_ssa = sum(excess_mortality_ssa, na.rm = T),
excess_mortality_inegi = sum(excess_mortality_inegi, na.rm = T))
})
municipalDF <- eventReactive(input$municipal_df,{
fread("../data-raw/mexico-covid-final.csv", header = T)
})
# ---- switch
cumulativeEnt <- eventReactive(input$cumdeath, {
countryDF() %>%
select(month_def, year_def, tot_covid_deaths)
})
output$plot <- renderPlot({
cumulativeEnt() %>%
ggplot(aes(tot_covid_deaths)) +
geom_histogram()
})
})
}
## To be copied in the UI
# mod_histogram_ui("histogram_ui_1")
## To be copied in the server
# mod_histogram_server("histogram_ui_1")
ui <- fluidPage(
mod_dfSelector_ui("country")
)
server <- function(input, output, session) {
mod_dfSelector_server("country")
}
shinyApp(ui, server)

Shiny, reuss reactive input pickerInput

I am trying to create my first shiny app but I am facing a difficulty: in the reproducible example below I am creating a reactive pickerInput (i.e. only show brands proposing a cylindre equal to the input visitors select).
I then want that based on the combination input_cyl and picker_cny (remember that picker_cny depends on input_cyl) to display a table which shows the relevant data for the observation matching the combination input_cyl and picker_cny.
Thank you for your help!
df <- mtcars
df$brand <- rownames(mtcars)
df$brand <- gsub("([A-Za-z]+).*", "\\1", df$brand)
if (interactive()) {
library(shiny)
library(shinyWidgets)
library(shinythemes)
library(shinycssloaders)
# Define UI -----------------------------------------------
ui <- fluidPage(
# Application title
titlePanel("Reproducible Example"),
# Parameters
sidebarLayout(
sidebarPanel(
selectInput(inputId = "input_cyl", label = "Cyl",
choices = c("6", "4", "8")),
pickerInput(
inputId = "picker_cny",
label = "Select Company",
choices = paste0(unique(df$brand)),
options = list(`actions-box` = TRUE),
multiple = TRUE),
width = 2),
# Show Text
mainPanel(
tableOutput("table"),
width = 10)
))
# Define Server ------------------------------------------
server <- function(input, output, session) {
# Reactive pickerInput ---------------------------------
observeEvent(input$input_cyl, {
df_mod <- df[df$cyl == paste0(input$input_cyl), ]
# Method 1
disabled_choices <- !df$cyl %in% df_mod$cyl
updatePickerInput(session = session,
inputId = "picker_cny",
choices = paste0(unique(df$brand)),
choicesOpt = list(
disabled = disabled_choices,
style = ifelse(disabled_choices,
yes = "color: rgba(119, 119, 119, 0.5);",
no = "")
))
}, ignoreInit = TRUE)
output$table <- renderTable(df)
}
}
# Run the application
shinyApp(ui = ui, server = server)
You need a reactive that will handle the change in the input and subset the dataframe before giving it to the output table. For that, you just need to add this block to your server:
data <- reactive({
if (length(input$picker_cny) > 0)
df[df$brand %in% input$picker_cny,]
else
df
})
and update the output$table like this:
output$table <- renderTable(data())
Note: feel free to remove the if else in the reactive to get that:
data <- reactive({
df[df$brand %in% input$picker_cny,]
})
The only difference in that case is: would you show all or nothing when no input has been entered yet. That's a matter of taste.

Shiny - adding/appending user-selected observations to a list of observations to analyze

The user interface of the Shiny app I'm working on is supposed to work in the following manner:
User finds the desired observation(s) after applying a set of filters.
User clicks "Add" action button, so selected observation(s) are added to a running list/vector/etc of observations to be analyzed.
User modifies filters to find other observations which are to be included as well.
Loop back to step 1 as many times as user desires.
I cannot seem to find a way to save this list of observations to be analyzed. In the example I attached, the "observation ID" is the name of the model of the car (mtcars is used). I also did not include any data analysis, since I do not think that's necessary. In essence, the entire dataset (mtcars) should be filtered using dplyr in a reactive environment to only include the running list of selected observations.
Here's the code:
data("mtcars")
mtcars$model <- rownames(mtcars)
ui <- fluidPage(
titlePanel("sample"),
sidebarLayout(
sidebarPanel(
uiOutput("disp"),
uiOutput("qsec"),
uiOutput("model"),
actionButton("add", "Add"),
uiOutput("selectedModel")
),
mainPanel(
plotOutput("data_analysis")
)
)
)
server <- function(input, output) {
output$disp <- renderUI({
selectInput(
"disp_sel",
"Select disp:",
unique(mtcars$disp),
selected = NULL,
multiple = T,
selectize = T
)
})
output$qsec <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
selectInput(
"qsec_sel",
"Select qsec:",
unique(temp$qsec),
selected = NULL,
multiple = T,
selectize = T
)
})
output$model <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
if (!is.null(input$qsec_sel)){temp = temp %>% filter(qsec %in% input$qsec_sel)}
selectInput(
"model_sel",
"Select model:",
unique(temp$model),
selected = NULL,
multiple = T,
selectize = T
)
})
output$selectedModel <- renderUI({
req(input$add)
selectInput(
"list_of_selections",
"Selected models:",
unique(mtcars$model),
selected = NULL, # this should change when "Add" is pressed
multiple = T,
selectize = T
)
})
r_data = eventReactive(input$add,{
mtcars %>% filter(model %in% input$list_of_selections)
})
output$data_analysis <- renderPlot({
# do something with r_data (filtered data)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I've looked into modular code, reactive lists, and other stuff I don't even remember... Any help is greatly appreciated.
Try this
data("mtcars")
mtcars$model <- rownames(mtcars)
df1 <- mtcars
ui <- fluidPage(
titlePanel("sample"),
sidebarLayout(
sidebarPanel(
uiOutput("disp"),
uiOutput("qsec"),
uiOutput("model"),
actionButton("add", "Add"),
uiOutput("selectedModel")
),
mainPanel(
DTOutput("selecteddata"),
plotOutput("data_analysis")
)
)
)
server <- function(input, output) {
output$disp <- renderUI({
selectInput(
"disp_sel",
"Select disp:",
unique(mtcars$disp),
selected = NULL,
multiple = T,
selectize = T
)
})
output$qsec <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
selectInput(
"qsec_sel",
"Select qsec:",
unique(temp$qsec),
selected = NULL,
multiple = T,
selectize = T
)
})
output$model <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
if (!is.null(input$qsec_sel)){temp = temp %>% filter(qsec %in% input$qsec_sel)}
selectInput(
"model_sel",
"Select model:",
unique(temp$model),
selected = NULL,
multiple = T,
selectize = T
)
})
selected_data <- eventReactive(input$add,{
df1 %>% filter(model %in% input$model_sel)
})
output$selecteddata <- renderDT(
selected_data(), # reactive data
class = "display nowrap compact", # style
filter = "top", # location of column filters
options = list( # options
scrollX = TRUE # allow user to scroll wide tables horizontally
)
)
output$selectedModel <- renderUI({
req(input$add)
selectInput(
"list_of_selections",
"Selected models:",
choices = unique(selected_data()$model),
selected = unique(selected_data()$model), # this should change when "Add" is pressed
multiple = T,
selectize = T
)
})
r_data = eventReactive(input$add,{
mtcars %>% filter(model %in% input$list_of_selections)
})
output$data_analysis <- renderPlot({
ggplot(data=selected_data(), aes(x=disp, y=qsec)) + geom_point()
# do something with r_data (filtered data)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Found the answer. I included
selected <- reactiveValues(s = NULL)
observeEvent(input$add,{selected$s = c(selected$s, input$model})
into the server part. Then the selected models are stored in selected$s.

Showing and hiding inputs based on checkboxGroupInput

My shiny app begins with a checkboxGroupInput which contains the names of three companies: A, B and C. It also has 3 hidden numeric inputs, each corresponding to a company. Potential investors may select the name of the company they wish to invest in and specifiy the amount they are willing to invest. When the name of a company is checked, the corresponding numeric input shows up. Also, when the company name is unchecked, the numeric input disappears.
The checkboxGroupInput is called company. The 3 numericInput fields are respectively called amountA, amountB and amountC and are all generated inside a uiOutput. They are hidden with the hidden function of shinyjs.
library(shiny)
library(shinyjs)
library(magrittr)
ui <- fluidPage(
useShinyjs(),
checkboxGroupInput(inputId = "company", label = "Select a company", choices = LETTERS[1:3]),
uiOutput(outputId = "amounts")
)
server <- function(input, output){
company_names <- LETTERS[1:3]
num_ids <- paste0("amount", LETTERS[1:3])
output$amounts <- renderUI({
num_inputs <- lapply(1:3, function(i){
numericInput(inputId = num_ids[i], label = paste0("Investment in ", company_names[i]), value = 0, min = 0, max = 5000)
}) %>% tagList
shinyjs::hidden(num_inputs)
})
observeEvent(eventExpr = input$company, handlerExpr = {
if(length(input$company) == 0){
for(i in num_ids){
shinyjs::hide(id = i)
}
} else {
for(i in input$company){
shinyjs::toggle(id = paste0("amount", i), condition = input$company)
}
}
})
}
shinyApp(ui = ui, server = server)
The problem with my app is that the intended dynamics between the checkboxGroupInput and the numericInput fields are not working as intended. For instance, once a numericInput is shown, it cannot be hidden anymore by unchecking the boxes. How can I handle this?
The code pasted above is fully functional. Thank you very much.
I fixed your code by explicitly show/hide the numericInput when the corresponding check box is selected/unselected. Also I change the observeEvent with observe to make sure that the observer reacts when none of the check boxes are selected.
library(shiny)
library(shinyjs)
library(magrittr)
ui <- fluidPage(
useShinyjs(),
checkboxGroupInput(inputId = "company", label = "Select a company", choices = LETTERS[1:3]),
uiOutput(outputId = "amounts")
)
server <- function(input, output){
company_names <- LETTERS[1:3]
num_ids <- paste0("amount", LETTERS[1:3])
output$amounts <- renderUI({
num_inputs <- lapply(1:3, function(i){
numericInput(inputId = num_ids[i], label = paste0("Investment in ", company_names[i]), value = 0, min = 0, max = 5000)
}) %>% tagList
shinyjs::hidden(num_inputs)
})
observe({
for(i in company_names){
if (i %in% input$company) {
shinyjs::show(id = paste0("amount", i))
} else {
shinyjs::hide(id = paste0("amount", i))
}
}
})
}
shinyApp(ui = ui, server = server)

Resources