Trigger observeEvent in Shiny even when condition hasn't changed - r

I am having some trouble getting the functionality in my app that I'm looking for because of the way observeEvent works (which is normally very intuitive).
The basic functionality I'm looking for is that a user can input a couple numbers, click "submit", and then a modal pops up to take the user's name. After that, the app records the name and sums the numbers, and then clears the inputs. Then I'd like the user to be able repeat the process using the same name - but the app currently is structured so that the sums use an observeEvent that responds only when the name is different (i.e., using the same name twice in a row doesn't work, though I'd like it to). You can see in the app that my attempt at a solution is to reset the input for the inputSweetAlert (using shinyjs), but it can't access it, I assume because it's not actually on the UI side. I am using shinyWidgets sweetAlerts, which I'd like to continue doing.
Here's an example app:
library("shiny")
library("shinyWidgets")
library("shinyjs")
ui <- fluidPage(
shinyjs::useShinyjs(),
numericInput("num1", "Enter a number", value=NULL),
numericInput("num2", "Enter another number", value=NULL),
actionButton(inputId = "go", label = "submit"),
verbatimTextOutput(outputId = "res1"),
verbatimTextOutput(outputId = "res2")
)
server <- function(input, output, session) {
observeEvent(input$go, {
inputSweetAlert(session = session, inputId = "name", title = "What's your name?")
})
x <- reactiveValues(val=NULL)
observeEvent(input$name, {
x$val <- input$num1 + input$num2
confirmSweetAlert(session = session, inputId = "confirmed", title = "Success!", text = "Your responses have been recorded. All is in order.", type = "success", btn_labels = c("Ok, let me continue")
)
})
## A possible approach to a solution...
observeEvent(input$confirmed, {
shinyjs::reset("num1")
shinyjs::reset("num2")
shinyjs::reset("name")
})
output$res1 <- renderPrint(paste("Name:", input$name))
output$res2 <- renderPrint(paste("Sum:", x$val))
}
shinyApp(ui = ui, server = server)
Thanks for any help you can provide!

You could reset input$name via JS:
runjs('Shiny.setInputValue("name", null, {priority: "event"});')
Here is a working example:
library("shiny")
library("shinyWidgets")
library("shinyjs")
ui <- fluidPage(
shinyjs::useShinyjs(),
numericInput("num1", "Enter a number", value = NULL),
numericInput("num2", "Enter another number", value = NULL),
actionButton(inputId = "go", label = "submit"),
verbatimTextOutput(outputId = "res1"),
verbatimTextOutput(outputId = "res2")
)
server <- function(input, output, session) {
observeEvent(input$go, {
inputSweetAlert(session = session, inputId = "name", title = "What's your name?")
runjs('Shiny.setInputValue("name", null, {priority: "event"});')
})
x <- reactiveValues(val = NULL)
observeEvent(input$name, {
x$val <- input$num1 + input$num2
confirmSweetAlert(session = session, inputId = "confirmed", title = "Success!", text = "Your responses have been recorded. All is in order.", type = "success", btn_labels = c("Ok, let me continue"))
})
observeEvent(input$confirmed, {
shinyjs::reset("num1")
shinyjs::reset("num2")
shinyjs::reset("mytext")
})
output$res1 <- renderPrint(paste("Name:", input$name))
output$res2 <- renderPrint(paste("Sum:", x$val))
}
shinyApp(ui = ui, server = server)
For further information please see this article.
EDIT: In apps using modules, the call to runjs() can be adapted like this in order to namespace the id:
runjs(paste0("Shiny.setInputValue(\"", ns("name"), "\", null, {priority: \"event\"});"))

Here is a workaround. The idea consists in changing the input id at each click on the button.
library("shiny")
library("shinyWidgets")
library("shinyjs")
ui <- fluidPage(
shinyjs::useShinyjs(),
numericInput("num1", "Enter a number", value=NULL),
numericInput("num2", "Enter another number", value=NULL),
actionButton(inputId = "go", label = "submit"),
verbatimTextOutput(outputId = "res1"),
verbatimTextOutput(outputId = "res2")
)
server <- function(input, output, session) {
go <- reactive({
input$go
})
observeEvent(input$go, {
inputSweetAlert(session = session, inputId = sprintf("name-%d", go()),
title = "What's your name?")
})
x <- reactiveValues(val=NULL)
observeEvent(input[[sprintf("name-%d", go())]], {
x$val <- input$num1 + input$num2
confirmSweetAlert(session = session, inputId = "confirmed", title = "Success!", text = "Your responses have been recorded. All is in order.", type = "success", btn_labels = c("Ok, let me continue")
)
})
## A possible approach to a solution...
observeEvent(input$confirmed, {
shinyjs::reset("num1")
shinyjs::reset("num2")
shinyjs::reset("mytext")
})
output$res1 <- renderPrint(paste("Name:", input[[sprintf("name-%d", go())]]))
output$res2 <- renderPrint(paste("Sum:", x$val))
}
shinyApp(ui = ui, server = server)

