Add lag before observe event updates numeric input - r

I have a shiny app with a bunch of numeric inputs. Some of them are dependent on the value of others. As an example, let's say that I need input_1 to be changed if the entered input_2 is greater, such that input_1 = input_2 + 1. The problem is that if the user inputs their value too slowly, it takes the first digit of the entered input_2, for instance 5, and makes the input_1 equal to 6, even if you finish typing 540.
Here's an example:
library(shiny)
ui <- fluidPage(
numericInput("input1", "Input 1:", 0),
numericInput("input2", "Input 2:", 0)
)
server <- function(input, output, session) {
observeEvent(input$input2, {
if (input$input2 > input$input1) {
updateNumericInput(session, "input1", value = input$input2 + 1)
}
})
}
shinyApp(ui, server)
I have tried using invalidateLater, or debounce, but I believe I haven't done it correctly since the output still changes almost immediately. Ideally it would only update once focus is lost, but I don't want to add js to my code. So having a fixed timer to update seems like a good middle ground. Any ideas?

As you have already mentioned debounce is the right way to go:
library(shiny)
ui <- fluidPage(
numericInput("input1", "Input 1:", 0),
numericInput("input2", "Input 2:", 0)
)
server <- function(input, output, session) {
input2_d <- debounce(reactive({input$input2}), 1000L)
observeEvent(input2_d(), {
if (input2_d() > input$input1) {
updateNumericInput(session, "input1", value = input2_d() + 1)
}
})
}
shinyApp(ui, server)

Here's an example using the delay function from the shinyjs package:
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(), # call for shinyjs to be used in the ui part of the dashboard
numericInput("value1",
"Value 1:",
value = 40),
numericInput("value2",
"Value 2:",
value = 50)
)
server <- function(input, output, session) {
observeEvent(input$value1, {
if (input$value1 > input$value2) {
delay(2000, # the delay in milliseconds that is imposed
updateNumericInput(session = session, "value2", value = input$value1 + 1))
}
})
}
shinyApp(ui, server)
In the event that value1 should be greater than value2, after 2 sec., value2 will be updated to be equal to 1 + value1.

Related

Set a default value in shiny inputs (in case the user deletes it in the UI)

I am trying to set a default (or fallback) value for numericInput() in my shiny app to prevent NAs.
I am aware that the NA can be dealt with later in the server.r, but was wondering if there is a more elegant way of replacing the value within the input whenever a user deletes it in the ui.
The best way is to use the validate package with need() (see this SO thread), but here is something simpler and closer to what you are asking for:
library(shiny)
ui <- fluidPage(
numericInput("obs", "Observations:", 10, min = 1, max = 100),
verbatimTextOutput("value")
)
server <- function(input, session, output) {
dafault_val <- 0
observe({
if (!is.numeric(input$obs)) {
updateNumericInput(session, "obs", value = dafault_val)
}
})
output$value <- renderText({ input$obs })
}
shinyApp(ui, server)
I'd recommend using library(shinyvalidate), which is RStudios "official" way to solve this:
library(shiny)
library(shinyvalidate)
ui <- fluidPage(
numericInput(
inputId = "myNumber",
label = "My number",
value = 0,
min = 0,
max = 10
),
textOutput("myText")
)
server <- function(input, output, session) {
iv <- InputValidator$new()
iv$add_rule("myNumber", sv_required(message = "Number must be provided"))
iv$add_rule("myNumber", sv_gte(0))
iv$add_rule("myNumber", sv_lte(10))
iv$enable()
output$myText <- renderText({
req(iv$is_valid())
input$myNumber
})
}
shinyApp(ui, server)

shiny: Enable sliderInput() based on selection in radioButton()

I am having trouble getting this observeEvent to work properly.
The sliderInput() should only be enabled and accessible if Yes is selected in radioButton()
What am I missing?
library(shiny)
ui <- fluidPage(
radioButtons("EXBR", "External Beam Radiation", choiceValues=list("No","Yes"),
choiceNames=list("No","Yes"), selected ="No", inline=T),
sliderInput("EXBRGy", "Cumulative Gy dosage",
min = 2, max = 60, value = 40)
)
server <- function(input, output, session) {
observeEvent(input$EXBR, {
if((input$EXBR == "Yes")){
disable("EXBRGy")
}else{
enable("EXBRGy")
}
})
}
shinyApp(ui, server)
Before using most shinyjs functions, you need to call useShinyjs() in the app’s ui. It’s best to include it near the top as a convention. Also added library(shinyjs) and removed extra parentheses in the if statement.
library(shiny)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
radioButtons("EXBR", "External Beam Radiation", choiceValues=list("No","Yes"),
choiceNames=list("No","Yes"), selected ="No", inline=T),
sliderInput("EXBRGy", "Cumulative Gy dosage",
min = 2, max = 60, value = 40)
)
server <- function(input, output, session) {
observeEvent(input$EXBR, {
if(input$EXBR == "Yes"){
disable("EXBRGy")
}else{
enable("EXBRGy")
}
})
}
shinyApp(ui, server)

