【问题标题】:observers fire on render of dynamic UI when they should not观察者在不应该渲染动态 UI 时触发
【发布时间】:2018-10-12 15:12:41
【问题描述】:

我面临的问题是链接到动态渲染元素的observers 似乎在render 上触发,而这不是我想要的样子。

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

我添加了 ignoreInit = Tobservers 创建的,但它们仍然会在渲染时触发,这与链接到直接在 UI 中构建的按钮的普通观察者不同

当元素被渲染时,如何阻止链接到动态渲染的colourInput 的观察者触发?

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

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

测试应用包含一个工作的静态 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)

【问题讨论】:

    标签: r dynamic shiny observers


    【解决方案1】:

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

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

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

    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)
    

    【讨论】:

    • 现在测试它,似乎确实有效,在我更复杂的应用程序中也是如此。为什么在 nrofbuttons 的观察者之外进行输出会对行为产生如此大的影响?
    • @Mark 这不是最佳实践——关键(正如我在答案中添加的那样)是ignoreInit 中的observeEvent
    • 我知道你提到的关于创建新实例的一些扩展,但我从未设法找到解决这个问题的方法,到目前为止它还没有造成明显的问题,但是如果你经常点击(或者当然是多次运行模型),你最终可能会得到一大堆观察者。这个代码设计来自我一年多前从另一个帮助者那里发布的一个问题,我最近决定是时候修复它触发使用颜色输入两次的绘图引起的错误
    • @Mark 那么这个答案是否解决了您提出的具体问题?如果没有,我该如何改进?
    • hm 奇怪...我刚刚完成了我的真实应用程序的调整(将各种动态输出元素放在观察者之外,观察者查看确定要制作多少个各种元素的数字反应值......并且然后再次测试了您的最新答案,似乎只要您单击测试按钮,它就会再次触发颜色打印,因此您的答案不会改变看起来的问题
    猜你喜欢
    • 1970-01-01
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2018-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多