Related

How to clear the mainPanel if a selectInput choice has changed?

I am trying to create an app that will show you results depending on a selectInput and the changes are controlled by actionButtons.
When you launch the app, you have to select a choice: Data 1 or Data 2. Once you have selected your choice (e.g. Data 1), you have to click the actionButton "submit type of data". Next, you go to the second tab, choose a column and then click "submit".
The output will be: one table, one text and one plot.
Then, if you go back to the first tab and select "Data 2", everything that you have generated is still there (as it is expected, since you didn't click any button).
However, I would like to remove everything that is in the mainPanel if I change my first selectInput as you could see it when you launch the app for the first time.
The idea is that since you have changed your first choice, you will have to do the same steps again (click everything again).
I would like to preserve and control the updates with actionButtons as I have in my code (since I am working with really long datasets and I don't want to depend on the speed of loading things that I don't want until I click the button). Nevertheless, I cannot think a way to remove everything from mainPanel if I change the choice of the first selectInput.
Does anybody have an idea how I can achieve this?
Thanks in advance
Code:
library(shiny)
library(shinyWidgets)
library(shinyFeedback)
library(DT)
library(datasets)
ui <- fluidPage(
sidebarPanel(
tabsetPanel(id="histogram",
tabPanel("Selection",
useShinyFeedback(),
selectInput(inputId = "type", label = "Select your data",
choices = c("Data 1" = "data1",
"Data 2" = "data2")),
conditionalPanel(
condition = "input.type == 'data2'",
div(style = "position:absolute;right:2.5em;",
actionButton(
inputId = "button_more_info_data2",
label = "More info",
icon = icon("info-circle"))
)
),
actionButton(
inputId = "button",
label = "Submit type of data",
icon = icon("check")
)
),
tabPanel("Pick the column",
br(),
selectizeInput(inputId = "list_columns", label = "Choose the column:", choices=character(0)),
actionButton(
inputId = "button2",
label = "Submit")
))
),
mainPanel(
dataTableOutput("table"),
textOutput("text"),
plotOutput("myplot")
)
)
server <- function(input, output, session) {
observeEvent(input$type,{
feedbackWarning(inputId = "type",
show = ("data2" %in% input$type),
text ="This data is... Please, be careful..."
)
})
mydata <- reactive({
if(input$type == "data1"){
mtcars
}else{
iris
}
}) %>% bindEvent(input$button2)
# This is to generate the choices (gene list) depending on the user's input.
observeEvent(input$button, {
updateSelectizeInput(
session = session,
inputId = "list_columns",
choices = colnames(trees), options=list(maxOptions = length(colnames(trees))),
server = TRUE
)
})
output$table <- renderDataTable({
req(input$button2)
mydata()
})
output$text <- renderText({
req(input$button2)
input$list_columns
})
output$myplot <- renderPlot({
req(input$button2, input$button)
hist(trees[,input$list_columns])
})
}
if (interactive())
shinyApp(ui, server)
Here is an example using a reset button - using the selectInput you'll end up with a circular reference:
library(shiny)
library(shinyWidgets)
library(shinyFeedback)
library(DT)
library(datasets)
ui <- fluidPage(sidebarPanel(tabsetPanel(
id = "histogram",
tabPanel(
"Selection",
useShinyFeedback(),
selectInput(
inputId = "type",
label = "Select your data",
choices = c("Data 1" = "data1",
"Data 2" = "data2")
),
conditionalPanel(
condition = "input.type == 'data2'",
div(
style = "position:absolute;right:2.5em;",
actionButton(
inputId = "button_more_info_data2",
label = "More info",
icon = icon("info-circle")
)
)
),
actionButton(
inputId = "button",
label = "Submit type of data",
icon = icon("check")
),
actionButton(
inputId = "reset",
label = "Reset",
icon = icon("xmark")
)
),
tabPanel(
"Pick the column",
br(),
selectizeInput(
inputId = "list_columns",
label = "Choose the column:",
choices = character(0)
),
actionButton(inputId = "button2",
label = "Submit")
)
)),
mainPanel(
dataTableOutput("table"),
textOutput("text"),
plotOutput("myplot")
))
server <- function(input, output, session) {
observeEvent(input$type, {
feedbackWarning(
inputId = "type",
show = ("data2" %in% input$type),
text = "This data is... Please, be careful..."
)
})
mydata <- reactiveVal(NULL)
observe({
if (input$type == "data1") {
mydata(mtcars)
} else if (input$type == "data2") {
mydata(iris)
} else {
mydata(data.frame())
}
}) %>% bindEvent(input$button2)
observeEvent(input$reset, {
mydata(data.frame())
})
# This is to generate the choices (gene list) depending on the user's input.
observeEvent(input$button, {
updateSelectizeInput(
session = session,
inputId = "list_columns",
choices = colnames(trees),
options = list(maxOptions = length(colnames(trees))),
server = TRUE
)
})
output$table <- renderDataTable({
req(input$button2)
mydata()
})
output$text <- renderText({
req(input$button2)
input$list_columns
})
output$myplot <- renderPlot({
req(input$button2, input$button)
hist(trees[, input$list_columns])
})
}
shinyApp(ui, server)

How to have a user input text and create a list with shiny? R

I have the following app which allows for text to be entered and it is then saved as VALUE and printed on a panel.
Although it looks like I can only do this with one text input at a time - even if I click add (so I don't believe this button is working). On top of that I would like for the user to be able to add multiple inputs (like I have below).
And then my VALUE function should be list with multiple inputs.
code below
library(shiny)
ui <- fluidPage(
headerPanel("R Package App"),
sidebarPanel(
# selectInput("options", "options", choices=c('abc','def')),
textInput("textbox", "Enter R Package Name", ""),
actionButton("add","Add")
),
mainPanel(
textOutput("caption")
)
)
server <- function(input, output, session) {
observe({
VALUE <- ''
if(input$add>0) {
isolate({
VALUE <- input$textbox
})
}
updateTextInput(session, inputId = "textbox", value = VALUE)
})
output$caption <- renderText({
input$textbox
})
}
shinyApp(ui = ui, server = server)
Have you considered using selectizeInput with it's create option?
library(shiny)
packagesDF <- as.data.frame(installed.packages())
ui <- fluidPage(
headerPanel("R Package App"),
sidebarPanel(
selectizeInput(
inputId = "selectedPackages",
label = "Enter R Package Name",
choices = packagesDF$Package,
selected = NULL,
multiple = TRUE,
width = "100%",
options = list(
'plugins' = list('remove_button'),
'create' = TRUE,
'persist' = TRUE
)
)
),
mainPanel(textOutput("caption"))
)
server <- function(input, output, session) {
output$caption <- renderText({
paste0(input$selectedPackages, collapse = ", ")
})
}
shinyApp(ui = ui, server = server)

Shiny App: Adding unlimited number of input bars

I want to build an app in which the user can add as many as input slots as he wants. I could only build an app that let the user to add only one more input slot. Here is my code:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput("a", "Something", choices = "blah blah"),
uiOutput("b"),
actionButton(inputId = "rm", label = "-"),
actionButton(inputId = "add", label = "+"),
),
mainPanel(
textOutput("test")
)
)
)
server <- function(input, output) {
observeEvent(input$add ,{
output$b <- renderUI({
selectizeInput("b", "Another thing", choices = "blah blah")
})
})
observeEvent(input$rm ,{
output$b <- renderUI({
NULL
})
})
}
shinyApp(ui = ui, server = server)
I have no idea how I can extend this to let the user add as many as input slots as he wants. Is this even possible?
We can try this approach:
We can access new added inputs with input$a1, input$a2 ... input$ax
Edit: added an observer to see the new inputs generated in the console. The first input created after pressing + button will be called input$a1.
observe({
print(names(input))
print(input$a1)
})
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput("a", "Something", choices = "blah blah"),
actionButton(inputId = "rm", label = "-"),
actionButton(inputId = "add", label = "+"),
),
mainPanel(
textOutput("test")
)
)
)
server <- function(input, output) {
input_counter <- reactiveVal(0)
observeEvent(input$add, {
input_counter(input_counter() + 1)
insertUI(
selector = "#rm", where = "beforeBegin",
ui = div(id = paste0("selectize_div", input_counter()), selectizeInput(paste0("a", input_counter()), label = "Another thing", choices = c("bla", "blabla")))
)
})
observeEvent(input$rm, {
removeUI(
selector = paste0("#selectize_div", input_counter())
)
input_counter(input_counter() - 1)
})
observe({
print(names(input))
print(input$a1)
})
}
shinyApp(ui, server)

