Related
Its quite quick and easy to instantiate a ShinyReForms for a Shiny app by following the example at https://piotrbajger.github.io/shinyreforms/articles/tutorial.html. I cannot though see how to get it working in a Shiny module.
The app below is a smaller version of the example app, presented in a module, and with an extra output which shows the result of the namespaced checkbox.
The ‘submit’ button doesn’t return the expected output though. I’m opining that this is a name space issue, though I can’t see where to wrap an id with something like... ns(“myformid”).
Any suggestions please. Thanks
library(shiny)
library(shinyreforms)
modUI <- function(id) {
ns <- NS(id)
tagList(
uiOutput(ns('form_ui')),
)
}
modServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
myForm <- shinyreforms::ShinyForm$new(
"myForm",
submit = "Submit",
onSuccess = function(self, input, output) {
yourName <- self$getValue(input, "name_input")
output$result <- shiny::renderText({
paste0("Your name is ", yourName, "!")
})
},
onError = function(self, input, output) {
output$result <- shiny::renderText({
"Form is invalid!"
})
},
shinyreforms::validatedInput(
shiny::checkboxInput(ns("checkbox"), label = "I accept!"),
validators = c(
shinyreforms::ValidatorRequired()
)
)
)
myForm$server(input, output)
output$ot_checkox <- renderUI({
h4(input$checkbox, style = 'color: blue;')
})
output$form_ui <- renderUI({
tagList(
shiny::tags$h1("Example ShinyForm!"),
myForm$ui(), # <- ShinyForm will be included here!
uiOutput(ns('ot_checkox')),
shiny::tags$h4("Result:"),
shiny::textOutput(ns("result"))
)
})
}
)
}
ui <- shiny::bootstrapPage(
shinyreforms::shinyReformsPage(
shiny::fluidPage(
modUI('mod_id')
)
)
)
server <- function(input, output, session) {
modServer('mod_id')
}
shinyApp(ui, server)
Aim: Return a reactive dataframe object from within the module named "modApplyAssumpServer"
Problem: I am getting an endless loop. Even if I wrap everything within the observeevent logic within isolate()
I have included another table in the app code below to indicate a simplified version of the logic that works outside of the module framework but that I can't seem to get to work within the module.
library(shiny)
library(dplyr)
df_agg_orig <- data.frame(proj_1 = c(2,3))
modGrowthInput <- function(id) {
ns <- NS(id)
tagList(
numericInput(ns("first"),label = "Assumption",value = 100),
)
}
modGrowthServer <- function(id, btnGrowth) {
moduleServer(id, function(input, output, session) {
list(
first = reactive({input$first})
)
})
}
modButtonUI <- function(id,lbl = "Recalculate"){
ns <- NS(id)
actionButton(inputId = ns("btn"),label = lbl)#,style = "pill",color = "primary",no_outline = T,size = "xs"
}
modButtonServer <- function(id){
moduleServer(id, function(input, output, session) {
reactive({input$btn})
})
}
modApplyAssumpServer <- function(id,btnGrowth, df_agg,case_vals){
moduleServer(id, function(input, output, session) {
stopifnot(is.reactive(btnGrowth))
stopifnot(is.reactive(df_agg))
mod_vals <- reactiveVal(df_agg())
observeEvent(btnGrowth(),{
isolate({mod_vals(df_agg() %>% mutate(proj_1 = proj_1*input$first))})
print("Looping problem...")
})
mod_vals()
})
}
#### Test App
GrowthInputApp <- function() {
ui <- fluidPage(
sidebarPanel(modGrowthInput("tst"),modButtonUI("tstGrowth")),
mainPanel(fluidRow( splitLayout( DT::DTOutput("no_module"),DT::DTOutput("module_tbl")))))
server <- function(input, output, session) {
btnGrowth <- modButtonServer("tstGrowth")
case_vals <- modGrowthServer("tst")
df_agg <- reactiveValues(df_wide = df_agg_orig)
#Outside of module test exhibiting expected/desired behavior (at least if the looping issue would let it do so :)
observeEvent(btnGrowth(),{
df_agg$df_wide$proj_1 <- round(df_agg$df_wide*case_vals$first(),2)
})
output$no_module <- DT::renderDT({DT::datatable(rownames = F,df_agg$df_wide,caption = "Not Updated Within Module")})
output$module_tbl <- DT::renderDT({DT::datatable(rownames = F,modApplyAssumpServer("tst",btnGrowth = btnGrowth,df_agg = reactive({df_agg_orig})),caption = "Table Returned From Module")}
)
}
shinyApp(ui, server)
}
runApp(GrowthInputApp())
Try this
library(shiny)
library(dplyr)
df_agg_orig <- data.frame(proj_1 = c(2,3))
modGrowthInput <- function(id) {
ns <- NS(id)
tagList(
numericInput(ns("first"),label = "Assumption",value = 10),
)
}
modGrowthServer <- function(id) {
moduleServer(id, function(input, output, session) {
list(
first = reactive({input$first})
)
})
}
modButtonUI <- function(id,lbl = "Recalculate"){
ns <- NS(id)
actionButton(inputId = ns("btn"),label = lbl)#,style = "pill",color = "primary",no_outline = T,size = "xs"
}
modButtonServer <- function(id){
moduleServer(id, function(input, output, session) {
reactive({input$btn})
})
}
modApplyAssumpServer <- function(id,btnGrowth, df_agg, val){
moduleServer(id, function(input, output, session) {
stopifnot(is.reactive(btnGrowth))
stopifnot(is.reactive(df_agg))
modvals <- eventReactive(btnGrowth(), {
print("Looping problem...")
#print(btnGrowth())
df_agg() %>% mutate(proj_1 = proj_1*val )
})
return(modvals())
})
}
#### Test App
GrowthInputApp <- function() {
ui <- fluidPage(
sidebarPanel(modGrowthInput("tst"),modButtonUI("tstGrowth")),
mainPanel(fluidRow( splitLayout( DT::DTOutput("no_module"),DT::DTOutput("module_tbl")))))
server <- function(input, output, session) {
btnGrowth <- modButtonServer("tstGrowth")
case_vals <- modGrowthServer("tst")
observe({ print(case_vals$first())})
df_agg <- reactiveValues(df_wide = df_agg_orig)
#Outside of module test exhibiting expected/desired behavior (at least if the looping issue would let it do so :)
observeEvent(btnGrowth(),{
df_agg$df_wide$proj_1 <- round(df_agg$df_wide*case_vals$first(),2)
})
mydf <- eventReactive(c(btnGrowth(),case_vals$first()), {
modApplyAssumpServer("tst", btnGrowth, reactive({df_agg$df_wide}), case_vals$first() )
})
#observe({print(btnGrowth())})
output$no_module <- renderDT({DT::datatable(rownames = F,df_agg$df_wide,caption = "Not Updated Within Module")})
output$module_tbl <- renderDT({DT::datatable(rownames = F, mydf() ,caption = "Table Returned From Module")} )
### using original data so no change after first click
#output$module_tbl <- renderDT({DT::datatable(rownames = F, modApplyAssumpServer("tst", btnGrowth, reactive({df_agg_orig}), case_vals$first() ),caption = "Table Returned From Module")}
#)
}
shinyApp(ui, server)
}
runApp(GrowthInputApp())
I've been playing with shiny modules and finally got some bits and pieces working. However, I've been totally thrown by an issue testing individual modules.
What I usually do is turn each module into a small app to test how it works. This particular module takes additional variables, but I don't seem to be able to insert some test vars into the test app as I would usually. Unfortunately, this fails.
Is there a standard way of dealing with this?
Many thanks
histogram_ui <- function(id) {
tagList(
plotOutput(NS(id, "hist"))
)
}
histogram_server <- function(id, var, bin) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[var()]])
#debug
observeEvent(var(), {
print(var())
})
output$hist <- renderPlot({
hist(data(), breaks = bin(), main = var())
})
})
}
#testing----
ui_t <- fluidPage(
histogram_ui("test")
)
server_t <- function(input, output, session) {
histogram_server("test", var = "mpg", bin = 10)
}
options(shiny.reactlog=TRUE) #ctrl+F3 to bring up
shinyApp(ui_t, server_t)
Try this
histogram_ui <- function(id) {
tagList(
plotOutput(NS(id, "hist"))
)
}
histogram_server <- function(id, var, bin) {
moduleServer(id, function(input, output, session) {
observeEvent(c(var(), bin()), {
print(var())
})
output$hist <- renderPlot({
hist(mtcars[[var()]], breaks = bin(), main = var())
})
})
}
#testing----
ui_t <- fluidPage(
selectInput("myvar","Choose",choices = colnames(mtcars)),
sliderInput("bins","Number of Bins", min=1, max=10, value=5),
histogram_ui("test")
)
server_t <- function(input, output, session) {
histogram_server("test", var = reactive(input$myvar), bin = reactive(input$bins))
}
options(shiny.reactlog=TRUE) #ctrl+F3 to bring up
shinyApp(ui_t, server_t)
Goal
I have five expectations:
Solution using modules
Communication between modules
Dynamic creation of modules
local storage using shinyStore
Export result in dataframe
What has worked so far
This is a continuation of the following question.
I have a Shiny app that currently has two modules, but I have had issues with both of them communicating. The first module Selects any number of species within a Species pool (SpeciesSelect), this module is in the file R/SpeciesSelect.R within my working directory with the following code.
SpeciesSelect_UI <- function(id, SpeciesList){
ns <- NS(id)
tagList(
shiny::selectizeInput(inputId = ns("SpeciesNames"), label = "SpeciesName",
choices = SpeciesList,
multiple = T)
)
}
SpeciesSelect_Server <- function(id){
moduleServer(id, function(input, output, session) {
# return the reactive here
return(reactive({input$SpeciesNames}))
})
}
And the second module (SpeciesCount) would use those species in order to select how you sample them, and in some cases to count them when the method is equal to pinpoint. This is stored in R/SpeciesCount.R and the code is as follows:
SpeciesCount_UI <- function(id, Species){
ns <- NS(id)
tagList(
shinyMobile::f7Card(
f7Flex(
textOutput(ns("SpeciesAgain")),
uiOutput(ns("Sampling_type_ui")),
uiOutput(ns("SpeciesCount"))
)
)
)
}
SpeciesCount_Server <- function(id, Species){
moduleServer(id, function(input, output, session) {
output$SpeciesAgain <- renderText({Species})
ns <- session$ns
output$Sampling_type_ui <- renderUI({
#req(input$SpeciesName)
req(Species)
f7Select(inputId = ns("Sampling_type"),
label = "Sampling type",
choices = c("5m circle", "15m circle", "Pin-point"))
})
output$SpeciesCount <- renderUI({
if (req(input$Sampling_type) == "Pin-point") {
shinyMobile::f7Stepper(inputId = ns("Species1"), label = "Species count", min = 1, max = 1000, step = 1, value = 1)
}
})
})
}
Each of the modules is working well on its own as shown in the following example:
library(shiny)
library(shinyMobile)
library(shinyStore)
source("R/SpeciesCount.R")
source("R/SpeciesSelect.R")
SpeciesList <- c("Species1", "Species2", "Species3", "Species4", "Species5")
ui = f7Page(
title = "Show navbar",
f7SingleLayout(
navbar = f7Navbar("Hide/Show navbar"),
f7Button(inputId = "toggle", "Toggle navbar", color = "red"),
SpeciesSelect_UI(id = "SpeciesList", SpeciesList = SpeciesList),
lapply(seq_along(SpeciesList), function(i) {
SpeciesCount_UI(id = i, Species = SpeciesList[i])
})
)
)
server = function(input, output, session) {
lapply(seq_along(SpeciesList), function(i) {
SpeciesCount_Server(id = i, Species = SpeciesList[i])
})
observeEvent(input$toggle, {
updateF7Navbar()
})
}
shinyApp(ui, server)
I have 4 issues that are not working well, first, the communication between modules, and then looping through the results of the first module to get several of the second module, the localStorage issue, and finally exporting it to a dataframe
Communication between modules and dynamic UI generation
In order to isolate both issues, for the communication problem, I selected only one species and took out the lapply function to see if I can get the SpeciesCount to recognise the output of the SpeciesSelect_Server and incorporate it into the SpeciesCount module, here is the code I ended up with:
library(shiny)
library(shinyMobile)
library(shinyStore)
source("R/SpeciesCount.R")
source("R/SpeciesSelect.R")
LIST <- c("Species1", "Species2", "Species3", "Species4", "Species5")
ui = f7Page(
title = "Show navbar",
f7SingleLayout(
navbar = f7Navbar("Hide/Show navbar"),
f7Button(inputId = "toggle", "Toggle navbar", color = "red"),
SpeciesSelect_UI(id = "SpeciesList", SpeciesList = LIST),
SpeciesCount_UI(id = "SpeciesCount", Species = SpeciesSelected())
)
)
server = function(input, output, session) {
SpeciesSelected <- SpeciesSelect_Server(id = "SpeciesList")
SpeciesCount_Server(id = "SpeciesCount", Species = SpeciesSelected())
observeEvent(input$toggle, {
updateF7Navbar()
})
}
shinyApp(ui, server)
But the results of the SpeciesSelect module are not generating any UI in the SpeciesCount module
Adding the LocalStorage issue
This app is going to be used in the field, that means, that at time we might get connectivity issues, I have issues at storing the values of the Species Select Module, then for sure I will have issues with the next module this is the shiny app I am using
library(shiny)
library(shinyMobile)
library(shinyStore)
source("R/SpeciesCount.R")
source("R/SpeciesSelect.R")
SpeciesList <- c("Species1", "Species2", "Species3", "Species4", "Species5")
ui = f7Page(
title = "Show navbar",
f7SingleLayout(
navbar = f7Navbar("Hide/Show navbar"),
f7Button(inputId = "toggle", "Toggle navbar", color = "red"),
SpeciesSelect_UI(id = "SpeciesList", SpeciesList = SpeciesList),
lapply(seq_along(SpeciesList), function(i) {
SpeciesCount_UI(id = i, Species = SpeciesList[i])
})
)
)
server = function(input, output, session) {
lapply(seq_along(SpeciesList), function(i) {
SpeciesCount_Server(id = i, Species = SpeciesList[i])
})
observeEvent(input$toggle, {
updateF7Navbar()
})
}
shinyApp(ui, server)
And I modified the species select for that also
SpeciesSelect_UI <- function(id, SpeciesList){
ns <- NS(id)
tagList(
shiny::selectizeInput(inputId = ns("SpeciesNames"), label = "SpeciesName",
choices = SpeciesList,
multiple = T)
)
}
SpeciesSelect_Server <- function(id){
moduleServer(id, function(input, output, session) {
ns <- session$ns
# return the reactive here
observeEvent(input$save, {
updateStore(session, name = ns("SpeciesNames"), input$SpeciesNames)
}, ignoreInit = TRUE)
observeEvent(input$clear, {
# clear current user inputs:
updateTextInput(session, inputId = ns("SpeciesNames"), value = "")
# clear shinyStore:
updateStore(session, name = ns("SpeciesNames"), value = "")
}, ignoreInit = TRUE)
return(reactive({ns(input$SpeciesNames)}))
})
}
But nothing gets stored. Maybe creating a module for shiny store is needed?
Export as a dataframe
This one is tied two point 2:
So lets say I am in the following input set:
The idea would be to generate a reactive that has the following that frame, that I can then export as a CSV file. I think I can handle the export, but I am unsure on how to generate the data.frame from the dynamic UI:
data.frame(Species = c("Species1", "Species2", "Species3"), Method = c("Pin-point","5m circle", "15m circle"), abundance = c(5, 1, 1))
Your first module is probably already silently returning the reactive but for clarity you can make it explicit. In you first module, return a reactive:
SpeciesSelect_Server <- function(id){
moduleServer(id, function(input, output, session) {
# return the reactive here
return(reactive({input$SpeciesNames}))
})
}
Now call the module AND assign its output a name where you'd like to use it (in another module or in your app server), like this:
selected_species <- SpeciesSelect_Server(id = "SpeciesList")
Now selected_species can be called, observed, etc with:
selected_species()
I am trying to use if/then construct in main server function to determine which, out of a choice of two, modules to call, depending on user input. One choice uses an add/remove action button module to call another module, the other choice calls a different module not using the add/remove button module. Calling the module using add/ remove module is easy enough, as I am passing the UI to call as one of the parameters in the call to the add/remove button module, but I am not sure how to properly insertUI() in the server function after callModule(). So in my example (as simple as I could think to make it), the UI starts with a textInput() box, which defaults to 1. I have a "first" module, which just prepends the userInput() data to letters a,b,c d in selectInput() box. The "second" module prepends "Not 1" to a,b,c,d in selectInput() box. I use observeEvent({}) such that if user does nothing (textInput() stays at 1), then "first" module is called. If the user changes textInput() to anything at all other than default 1, "second" module is called. What I am not clear on is how to call the UI for the second module. I have a uiOutput("dummy") as a placeholder in the ui() function. However, my example does not work as described above, because it does not ever successfully call "second" module if the user changes the testInput() default value. Code below, thanks!
library(shiny)
firstUI <- function(id) { uiOutput(NS(id, "first")) }
firstServer <- function(input, output, session, a) {
ns = session$ns
output$first <- renderUI({
selectInput(ns("select"), h4("Select"), paste0(isolate(a()), letters[1:4]))
})
return(reactive({ c(paste0(input$select), paste0(input$select)) }))
}
removeFirstUI <- function(id) {
removeUI(selector = paste0('#', NS(id, "first")))
}
secondUI <- function(id) { uiOutput(NS(id, "second")) }
secondServer <- function(input, output, session, a) {
ns = session$ns
output$second <- renderUI({
selectInput(ns("select"), h4("Select"), paste0("Not 1", letters[1:4]))
})
return(reactive({ c(paste0(input$select), paste0(input$select)) }))
}
removeSecondUI <- function(id) {
removeUI(selector = paste0('#', NS(id, "second")))
}
addRmBtnUI <- function(id) {
ns <- NS(id)
tags$div(
actionButton(inputId = ns('insertParamBtn'), label = "Add"),
actionButton(ns('removeParamBtn'), label = "Remove"),
hr(),
tags$div(id = ns('placeholder'))
)
}
addRmBtnServer <- function(input, output, session, moduleToReplicate, ...) {
ns = session$ns
params <- reactiveValues(btn = 0, a = list())
observeEvent(input$insertParamBtn, {
params$btn <- params$btn + 1
params$a[[params$btn]] <- callModule(moduleToReplicate$server, id = params$btn, ...)
insertUI(
selector = paste0('#', ns('placeholder')),
ui = moduleToReplicate$ui(ns(params$btn))
)
})
observeEvent(input$removeParamBtn, {
moduleToReplicate$remover(ns(params$btn))
params$btn <- params$btn - 1
})
return(params)
}
ui <- fluidPage(
addRmBtnUI("addRm"),
textInput("a", label = "a", value = 1, width = '150px'),
verbatimTextOutput("text", placeholder = TRUE),
uiOutput("dummy")
)
server <- function(input, output, session) {
a <- reactive({ input$a })
comp <- reactiveValues()
observeEvent(a(), {
if (input$a == 1) {
comp <- callModule(
addRmBtnServer, id = "addRm",
moduleToReplicate = list(
ui = firstUI,
server = firstServer,
remover = removeFirstUI
),
a = a
)
} else {
comp <- callModule(
secondServer, id = 0,
a = a
)
}
}, ignoreNULL = TRUE)
output$text <- renderPrint({
if (!(is.null(comp$btn))) {
if (comp$btn > 0) {
paste(
comp$a[[comp$btn]](),
sep = " "
)
}
} else { paste0("") }
})
}
shinyApp(ui = ui, server = server)