【问题标题】:What's the best way to write custom JavaScript for R Shiny Module that uses module's namespace? [closed]为使用模块命名空间的 R Shiny Module 编写自定义 JavaScript 的最佳方法是什么? [关闭]
【发布时间】:2018-08-02 10:16:41
【问题描述】:

我有一个复杂的 Shiny 应用程序,需要使用自定义 JavaScript 代码。该应用程序由在具有不同名称空间的多个位置调用的模块组成。我需要一些 JavaScript 代码与 R 代码一起“模块化”,即使用模块名称空间。我可以通过创建一个包含 JS 代码的自定义字符串并使用 shinyjs::runjs() 函数(下面的示例)执行它来使其工作。对于给定的示例,这是一个公平的解决方案。然而,将更复杂的超过 100 行 JavaScript 代码放入粘贴有标识符的字符串中似乎非常容易出错且不是最佳解决方案(缺乏突出显示、令人痛苦的格式化等)。有没有更好的方法来达到同样的效果?

library(shiny)
library(shinyJS)

myModuleUI <- function(id) {
    ns <- NS(id)

    tagList(
        div(id = ns("clickableElement"), class = "btn btn-primary", "Click Me"),
        div(id = ns("highlightableElement"), "This (and only this!) text should be highlighted on click")
    )
}

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

    ns <- session$ns

    shinyjs::runjs(paste0("
        $('#", ns("clickableElement"), "').click(function() {
            $('#", ns("highlightableElement"), "').css('background', 'yellow');
        })   
    "))
}

ui <- fluidPage(
    useShinyjs(),
    tabsetPanel(
        tabPanel(
            "Instance 1",
            myModuleUI("one")
        ),
        tabPanel(
            "Instance 2",
            myModuleUI("two")
        )
    )
)

server <- function(input, output) {
    callModule(myModule, "one")
    callModule(myModule, "two")
}

shinyApp(ui = ui, server = server)

更新

为了将来参考,我决定分享我最终实施的解决方案。我最终为每个模块创建了一个 JS 文件,其中包含一个将命名空间作为唯一参数的函数。此函数使用此命名空间创建所有需要的对象和绑定。然后我在模块的开头使用shinyjs 调用该单个函数。这允许我将 JS 代码保存在一个单独的文件中,这解决了最初的问题并使代码易于管理(尤其是如果您有很多 JS 代码)。

app.R

library(shiny)
library(shinyjs)

myModuleUI <- function(id) {
    ns <- NS(id)

    tagList(
        div(id = ns("clickableElement"), class = "btn btn-primary", "Click Me"),
        div(id = ns("highlightableElement"), "This (and only this!) text should be highlighted on click")
    )
}

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

    ns <- session$ns

    shinyjs::runjs(paste0("myModuleJS('", ns(""), "');"))
}

ui <- fluidPage(
    useShinyjs(),
    tags$head(
        tags$script(src = "myModuleJS.js")
    ),
    tabsetPanel(
        tabPanel(
            "Instance 1",
            myModuleUI("one")
        ),
        tabPanel(
            "Instance 2",
            myModuleUI("two")
        )
    )
)

server <- function(input, output) {
    callModule(myModule, "one")
    callModule(myModule, "two")
}

shinyApp(ui = ui, server = server)

www/myModuleJS.js

function myModuleJS(ns) {
    $('#' + ns + 'clickableElement').click(function() {
        $('#' + ns + 'highlightableElement').css('background', 'yellow');
    });
}

【问题讨论】:

  • 使用命名空间作为消息的一部分向浏览器发送自定义消息,以便它找到正确的客户端?

标签: javascript r shiny


【解决方案1】:

我认为有两种方法可以解决这个问题,尽管它们不是非常优雅的解决方案。

第一个解决方案是定义几个全局函数,例如getJavascriptSelector &lt;- function(id, session){paste0("'#", session$ns(id), "'")}。这样每次使用选择器的时候还是需要调用这个函数,不能在单独的文件中定义js。

由于您正在运行的 javascript 都是字符串,另一个解决方案是您可以定义一个函数,比如 wrapJavascriptWithNamespace(script, ns),它接受脚本字符串(或 js 文件)和 ns 对象。该函数将使用正则表达式来匹配选择器并将其替换为会话命名空间的选择器。这样,您也可以重用您的 javascript 代码,但需要更多工作,并且存在潜在问题,例如如果此代码需要引用全局范围元素怎么办(这可以通过使用一些关键字标记您的 javascript 来解决一个模板)。假设您将示例中使用的代码放在字符串中:

$('#[shiny-namespace]clickableElement').click(function() {
    $('#[shiny-namespace]highlightableElement').css('background', 'yellow');
})  

您可以轻松解析此字符串并将shiny-namespace 替换为ns()。当您需要将此字符串作为常规 javascrip 与模板时,您只需要编写另一个函数来去除 [shiny-namespace] 标记。

【讨论】:

  • 谢谢!这给了我几个好主意。我认为编写一个用命名空间替换占位符的函数有点矫枉过正,因为我想无论如何我都会为每个模块创建一个单独的专用 JS 文件。在我的例子中,每个文件都包含一个函数,该函数只将命名空间作为唯一参数。此函数只需将现有 id 与该命名空间参数连接起来即可创建所有需要的对象和绑定。然后,我只需使用shinyjs(传递命名空间)在模块服务器代码的开头调用该单个函数。
  • @Mikolaj,听起来不错。实际上,您的方式也可以使其可重用,甚至更加优雅。我对 JQuery 不是很熟悉,但我相信您可以通过将名称空间和选择器连接起来,将 $ 重新定义为本地范围内原始 $ 的包装器。这样你就不需要重写你的javascript代码了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 2021-01-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多