Best practices for returning a server-side generated value from a Shiny module?

Consider the following example application:
library(shiny)
library(shinyWidgets)
module_UI <- function(id){
tagList(
div(
uiOutput(
outputId = NS(id, "selection")
),
shinyWidgets::dropdown(
uiOutput(outputId = NS(id, "new_option")),
style = "unite",
label = "New",
color = "primary",
animate = animateOptions(
enter = animations$fading_entrances$fadeInLeftBig,
exit = animations$fading_exits$fadeOutRightBig
),
up = F,
width = "600px",
inline = T
)
)
)
}
module_server <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
return_values <- reactiveValues(selection=NULL)
output$selection <- renderUI({
selectInput(inputId = ns("selection"), label = "Select:", choices = 1:5)
})
output$new_option <- renderUI({
div(
numericInput(ns("new_option_input"), label = "Add a new option:"),
shinyWidgets::actionBttn(
inputId = ns("submit_new_option"),
label = "Submit",
icon = icon("paper-plane"))
)
})
observeEvent(input$submit_new_option, {
#does not work as intended
updateSelectInput(session = session, inputId = "selection", selected = input$new_option_input)
})
observe({
return_values$selection <- input$selection
})
return(return_values)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
title = "Test App",
module_UI("test"),
verbatimTextOutput(outputId = "selection_chosen")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
picker <- module_server("test")
output$selection_chosen <- renderText({
picker$selection
})
}
# Run the application
shinyApp(ui = ui, server = server)
Basically, the module should do two things:
Allow user to select a pre-existing option --> return that value from module
Allow user to create their own, new option --> return that value from module
I have #1 working, but am struggling on #2. Specifically, where I have the "does not work" comment. How can I achieve this functionality? What are/is the best practice(s) for returning server-side created values from a Shiny module? This is an example app; the real one involves reading the selectInput options from a database, as well as saving the newly created options in the database. Appreciate any help on this! A lot of SO answers regarding Shiny modules have the older callModule(...) syntax, which makes researching this topic a bit more confusing.
You just need to provide the default value in numericInput. Perhaps you are looking for this.
library(shiny)
library(shinyWidgets)
module_UI <- function(id){
ns <- NS(id)
tagList(
div(
uiOutput(
outputId = NS(id, "selection")
),
shinyWidgets::dropdown(
uiOutput(outputId = NS(id, "new_option")),
style = "unite",
label = "New",
color = "primary",
animate = animateOptions(
enter = animations$fading_entrances$fadeInLeftBig,
exit = animations$fading_exits$fadeOutRightBig
),
up = F,
width = "600px",
inline = T
),
DTOutput(ns("t1"))
)
)
}
module_server <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
return_values <- reactiveValues(selection=NULL,myiris = iris)
output$selection <- renderUI({
selectInput(inputId = ns("selection"), label = "Select:", choices = 1:5)
})
output$new_option <- renderUI({
tagList(
numericInput(ns("new_option_input"), label = "Add a new option:",10, min = 1, max = 100),
shinyWidgets::actionBttn(
inputId = ns("submit_new_option"),
label = "Submit",
icon = icon("paper-plane"))
)
})
observeEvent(input$submit_new_option, {
return_values$myiris <- iris[1:input$new_option_input,]
#does work as intended
updateSelectInput(session = session, inputId = "selection", choices= c(1:input$new_option_input), selected = input$new_option_input)
})
output$t1 <- renderDT({return_values$myiris})
observe({
return_values$selection <- input$selection
})
return(return_values)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
title = "Test App",
module_UI("test"),
verbatimTextOutput(outputId = "selection_chosen"),
DTOutput("t2")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
picker <- module_server("test")
output$selection_chosen <- renderText({
picker$selection
})
output$t2 <- renderDT({picker$myiris[,c(3:5)]})
}
# Run the application
shinyApp(ui = ui, server = server)

