为使用模块命名空间的 R Shiny 模块编写自定义 JavaScript 的最佳方法是什么?

Mik*_*laj 6 javascript r shiny

我有一个复杂的 Shiny 应用程序,需要使用自定义 JavaScript 代码。该应用程序由在具有不同命名空间的多个地方调用的模块组成。我需要一些 JavaScript 代码与 R 代码一起“模块化”,即使用模块命名空间。我能够通过创建一个包含 JS 代码的自定义字符串并使用shinyjs::runjs()函数执行它来使其工作(下面的示例)。对于给定的示例,这是一个公平的解决方案。然而,将更复杂的超过一百行 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)
Run Code Online (Sandbox Code Playgroud)

更新

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

应用程序

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)
Run Code Online (Sandbox Code Playgroud)

www/myModuleJS.js

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

Keq*_* Li 2

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

第一个解决方案是定义几个全局函数,例如getJavascriptSelector <- 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');
})  
Run Code Online (Sandbox Code Playgroud)

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