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

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

问题描述

当我以反应方式用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))

我在做什么错了?

这与懒惰评估有关吗?

推荐答案

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

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中的watchEvent的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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