R Shiny: How to Write Loop for Observeevent

R Shiny: How to write loop for observeEvent

observeEvent works great in lapply :

library("shiny")
ui <- fluidPage(
fluidRow(
column(
width = 6,
lapply(
X = 1:6,
FUN = function(i) {
sliderInput(inputId = paste0("d", i), label = i, min = 0, max = 10, value = i)
}
)
),
column(
width = 6,
verbatimTextOutput(outputId = "test")
)
)
)
server <- function(input, output){

vals <- reactiveValues()

lapply(
X = 1:6,
FUN = function(i){
observeEvent(input[[paste0("d", i)]], {
vals[[paste0("slider", i)]] <- input[[paste0("d", i)]]
})
}
)

output$test <- renderPrint({
reactiveValuesToList(vals)
})
}
shinyApp(ui = ui, server = server)

EDIT : for previous version of shiny, use this in server (add assign) :

lapply(
X = 1:6,
FUN = function(i){
assign(
paste0("obs", i),
observeEvent(input[[paste0("d", i)]], {
vals[[paste0("slider", i)]] <- input[[paste0("d", i)]]
})
)
}
)

Update shiny output from for loop inside observeEvent using reactiveValues

I rewrote your sever codes to make it working.

You cannot update UI in a loop as I already mentioned in the comment, it isn't how Shiny works. Use invalidateLater() to do something similar to a for loop.

And also, invalidateLater() doesn't work in observeEvent, so you need to write the loop logic in observe()

isolate() is used to prevent recursive triggering of the observer, so it only re-evaluate every 0.5 second based on invalidateLater(500)

server = function(input, output, session){
vars = reactiveValues(cc="",ct=0)
startSearch <- reactiveVal(FALSE)
startSearch <- eventReactive(input$searchgt,{
TRUE
})

observe({
req(startSearch())
if (isolate(vars$ct) < 10){
invalidateLater(500)
isolate({
vars$ct=vars$ct+1
vars$cc=paste('<b style="color:blue">',"Searching...",vars$ct,"</b>")
vars$busca = try(print("Something"),silent = T)
})
} else {
vars$cc=paste('<b style="color:red">',"Some warning.","</b>")
}

})

output$warngt = renderUI({HTML(vars$cc)})
}

How do I loop an observeEvent in shiny? to change style in leaflet when polygons are clicked

Try this

observe({
lapply(1:input$nomaps, function(i) {

observeEvent(input[[paste0("plot_", i,"_shape_click")]], {
# execute only if the polygon has never been clicked
selected.id <- input[[paste0("plot_", i,"_shape_click")]]
data <- rv$df[rv$df$ID==selected.id$id,]

if (selected.id$group == "unclicked_poly") {
change_color(map = paste0("plot_", i),
id_to_remove = selected.id$id,
data = data,
colour = "yellow",
new_group = "clicked1_poly")
} else {
leafletProxy(paste0("plot_", i)) %>%
removeShape(selected.id$id) %>% # remove previous occurrence
addPolygons(
data = data,
label = as.character(data$display),
layerId = data$ID,
group = "unclicked_poly") # back to initialize group
}
})
})
})

For loop in observe Event

According to the docs, observeEvent()

eventExpr A (quoted or unquoted) expression that represents the event; this can be a simple reactive value like input$click, a call to a reactive expression like dataset(), or even a complex expression inside curly braces

Try this:

observeEvent({
for (...) {
input[[...]]
}
}, {
print("Cleicked")
})

Observe event inside loop

You can Do this:

library(shiny)

loadBackup <- function(x){
if(file.exists(x)){
init <- readRDS(x)
} else {
init <- NULL
}
return(init)
}

ui <- fluidPage(
navbarPage(
"Scouting",
id = "navtab",
tabPanel("Attack",
navlistPanel(
tabPanel('OH1',
mainPanel(
tags$b("______1____________2____________3_____________4______"),
br(),
uiOutput(outputId = "CustomUI")
)
)
)
)
)
)

