观察者在不应该渲染动态 UI 时触发

observers fire on render of dynamic UI when they should not

我面临的问题是 observers 链接到动态呈现的元素似乎在 render 上触发,但这不是我想要的。

这是个问题的原因是,我正在制作的颜色按钮链接到需要几秒钟才能呈现的图(plotly 小部件)

我添加了 ignoreInit = T 创建的 observers,但它们仍然在渲染时触发,这与直接在 UI

中链接到按钮构建的普通观察者不同

如何阻止链接到动态呈现 colourInput 的观察者在呈现元素时触发?

在下面的虚拟应用程序中,以简化形式重新创建了以下一系列事件:
一个模型吐出一个数字(通过演示应用程序中的测试按钮模拟) 根据这个数字,制作若干个colourInput按钮 每个都制作了相同数量的 observeEvent

不在虚拟应用程序中:当用户选择更改颜色时,绘图中相应的组会相应地重新着色

测试应用程序包含一个工作静态 colourInput 和一个演示问题场景的动态部分。

测试应用程序:

library(shiny)
 library("colourpicker")

THECOLORS <- c('#383838', '#5b195b','#1A237E', '#000080', '#224D17', '#cccc00', '#b37400',  '#990000', 
               '#505050',  '#a02ca0',  '#000099', '#2645e0', '#099441', '#e5e500', '#cc8400', '#cc0000', 
               '#737373', '#e53fe5', '#0000FF', '#4479e1',  '#60A830', '#ffff00','#e69500', '#ff0000', 
               '#b2b2b2', '#eb6ceb', '#6666ff', '#d0a3ff', '#9FDA40',  '#ffff7f', '#ffa500', '#ff4c4c')
ui <- fluidPage(      
      h1("WELCOME TO THE TEST APP", style = 'text-align: center; font-weight:bold' ),
    br(), 
    h3("STATIC PART: doesn't fire on startup, great!",  style = 'font-weight:bold'),
    div(colourpicker::colourInput(inputId = 'StaticColor', label = NULL, palette = "limited", allowedCols = THECOLORS, value = THECOLORS[14], showColour = "background", returnName = TRUE), 
    style = " height: 30px; width: 30px; border-radius: 6px;  border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"),
    br(),
    h3("Dynamic part: fires on render, NOT great!",  style = 'font-weight:bold'),
    actionButton(inputId = 'Tester', label = 'Click me'),
    br(),
    uiOutput('colorbutton')
  )

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

 values <- reactiveValues()
 values$mycolors <- THECOLORS

 observeEvent(input$Tester, { values$NrofButtons <- sample(1:10, 1) })

 observeEvent(values$NrofButtons, { 
  COLElement <-    function(idx){sprintf("COL_button-%s-%d",values$NrofButtons,idx)}

  output$colorbutton <- renderUI({
    lapply(1:values$NrofButtons, function(x) { 
      div(colourpicker::colourInput(inputId = COLElement(x), label = NULL, palette = "limited", allowedCols = values$mycolors, value = values$mycolors[x], showColour = "background", returnName = TRUE), 
      style = " height: 30px; width: 30px; border-radius: 6px;  border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px")  })
  })

  lapply(1:values$NrofButtons, function(x) { observeEvent(input[[COLElement(x)]], { print(input[[COLElement(x)]] )}, ignoreInit = T)  }) # make observer for each button

 })

 observeEvent(input[['StaticColor']], { print(input[['StaticColor']] )}, ignoreInit = T)

}

shinyApp(ui,server)

渲染应该始终由它们自己完成,并且是数据驱动的,而不是事件驱动的——所以我让渲染需要在渲染之前定义颜色的数量。当然,在单击按钮触发 observeEvent 之前,不会定义颜色数量。

总的来说仍然存在这样的问题,即每次单击按钮时都会为同一 ID 创建更多的观察者,正在研究一种在随后单击测试器按钮时自动销毁这些观察者的方法。

关键的添加是 ignoreInit = TRUE 在你的 observeEvent(input$Tester, {...}) 观察者中。

library(shiny)
library("colourpicker")

THECOLORS <- c('#383838', '#5b195b','#1A237E', '#000080', '#224D17', '#cccc00', '#b37400',  '#990000', 
               '#505050',  '#a02ca0',  '#000099', '#2645e0', '#099441', '#e5e500', '#cc8400', '#cc0000', 
               '#737373', '#e53fe5', '#0000FF', '#4479e1',  '#60A830', '#ffff00','#e69500', '#ff0000', 
               '#b2b2b2', '#eb6ceb', '#6666ff', '#d0a3ff', '#9FDA40',  '#ffff7f', '#ffa500', '#ff4c4c')
ui <- fluidPage(      
  h1("WELCOME TO THE TEST APP", style = 'text-align: center; font-weight:bold' ),
  br(), 
  h3("STATIC PART: doesn't fire on startup, great!",  style = 'font-weight:bold'),
  div(colourpicker::colourInput(inputId = 'StaticColor', label = NULL, palette = "limited", allowedCols = THECOLORS, value = THECOLORS[14], showColour = "background", returnName = TRUE), 
      style = " height: 30px; width: 30px; border-radius: 6px;  border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"),
  br(),
  h3("Dynamic part: fires on render, NOT great!",  style = 'font-weight:bold'),
  actionButton(inputId = 'Tester', label = 'Click me'),
  br(),
  uiOutput('colorbutton')
)


COLElement <- function(idx) sprintf("COL_button-%d", idx)

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

  values <- reactiveValues(previous_max = 1)

  observeEvent(input$Tester, {
    values$NrofButtons <- sample(1:10, 1)

    # reset counters for all observers
    for (i in seq(values$NrofButtons)) {
      values[[sprintf("observer%d_renders", i)]] <- 0L
    }

    # only initialize incremental observers
    lapply(values$previous_max:values$NrofButtons, function(x) { 
      observeEvent(input[[COLElement(x)]], { 
        # only execute the second time, since the `ignoreInit` isn't obeyed
        if (values[[sprintf("observer%d_renders", x)]] > 0) {
          print(input[[COLElement(x)]] )
        } else {
          values[[sprintf("observer%d_renders", x)]] <- 1L
        }

      }, ignoreInit = TRUE)
    }) # make observer for each button

    # record the max
    values$previous_max <- max(values$previous_max, max(values$NrofButtons))
  }, ignoreInit = TRUE)


  output$colorbutton <- renderUI({

    req(length(values$NrofButtons) > 0)

    lapply(1:values$NrofButtons, function(x) { 
      div(colourpicker::colourInput(
        inputId     = COLElement(x)
        , label       = NULL
        , palette     = "limited"
        , allowedCols = THECOLORS
        , value       = THECOLORS[x]
        , showColour  = "background"
        , returnName  = TRUE
      )
      , style = " height: 30px; width: 30px; border-radius: 6px;  border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"
      )
    })
  })

  observeEvent(input$StaticColor, { 
    print(input$StaticColor )
  }, ignoreInit = TRUE)

}

shinyApp(ui,server)