Create a popup dialog box interactive

I was wondering if it is possible to create a popup dialog box interactive by using shiny (and shinyBS).
For example, I have a string and I want to change it and before doing a dialog box shows up asking if I really want to change it. In case I say "yes", it does it otherwise it discards the change. Here's my try but I found two issues: 1. if you click "yes" or "no", nothing changes 2. you always need to close the box by the bottom "close".
rm(list = ls())
library(shiny)
library(shinyBS)
name <- "myname"
ui =fluidPage(
textOutput("curName"),
br(),
textInput("newName", "Name of variable:", name),
br(),
actionButton("BUTnew", "Change"),
bsModal("modalnew", "Change name", "BUTnew", size = "small",
textOutput("textnew"),
actionButton("BUTyes", "Yes"),
actionButton("BUTno", "No")
)
)
server = function(input, output, session) {
output$curName <- renderText({paste0("Current name: ", name)})
observeEvent(input$BUTnew, {
output$textnew <- renderText({paste0("Do you want to change the name?")})
})
observeEvent(input$BUTyes, {
name <- input$newName
})
}
runApp(list(ui = ui, server = server))
Other proposals are more than welcome!!
Here's a proposition, I mainly changed the server.R:
library(shiny)
library(shinyBS)
ui =fluidPage(
textOutput("curName"),
br(),
textInput("newName", "Name of variable:", "myname"),
br(),
actionButton("BUTnew", "Change"),
bsModal("modalnew", "Change name", "BUTnew", size = "small",
HTML("Do you want to change the name?"),
actionButton("BUTyes", "Yes"),
actionButton("BUTno", "No")
)
)
server = function(input, output, session) {
values <- reactiveValues()
values$name <- "myname";
output$curName <- renderText({
paste0("Current name: ", values$name)
})
observeEvent(input$BUTyes, {
toggleModal(session, "modalnew", toggle = "close")
values$name <- input$newName
})
observeEvent(input$BUTno, {
toggleModal(session, "modalnew", toggle = "close")
updateTextInput(session, "newName", value=values$name)
})
}
runApp(list(ui = ui, server = server))
A couple of points:
I created a reactiveValues to hold the name that the person currently has. This is useful because you can then update or not update this value when the person clicks on the modal button. You can access the name using values$name.
You can use toggleModal to close the modal once the user has clicked on yes or no
#NicE provided a nice solution. I'm going to offer an alternative solution using the shinyalert package instead, which I believe results in easier to understand code (disclaimer: I wrote that package so may be biased).
The main difference is that the modal creation is no longer in the UI and is now done on the server when the button is clicked. The modal uses a callback function to determine if "yes" or "no" were clicked.
library(shiny)
library(shinyalert)
ui <- fluidPage(
useShinyalert(),
textOutput("curName"),
br(),
textInput("newName", "Name of variable:", "myname"),
br(),
actionButton("BUTnew", "Change")
)
server = function(input, output, session) {
values <- reactiveValues()
values$name <- "myname"
output$curName <- renderText({
paste0("Current name: ", values$name)
})
observeEvent(input$BUTnew, {
shinyalert("Change name", "Do you want to change the name?",
confirmButtonText = "Yes", showCancelButton = TRUE,
cancelButtonText = "No", callbackR = modalCallback)
})
modalCallback <- function(value) {
if (value == TRUE) {
values$name <- input$newName
} else {
updateTextInput(session, "newName", value=values$name)
}
}
}
runApp(list(ui = ui, server = server))
You can do something like this using conditionalPanel, I would further suggest adding a button to ask for confirmation oppose to instant update.
rm(list = ls())
library(shiny)
library(shinyBS)
name <- "myname"
ui = fluidPage(
uiOutput("curName"),
br(),
actionButton("BUTnew", "Change"),
bsModal("modalnew", "Change name", "BUTnew", size = "small",
textOutput("textnew"),
radioButtons("change_name", "", choices = list("Yes" = 1, "No" = 2, "I dont know" = 3),selected = 2),
conditionalPanel(condition = "input.change_name == '1'",textInput("new_name", "Enter New Name:", ""))
)
)
)
server = function(input, output, session) {
output$curName <- renderUI({textInput("my_name", "Current name: ", name)})
observeEvent(input$BUTnew, {
output$textnew <- renderText({paste0("Do you want to change the name?")})
})
observe({
input$BUTnew
if(input$change_name == '1'){
if(input$new_name != ""){
output$curName <- renderUI({textInput("my_name", "Current name: ", input$new_name)})
}
else{
output$curName <- renderUI({textInput("my_name", "Current name: ", name)})
}
}
})
}
runApp(list(ui = ui, server = server))

Resources