server <- function(input, output, session) {

rv <- reactiveValues(length = 4,
width = 4)
initialize <- reactive({
backup <- matrix(0, nrow = rv$length, ncol = rv$width)
.backup <- loadBackup("backup.rds")
if(!is.null(.backup)){
dim <- dim(.backup)
backup[1:dim[1],1:dim[2]] <- .backup
}
for(x in 1:rv$length){
for(y in 1:rv$width){
rv[[paste0("a_OH1_z",x,"_q",y)]] <- backup[x,y]
}
}
})

savebackup <- reactive({
backup <- matrix(0, nrow = rv$length, ncol = rv$width)
for(x in 1:rv$length){
for(y in 1:rv$width){
backup[x,y] <- rv[[paste0("a_OH1_z",x,"_q",y)]] + input[[paste0("aOH1z",x,"q",y)]]
}
}
saveRDS(backup, file = "backup.rds")
})
session$onSessionEnded(function() {
isolate(savebackup())
})

output$CustomUI <- renderUI({
initialize()
column(12,
lapply(1:rv$length, function(x){
fluidRow(
br(),
lapply(1:rv$width, function(y, x){
actionButton(paste0("aOH1z",x,"q",y),
rv[[paste0("a_OH1_z",x,"_q",y)]],
style = "width: 100px; height:100px; background-color:blue; color:white;")
}, x = x)
)

})
)
})

observe(

lapply(1:rv$length, function(x){
lapply(1:rv$width, function(y, x){
z <- rv[[paste0("a_OH1_z",x,"_q",y)]] + input[[paste0("aOH1z",x,"q",y)]]
updateActionButton(session,paste0("aOH1z",x,"q",y),label = z)
}, x = x)
})
)

}

shinyApp(ui, server)

LoadBackup will check for a specific file, if it finds it it will return it's value, if not, it returns 0.

Files are saved when the session ends with the session$onSessionEnded call back function. The UI is built using an lapply which I adapted from this example given by RStudio.

The entire UI is built from the width and length reactive values that I specify, so the user may increase the size as they wish. You could remove this and make it inputs and it should still work the same.

RDS files are saved locally on the device so this will not be reproducible on shinyapps.io. You will need to look for database solutions for that.

observeEvent in insertUI generated in loop

For loops in R all run in the same scope, which means a variable defined in the loop will be shared by all iterations. This is an issue if you create a function in each loop iteration that accesses this variable, and assume that it'll be unique for each iteration.

Here's a simple demo:

counter <- 0; funcs <- list()
for (i in 1:3) {
counter <- counter + 1
funcs[[i]] <- function() print(counter)
}
for (i in 1:3) {
funcs[[i]]() # prints 3 3 3
}

In this Shiny app, the observeEvent handler accesses the local variable add, and doesn't get called until after the for loop is over, and add is at its final value.

There are a few ways to get around this and create a unique scope for each loop iteration. My favorite is to use an apply function to replace the for loop. Then each apply iteration runs in its own function so local variables are unique each item.

library(shiny)

# Define the UI
ui <- fluidPage(
#actionButton("adder", "Add"),
tags$div(id = 'placeholder')
)

# Define the server code
server <- function(input, output) {
rv <- reactiveValues(counter = 0)

lapply(1:3, function(i) {
isolate({
rv$counter <- rv$counter + 1

add <- sprintf("%03d",rv$counter)

#prefix <- generateRandomString(1,20)
filterId <- paste0('adder_', add)
divId <- paste0('adder_div_', add)
elementFilterId <- paste0('adder_object_', add)
removeFilterId <- paste0('remover_', add)

insertUI(
selector = '#placeholder',
ui = tags$div(
id = divId,
actionButton(removeFilterId, label = "Remove filter", style = "float: right;"),
textInput(elementFilterId, label = paste0("Introduce text #",rv$counter), value = "")
)
)
})

# Observer that removes a filter
observeEvent(input[[removeFilterId]],{
removeUI(selector = paste0("#", divId))
})
})
}

# Return a Shiny app object
shinyApp(ui = ui, server = server, options = list(launch.browser = T))

Note that I also removed the outer observeEvent since the server function runs on session initialization anyway.

How to break a for loop basing on captured shinyalert input value

Shiny alert does not seem to be run synchronous to the loop. Put print(decision) in front of the for-loop. It'll show in the console that the loop runs independently from the user clicking on the alert messages. That means: it won't work with a for loop or any other loop. It can only be done using the event mechanisms provided by Shiny.

The solution below creates and manipulates a reactive value RequiredAnswers. Any change to it will trigger the shiny alert to open and ask the user to confirm the first element of the RequiredAnswers vector. In other words, it removes the element that has just been answered with "No".

