【问题标题】:Trigger a reaction after change of textOutput() in shiny在闪亮的 textOutput() 更改后触发反应
【发布时间】:2020-02-08 00:03:50
【问题描述】:

我正在尝试使用 JohnCoene/marker 包来突出显示闪亮应用程序中的文本部分。我的意图是首先使用一些服务器逻辑生成文本并使用textOutput 显示它。但是,我正在努力解决如何触发marker 在网站上出现文字后。将它放在同一个observeEvent() 中不起作用。

这是我的代表

# remotes::install_github("johncoene/marker")
library(shiny)
library(marker)

ui <- fluidPage(
  use_marker(),
  actionButton("click", "click"),
  textOutput("text_to_mark")
)
server <- function(input, output) {
   observeEvent(input$click, 
                {
                  output$text <- renderText("My house is yellow")
                })
  # observeEvent() below does not work. This is just for illustration
  observeEvent(input$text_to_mark,
               {
                 marker <- marker$new("#text_to_mark.shiny-text-output.shiny-bound-output")
                 marker$
                   unmark()$ # unmark all before we mark
                   mark("My house")
               })
}

# Run the application 
shinyApp(ui = ui, server = server)

reprex package (v0.3.0) 于 2019 年 10 月 10 日创建

为了说明:我可以通过添加第二个按钮来使标记工作,如下面的代码所示,但我正在寻找一种解决方案,当文本出现时它会被触发。

# remotes::install_github("johncoene/marker")
library(shiny)
library(marker)

ui <- fluidPage(
  use_marker(),
  actionButton("click", "click"),
  textOutput("text_to_mark"),
  actionButton("mark", "Mark!")
)

server <- function(input, output) {
  observeEvent(input$click, 
               {
                 output$text_to_mark <- renderText("My house is yellow")
               })
  observeEvent(input$mark,
               {
                 marker <- marker$new("#text_to_mark.shiny-text-output.shiny-bound-output")
                 marker$
                   unmark()$ # unmark all before we mark
                   mark("My house")
               })
}

# Run the application 
shinyApp(ui = ui, server = server)

reprex package (v0.3.0) 于 2019 年 10 月 10 日创建

【问题讨论】:

    标签: javascript r shiny reactive


    【解决方案1】:

    您可以使用 javascript:Is there a JavaScript / jQuery DOM change listener? 监听 DOM 更改。

    当发生变化时,你可以检查你的目标元素是否有文本:

      hasText = document.getElementById("text_to_mark").innerHTML != ""
    

    请注意,我假设您的元素的 id 为“text_to_mark”。

    您可以使用“发送到 R”的结果

      Shiny.onInputChange("hasText", hasText);
    

    在 R 端,您将通过侦听 input$hasText 知道元素是否有文本。

    所以你可以添加:

    observeEvent(input$hasText,{
       ...
    })
    

    您可以使用tags$script(jsCode) 或使用shinyjs 添加到您的应用中的javascript。

    一个可重现的例子:

    library(shiny)
    library(marker)
    
    jsCode <- '
    MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    
    var observer = new MutationObserver(function(mutations, observer) {
      console.log(mutations, observer);
      hasText = document.getElementById("text_to_mark").innerHTML != ""
      Shiny.onInputChange("hasText", hasText);
    });
    
    observer.observe(document, {
      subtree: true,
      attributes: true
    });
    '
    
    ui <- fluidPage(
      use_marker(),
      tags$script(jsCode),
      actionButton("click", "click"),
      textOutput("text_to_mark"),
      actionButton("mark", "Mark!")
    )
    
    server <- function(input, output) {
    
      observeEvent(input$click, {
                     output$text_to_mark <- renderText("My house is yellow")
                   })
      observeEvent(input$hasText,{
                     marker <- marker$new("#text_to_mark.shiny-text-output.shiny-bound-output")
                     marker$
                       unmark()$ # unmark all before we mark
                       mark("My house")
                   })
    }
    
    # Run the application 
    shinyApp(ui = ui, server = server)
    

    请注意,这仅适用于文本的首次出现。如果您还想收听文本的更改,可以将文本发送到 R 并在 R 端检查文本是否已更新。不确定这里是否需要。

    【讨论】:

    • 确实想听听文字的变化。我已将其更改为Text = document.getElementById("text_to_mark").innerHTML。似乎工作正常。
    【解决方案2】:

    侦听 DOM 更改是一种选择,但是您的方法已经表明有一个纯闪亮(非自定义 JS)解决方案,只需单击一次,所以问题是如何只需单击一次.我建议使用invalidateLater 并将其包装在if 语句中,以防止它像看到的here 一样一遍又一遍地运行。

    诀窍是在observe 语句中运行标记调用。在此处包含invalidateLater 并将其包装在if 条件中,并使用计数器计算语句已执行的次数。玩转毫秒数和计数,在我的情况下,它适用于 if(isolate(val$cnt) &lt; 1)invalidateLater(1000)。不要忘记将您的计数器包裹在 isolate 中,否则它会陷入循环。

    还要注意input$click不仅将文本写入reactiveValue,而且还将计数器val$cnt重置为0,以便您可以在新文本上再次使用invalidateLater。如果您想使用 observeEvent 或类似的方法更新文本,相同的过程将对您有所帮助。只需确保将计数器也重置为 0,然后突出显示在您的新文本部分上起作用。

    # remotes::install_github("johncoene/marker")
    library(shiny)
    library(marker)
    
    ui <- fluidPage(
      use_marker(),
      actionButton("click", "click"),
      textOutput("text_to_mark")
    )
    
    server <- function(input, output) {
    
      val <- reactiveValues(cnt = 0,
                            text = NULL)
    
      observeEvent(input$click, {
        val$text <- "My house is yellow"
        val$cnt <- 0
    
      })
    
    
      observe({
        if(isolate(val$cnt) < 1) {
          invalidateLater(1000)
        }
    
        marker <- marker$new("#text_to_mark.shiny-text-output.shiny-bound-output")
        marker$
          unmark()$ # unmark all before we mark
          mark("My house")
    
        val$cnt = isolate(val$cnt) + 1
        })
    
      output$text_to_mark <-renderText({
        val$text
      })
    }
    
    # Run the application 
    shinyApp(ui = ui, server = server)
    

    【讨论】:

    • 感谢您提供替代解决方案。我更喜欢 DOM 变体而不是 invalidateLater。我发现它更具可读性,并且不依赖于任意时间。
    猜你喜欢
    • 2019-06-30
    • 1970-01-01
    • 2015-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-16
    • 2016-06-16
    相关资源
    最近更新 更多