включая пространство имен в вызове Javascript в модуле Shiny - PullRequest
0 голосов
/ 26 марта 2020

Я использую в своих приложениях Блестящие пользовательские функции блестящий вывод, которые облегчают создание динамических таблиц c, чтобы пользователи могли вводить данные из флажков, раскрывающихся списков, текстовых полей и т. Д. c. к каждому ряду таблицы. Эта функциональность основана на вызове Javascript в пользовательском интерфейсе и при генерации вывода из DT :: renderDataTable. Это все прекрасно работает, когда я использую этот инструмент в основном приложении. R, но я не могу заставить его работать, когда помещен в модуль. Проблема почти наверняка состоит в том, что мне нужно включить пространство имен в вызовы к Javascript, когда он находится в модуле, но я не знаю, где это сделать. Я включил код из игрушечного примера, иллюстрирующего проблему.

Заранее благодарен за любую помощь.


# libraries ---------------------------------------------------------------

library(shiny)
library(dplyr)
library(DT)


# module UI ---------------------------------------------------------------

moduleUI <- function(id) {

    ns <- NS(id)

    fluidPage(
        DT::dataTableOutput(ns("moduleOutput")),
        tags$script(HTML("Shiny.addCustomMessageHandler('unbind-DT', function(id) {
                                               Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
                                               })")),
        DT::dataTableOutput(ns("moduleWithRatingOutput"))
    ) # close the page

}


# module server -----------------------------------------------------------

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


    # helper function to add interactive elements to rows of a table
    shinyInputOther <- function(FUN, len, id, ...) {
        inputs = character(len)
        for (i in seq_len(len)) {
            inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
        }
        inputs
    }

    # helper function to extract interactive elements from rows of a table
    shinyValue <- function(id, len) {
        unlist(lapply(seq_len(len), function(i) {
            value = input[[paste0(id, i)]]
            if (is.null(value)) NA else value
        }))
    }


    # reactive data
    mtcarsReactive <- reactive({


        head(mtcars)

    }) # close reactive


    # reactive data output - allows adding a rating to each row/car
    output$moduleOutput <- DT::renderDataTable({

        mtcarsReactive() %>% 
            mutate(
                rating = shinyInputOther(FUN = selectInput,
                                         len = nrow(mtcarsReactive()),
                                         id = 'rating_',
                                         choices=c("high", "med", "low"),
                                         width = "60px")
            )
    },
    selection = 'none',
    escape = FALSE,
    server = FALSE,
    options = list(bFilter = 0,
                   bLengthChange = F,
                   bPaginate = F,
                   bSort = F,
                   preDrawCallback = JS('function() { 
                           Shiny.unbindAll(this.api().table().node()); }'), 
                   drawCallback = JS('function() { 
                        Shiny.bindAll(this.api().table().node()); } ') 
    ),
    rownames = F) # close output


    # reactive data with added rating for each row/car
    mtcarsWithRating <- reactive({

        mtcarsReactive() %>%
            mutate(
                rating = shinyValue("rating_",
                                    nrow(mtcarsReactive())
                )
            )

    }) # close reactive


    # reactive data output - includes rating for each row/car
    output$moduleWithRatingOutput <- DT::renderDataTable({

        mtcarsWithRating()

    },
    selection = 'none',
    escape = FALSE,
    server = FALSE,
    options = list(bFilter = 0,
                   bLengthChange = F,
                   bPaginate = F,
                   bSort = F
    ),
    rownames = F) # close output

} # close module


# app UI ------------------------------------------------------------------

ui <- navbarPage("mtcars",

                 # app tab
                 tabPanel("app",
                          fluidPage(
                              DT::dataTableOutput("mtcarsOutput"),
                              tags$script(HTML("Shiny.addCustomMessageHandler('unbind-DT', function(id) {
                                               Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
                                               })")), 
                              DT::dataTableOutput("mtcarsWithRatingOutput")
                          ) # close the page
                 ), # close tab


                 # module tab
                 tabPanel("module",
                          moduleUI("mtcarsModule")
                 ) # close tab

) # close navbarPage


# app server --------------------------------------------------------------

server <- function(input, output) {


    # helper function to add interactive elements to rows of a table
    shinyInputOther <- function(FUN, len, id, ...) {
        inputs = character(len)
        for (i in seq_len(len)) {
            inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
        }
        inputs
    }

    # helper function to extract interactive elements from rows of a table
    shinyValue <- function(id, len) {
        unlist(lapply(seq_len(len), function(i) {
            value = input[[paste0(id, i)]]
            if (is.null(value)) NA else value
        }))
    }


    # reactive data
    mtcarsReactive <- reactive({

        head(mtcars)

    }) # close reactive


    # reactive data output - allows adding a rating to each row/car
    output$mtcarsOutput <- DT::renderDataTable({

        mtcarsReactive() %>% 
            mutate(
                rating = shinyInputOther(FUN = selectInput,
                                         len = nrow(mtcarsReactive()),
                                         id = 'rating_',
                                         choices=c("high", "med", "low"),
                                         width = "60px")
            )
    },
    selection = 'none',
    escape = FALSE,
    server = FALSE,
    options = list(bFilter = 0,
                   bLengthChange = F,
                   bPaginate = F,
                   bSort = F,
                   preDrawCallback = JS('function() { 
                           Shiny.unbindAll(this.api().table().node()); }'), 
                   drawCallback = JS('function() { 
                        Shiny.bindAll(this.api().table().node()); } ') 
    ),
    rownames = F) # close output


    # reactive data with added rating for each row/car
    mtcarsWithRating <- reactive({

        mtcarsReactive() %>%
            mutate(
                rating = shinyValue("rating_",
                                    nrow(mtcarsReactive())
                )
            )

    }) # close reactive


    # reactive data output - includes rating for each row/car
    output$mtcarsWithRatingOutput  <- DT::renderDataTable({

        mtcarsWithRating()

    },
    selection = 'none',
    escape = FALSE,
    server = FALSE,
    options = list(bFilter = 0,
                   bLengthChange = F,
                   bPaginate = F,
                   bSort = F
    ),
    rownames = F) # close output


    # call module -------------------------------------------------------------

    callModule(module = mtcarsModule,
               id = "mtcarsModule")    


} # close server

# run the application -----------------------------------------------------

shinyApp(ui = ui, server = server)

РЕДАКТИРОВАТЬ (2020-03-26): В случае, если другие столкнутся с этой проблемой исправление должно было включать session$ns в id вызова shinyInputOther. Используя приведенный пример, он выглядит так:

rating = shinyInputOther(
  FUN = selectInput,
  len = nrow(mtcarsReactive()),
  id = paste0(session$ns('rating_')),
  choices=c("high", "med", "low"),
  width = "60px")
...