shiny modules: Store parameters (additional argument) already when creating module-UI instead of passing it to module's server function?

I have created a module sliderCheckbox which bundles together a sliderInput and a checkBoxInput to disable the sliderInput - basically a possibility to state "I don't know", which is necessary for survey-like inputs. When the slider is disabled, I want it to return a default value - most often the initial value, but not necessarily.
Now my question is: Is there any possibility to pass this default value when initialising the UI, that is with sliderCheckboxInput()? As the default value is a property like minimum and maximum, that is where it logically belongs to, and it also fits better to the rest of my setup.
Example:
library(shiny)
library(shinyjs)
sliderCheckboxInput <- function(id,description="",
min = 0,
max = 100,
value = 30,
default= NULL ##HERE I would want the default value to be set
cb_title = "I don't know"){
ns <- NS(id)
fluidRow(
column(width=9,
sliderInput(ns("sl"),
paste0(description, collapse=""),
min = min,
max = max,
value = value)
),
column(width=2,
checkboxInput(ns("active"),
cb_title, value=FALSE )
)
)
}
sliderCheckbox<- function(input, output, session,
default=NA) { #Problem: set default when initialising module
oldvalue<- reactiveVal()
observeEvent(input$active, {
if (input$active){
oldvalue(input$sl)
disable("sl")
updateSliderInput(session, "sl", value=default)
}else {
updateSliderInput(session, "sl", value=oldvalue())
enable("sl")
}
toggleState("sl", !input$active)
})
onclick("sl",
if(input$active) updateCheckboxInput(session, "active", value=FALSE)
)
return ( reactive({
if (input$active){
default
}else {
input$sl
}
}))
}
ui <- fluidPage(
useShinyjs(),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderCheckboxInput("bins", "Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
server <- function(input, output, session) {
bins_nr <- callModule(sliderCheckbox, "bins", default=44)
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = bins_nr() + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
shinyApp(ui, server)
You can send the value from the ui to the server using a hidden textInput
library(shiny)
library(shinyjs)
sendValueToServer <- function(id, value) {
hidden(textInput(
id, "If you can see this, you forgot useShinyjs()", value
))
}
myModuleUI <- function(id, param) {
ns <- NS(id)
tagList(
sendValueToServer(ns("param_id"), param),
textOutput(ns("text_out"))
)
}
myModule <- function(input, output, session) {
param <- isolate(input$param_id)
output$text_out <- renderText({
param
})
}
shinyApp(
ui = fluidPage(
useShinyjs(),
myModuleUI("id", "test")
),
server = function(input, output, session) {
callModule(myModule, "id")
}
)
There are probably more direct ways to do this using the JavaScript API of shiny but this is a "pure R" solution which should be enough for most usecases. Note that you can use the input value at initialization time with
isolate(input$text_in)
because the ui is always built before the server. Things get more complicated if everything is wrapped into renderUI but this does not seem to be the case for you.
Somewhat late to the party, but I think a neater way to do this is to use session$userData. This is available to both the main server function and the module's sewrver function.
So, in the main server, before callModule creates the module server:
session$userData[["module_id"]]$defaultValue <- myDefaultValue
and then at the end of module server function:
return ( reactive({
if (input$active){
session$userData[["module_id"]]$defaultValue
} else {
input$sl
}
})
)
That strikes me as neater, more robust and more generic than using a hidden input.

'observeEvent' function into another 'observeEvent' function not working

Following is the toned down version of my original problem. Here I am
trying to run an 'observeEvent' function into another 'observeEvent'
function. The code should perform the following steps sequentially:
On click of 'Print' button, Print the input number
On click of 'Add' button, Add +5 with the printed number
The code is working for first time only. From second time it is
showing the added number along with the printed without any click on
'Add' button.
Please help.
library(shiny)
ui <- fluidPage(
fluidRow(
sliderInput("n", min = 0, max = 100, value = 50, label = "Choose a number"),
actionButton("Print","Print the number"),
verbatimTextOutput("num1"),
actionButton("Add","Add +5 to the printed number"),
verbatimTextOutput("num2")
)
)
server <- function(input, output){
all <- reactiveValues(
n = 50,
a = 55
)
observeEvent(input$Print,{
all$n <- input$n
output$num1 <- renderPrint(all$n)
observeEvent(input$Add,{
all$d <- input$n + 5
output$num2 <- renderPrint(all$d)
})
})
}
shinyApp(ui = ui, server = server)
If you separate them, you can make the second observeEvent to get triggered from whatever happens inside the first one.
library(shiny)
ui <- fluidPage(
fluidRow(
sliderInput("n", min = 0, max = 100, value = 50, label = "Choose a number"),
actionButton("Print","Print the number"),
verbatimTextOutput("num1"),
actionButton("Add","Add +5 to the printed number"),
verbatimTextOutput("num2")
)
)
server <- function(input, output){
all <- reactiveValues(
n = 50,
a = 55
)
observeEvent(input$Print, {
all$n <- input$n
output$num1 <- renderPrint(all$n)
})
observeEvent(all$n, {
all$d <- input$n + 5
output$num2 <- renderPrint(all$d)
})
}
shinyApp(ui = ui, server = server)

Update label of actionButton in shiny

I know that similar question was already answered, however the solution creates a new actionButton with different label upon string-input. What I need is to keep the button(the counter of the button), because when I change the label and create a new button it has a counter of 0(not clicked).
So basically I need something like an update function to just change the label of the actionButton, when it is pressed. You press it once and the label changes.
input$Button <- renderUI({
if(input$Button >= 1) label <- "new label"
else label <- "old label"
actionButton("Button", label = label)
})
Something like this, but without reseting the value of the button(by creating a whole new one).
Thanks!
reactiveValues() can help. Check http://shiny.rstudio.com/articles/reactivity-overview.html for details.
In the following example, I renamed your input$Button to input$click to avoid double usage of the "Button" name.
Since we wrap the label in a renderUI(), input$click initially fires once it is created?!?, that's why I put the label
condition as: if(vars$counter >= 2)
An alternative solution could be to remove the read-only attribute (found here: https://github.com/rstudio/shiny/issues/167)
attr(input, "readonly") <- FALSE
input$click <- 1
For an example
paste the following in your R console:
ui <- bootstrapPage(
uiOutput('Button')
)
server <- function(input, output) {
# store the counter outside your input/button
vars = reactiveValues(counter = 0)
output$Button <- renderUI({
actionButton("click", label = label())
})
# increase the counter
observe({
if(!is.null(input$click)){
input$click
isolate({
vars$counter <- vars$counter + 1
})
}
})
label <- reactive({
if(!is.null(input$click)){
if(vars$counter >= 2) label <- "new label"
else label <- "old label"
}
})
}
# run the app
shinyApp(ui = ui, server = server)
You can use updateActionButton from native shiny package:
ui <- fluidPage(
actionButton('someButton', ""),
h3("Button value:"),
verbatimTextOutput("buttonValue"),
textInput("newLabel", "new Button Label:", value = "some label")
)
server <- function(input, output, session) {
output$buttonValue <- renderPrint({
input$someButton
})
observeEvent(input$newLabel, {
updateActionButton(session, "someButton", label = input$newLabel)
})
}
shinyApp(ui, server)
A few years later some small addition: If you want to switch between button icons, e.g. play / pause button (and switching between labels would be similar) you could do something like this (based on shosaco's answer).
library(shiny)
ui <- fluidPage(
fluidRow(
actionButton("PlayPause", NULL, icon = icon("play"))
)
)
server <- function(input, output, session) {
# whenever the ActionButton is clicked, 1 is added to input$PlayPause
# check if input$PlayPause is even or odd with modulo 2
# (returns the remainder of division by 2)
observeEvent(input$PlayPause, {
if (input$PlayPause %% 2 != 0) {
updateActionButton(session, "PlayPause", NULL, icon = icon("pause"))
} else {
updateActionButton(session, "PlayPause", NULL, icon = icon("play"))
}
})
}
shinyApp(ui = ui, server = server)

Resources