Each answer to the alert will be caught by observeEvent(input$AnswerAlert, {}). If the response was "cancel" it dismisses the first element of RequiredAnswers thus triggering the next alert. This way we get a loop. If the response was "Ok" it will clear RequiredAnswers and no more alerts will be triggered (because observeEvent(RequiredAnswers(), {}) does not respond to RequiredAnswers == NULL.

Drawback: if the user clicks 'Cancel' quite fast, Shiny does not recognize the event observeEvent(input$AnswerAlert, {}) does not get called. I cannot say for sure what the source of this is. My guess is a bug in Shiny Alert.

Another way would be to do it recursively (see the section "Chaining modals" in the documentation). This way, the lost events may be avoided.

library(shiny)
library(shinyalert)

ui <- fluidPage(
actionButton("run", "Run"),
verbatimTextOutput("answer", placeholder = TRUE)
)

server <- function(input, output, session) {

RecentDecision <- reactiveVal()
RequiredAnswers <- reactiveVal()

# Responds to the alert being confirmed or dismissed
observeEvent(input$AnswerAlert, {
if (input$AnswerAlert) {
Answer <- RequiredAnswers()[1]
RecentDecision(Answer)
print(RecentDecision())
RequiredAnswers(NULL)
} else {
# Remove the first item of RequiredAnswers
# Clear it completely when the end has been reached
if (length(RequiredAnswers()) == 1) {
RequiredAnswers(NULL)
RecentDecision(NULL)
}
else
RequiredAnswers(RequiredAnswers()[-1])
}
})

# Responds to changes, ignores NULL
observeEvent(RequiredAnswers(), {
shinyalert(
title = "Do you accept following element?",
text = RequiredAnswers()[1],
showCancelButton = TRUE,
inputId = "AnswerAlert" # use individual id
)
})

# Respond to the Run button
observeEvent(input$run, {
# Set up the vector of desired answers
RequiredAnswers(c("A", "B", "C", "D", "E"))
})

output$answer <- renderText({
if (!is.null(RecentDecision()))
RecentDecision()
else
"No answer, yet"
})
}

shinyApp(ui = ui, server = server)

modalDialog inside a loop in shinyapp

Here is a way with shinyalert. That's strange, if you put a button in a Shiny alert, then clicking the button automatically closes the alert. That's why I use actionLink instead of actionButton. The app does not react as usual when clicking one button/link, so I use the onclick attribute which runs Shiny.setInputValue. As you can see I'm using local in the loop, otherwise that does not work as expected. But I think (I didn't test) that instead of using for+local, you can use an ordinary lapply.

library(shiny)
library(shinyalert)

dialog_filtro <- function(i, input, session, RESFIL, ID, LabelID, messagee) {
al <- shinyalert(
title = "Menssagem importante",
text = tagList(
tags$p(messagee),
actionLink(
ID[1], LabelID[1], class = "btn btn-primary",
onclick = sprintf(
'Shiny.setInputValue("%s", true, {priority: "event"});',
ID[1]
)
),
actionLink(
ID[2], LabelID[2], class = "btn btn-primary",
onclick = sprintf(
'Shiny.setInputValue("%s", true, {priority: "event"});',
ID[2]
)
),
),
html = TRUE,
session = session
)
observeEvent(input[[ID[1]]], {
RESFIL$dest[[i]] <- i + 10
closeAlert(id = al)
}, domain = session)
observeEvent(input[[ID[2]]], {
RESFIL$dest[[i]] <- i + 100
closeAlert(id = al)
}, domain = session)
}

ui <- fluidPage(
verbatimTextOutput("res")
)

server <- function(input, output, session) {

RESFIL <- reactiveValues(dest = NULL)
lista <- list(a = 2, a = 3)
grupdest <- rep(list(0), length(lista))
RESFIL$dest <- grupdest

for (i0 in 1:length(lista)) {
local({

i <- i0

if (lista[[i]] > 0) {

dialog_filtro(
i,
input,
session,
RESFIL,
ID = c(paste0("yes", i), paste0("no", i)),
LabelID = c("Yes", "No"),
messagee = paste0("This is the loop ", i)
)

} else {
RESFIL$dest[[i]] <- i + 1000
# removeModal() not clear what you want to do here: remove which modal?
}

})
}

output$res <- renderPrint({
RESFIL$dest
})
}

shinyApp(ui = ui, server = server)


Related Topics



Leave a reply



Submit