简体   繁体   English

在循环中生成的insertUI中的observeEvent

[英]observeEvent in insertUI generated in loop

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: 当我以反应方式用insertUI创建新对象时,我创建的所有观察者都可以正常工作,如下面的虚拟代码所示:

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

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: 但是,如果我使用for循环创建相同的对象,则仅在最后一个创建的对象的观察者看来有效,如下面的示例所示:

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

What am I doing wrong? 我究竟做错了什么?

Can it be related to lazy evaluation? 可能与懒惰评估有关吗?

For loops in R all run in the same scope, which means a variable defined in the loop will be shared by all iterations. 对于R中的循环,所有循环都在同一范围内运行,这意味着循环中定义的变量将由所有迭代共享。 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. 在此Shiny应用程序中, observeEvent处理程序访问局部变量add ,直到for循环结束且add为其最终值后才被调用。

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. 我最喜欢的是使用apply函数替换for循环。 Then each apply iteration runs in its own function so local variables are unique each item. 然后,每个apply迭代都在其自己的函数中运行,因此局部变量在每个项目中都是唯一的。

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. 请注意,由于服务器功能observeEvent在会话初始化上运行,因此我也删除了外部的observeEvent

I've found a workaround but I guess it should be done in a more efficient way. 我找到了一种解决方法,但我想应该以更有效的方式完成它。

It looks like this problem is related to lazy evaluation, so only the last object created has its observeEvent working. 看来此问题与延迟求值有关,因此只有最后创建的对象才具有其watchEvent的功能。 Thus, I've decided to create, for each iteration of the loop new variables using eval: 因此,我决定使用eval为循环的每次迭代创建新变量:

library(shiny)

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


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

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

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

    for(i in 1:4) {
      rv$counter <- rv$counter + 1

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

      coding <- paste0(
        "divId",add," <- paste0('adder_div_', add);
        elementFilterId",add," <- paste0('adder_object_', add);
        removeFilterId",add," <- paste0('remover_', add);
        insertUI(
          selector = '#placeholder',
          ui = tags$div(
            id = divId",add,",
            actionButton(inputId=removeFilterId",add,", label = \"Remove filter\", style = \"float: right;\"),
            textInput(inputId=elementFilterId",add,", label = paste0(\"Introduce text #\",rv$counter), value = '')
          )
        );

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

      eval(parse(text=coding))
    }

    rv$init <- F
  })
}

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

As it can be seen, each loop has new variables, so the lazy-evaluation problem is solved. 可以看出,每个循环都有新变量,因此可以解决延迟评估问题。

What I would like to now is if it can be done in a more efficient way. 我现在想说的是,是否可以更有效的方式来完成这项工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM