循环中生成的 insertUI 中的 observeEvent [英] observeEvent in insertUI generated in loop

查看:30
本文介绍了循环中生成的 insertUI 中的 observeEvent的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我使用 insertUI 以反应方式创建新对象时,我创建的所有观察者都可以正常工作,如您在以下虚拟代码中所见:

When I create new objects with insertUI in a reactive way, all the observers that I create work perfectly fine, as you can see in the following dummy code:

library(shiny)

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


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

  rv$counter <- 0

  observeEvent(input$adder,{
    rv$counter <- rv$counter + 1

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

    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))

但是,如果我使用 for 循环创建相同的对象,则似乎只有最后创建的对象的观察者可以工作,如下例所示:

However, if I create the same objects using a for loop, only the observers of the last object created seem to work, as you can see in the example below:

library(shiny)

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


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

  rv$counter <- 0
  rv$init <- T

  observeEvent(rv$init, {
    if(!rv$init) return(NULL)

    rv$init <- F

    for(i in 1:3) {
      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))

我做错了什么?

会不会和惰性求值有关?

Can it be related to lazy evaluation?

推荐答案

R中的for循环都运行在同一个范围内,这意味着循环中定义的变量将被所有迭代共享.如果您在每次循环迭代中创建一个访问此变量的函数,并假设它对于每次迭代都是唯一的,则会出现问题.

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.

这是一个简单的演示:

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
}

在这个 Shiny 应用程序中,observeEvent 处理程序访问局部变量 add,直到 for 循环结束后才被调用,并且 add 是它的最终值.

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.

有几种方法可以解决这个问题并为每个循环迭代创建一个独特的范围.我最喜欢的是使用 apply 函数来替换 for 循环.然后每个 apply 迭代都在其自己的函数中运行,因此每个项目的局部变量都是唯一的.

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))

请注意,我还删除了外部 observeEvent,因为无论如何服务器函数都会在会话初始化时运行.

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

这篇关于循环中生成的 insertUI 中的 observeEvent的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