Как динамически генерировать и захватывать пользовательский ввод из ModalDialog в модуле?

#javascript #r #shiny

Вопрос:

Справочная информация: я создаю приложение для регистрации доноров / образцов. Рабочий процесс как таковой:

  1. Пользователи выбирают, от какого поставщика исходят доноры / образцы.
  2. Пользователи загружают файл (используя datamods::import_file_server() ).
  3. Запустите некоторую проверку для этого файла (т.Е. x количество строк, y количество столбцов и т. Д., Используя datamods::validation_server() )
  4. Выберите уже существующее или создайте новое сопоставление полей.

    4a) Сопоставление полей — это механизм, позволяющий пользователям сопоставлять столбцы загруженных файлов со столбцами таблицы нашей базы данных.

    4b) Если Создать новый, modalDialog должно появиться окно, в котором отображаются данные из 2 столбцов, один столбец для имен столбцов файлов, один столбец selectInput() ‘s, которые заполняются полями (столбцами) таблицы нашей базы данных.

Я настроил это таким образом, что a registration_module обрабатывает 1-3, а затем внутри этого у меня есть вложенный fieldmapping_module , который принимает в качестве входных данных:

  1. (проверенные) данные файла
  2. выбор поставщика
  3. и столбцы базы данных

Проблема: кажется, я не могу сделать динамически генерируемые selectInput() ‘ы «видимыми» для Shiny. Ниже fieldmapping_module приведен код.

 
### FieldMapping module ####

fieldmappingUI <- function(id) {
  
  tagList(
    div(
      column(8,
             selectInput(
               inputId = NS(id, "fieldmapping_selection"),
               label = "Select Field Mapping",
               choices = c("Choice 1", "Choice 2", "Choice 3") 
             ),
      ),
      column(4,
             shinyWidgets::actionBttn(
               inputId = NS(id, "create_new_fieldmapping_btn"),
               label = "Create New",
               icon = icon("file-alt"),
               size = "sm"
             )
      ),
      style = "display:inline-block",
      class = "form-group shiny-input-container")
  )
}


fieldmappingServer <- function(id, file_data, vendor_selection, db_cols) {
  stopifnot(is.reactive(file_data))
  stopifnot(is.reactive(vendor_selection))
  
  moduleServer(id, function(input, output, session) {
    
    ns <- session$ns
  
    #observe for creation of new FieldMappings
    observeEvent(input$create_new_fieldmapping_btn, {
      
      fieldmapping_table <- data.frame(
        "File Columns" = colnames(file_data()),
        "DB Field Mapping" = rep("", ncol(file_data()))
      )
      
      #browser()
      
      for(i in seq_len(nrow(fieldmapping_table))) {
        fieldmapping_table[i,"DB.Field.Mapping"] <- as.character(selectInput(
          inputId = glue::glue("fieldmap_select_{fieldmapping_table$File.Column[i]}"),
          label = NULL,
          choices = db_cols
        ))
      }
      #browser()

      #display the table
      showModal(modalDialog(
        
        renderDataTable({
          DT::datatable(fieldmapping_table,
                        escape = 2,
                        selection = "none",
                        filter = 'none',
                        options = list(
                          dom = 't' 
                          ),
                        callback = JS("table.rows().every(function(i, tab, row) {
                                                       var $this = $(this.node());
                                                       $this.attr('id', this.data()[0]);
                                                       $this.addClass('shiny-input-slider-input');
                                                         });
                                                       Shiny.unbindAll(table.table().node());
                                                       Shiny.bindAll(table.table().node());")
          )

            
          }),
        title = "New Field Mapping",
        footer = tagList(
          actionButton(ns("submit_fieldmapping"), label = "Submit", icon = icon("paper-plane")),
          modalButton(label = "Close", icon = icon("window-close"))
          )
      ))
      
    })
   
    observeEvent(input$submit_fieldmapping, {
      browser()
    })
    

  })
  
}
 

Исключенное поведение: например, допустим, я загрузил файл с 3 столбцами: Subject_ID , Col_A , и Col_B , и он прошел проверку (сделано в registration_module , не показано).

Когда я нажимаю кнопку отправки modalDialog , и приложение приостанавливается из-за browser() вызова, я исключаю, чтобы иметь доступ к input$fieldmap_select_[column name] (например: input$fieldmap_select_Subject_ID ), но я этого не делаю. Я думал, что пользовательский обратный вызов JS достигнет этого, поскольку, похоже, он сработал здесь (и с другим кодом / приложениями, с которыми я столкнулся во время поиска в Google).

На browser() паузе, если я вхожу input в консоль, чтобы просмотреть список входных данных, я действительно вижу созданные мной динамически сгенерированные входные данные (первые три), но они все NULL , несмотря на то, что они «установлены» в окне ModalDialog. введите описание изображения здесь

Я не слишком хорошо разбираюсь во взаимодействии Javascript / Shiny, но был бы признателен за любую помощь, которую я мог бы получить с этим! Что я делаю не так?

(перекрестный пост на форуме сообщества RStudio)

Ответ №1:

Понял это. Мне нужно было включить ns функцию на стороне сервера в динамическую генерацию полей ввода. ie:

 for(i in seq_len(nrow(fieldmapping_table))) {
        fieldmapping_table[i,"DB.Field.Mapping"] <- as.character(selectInput(
          inputId = ns(glue::glue("fieldmap_select_{fieldmapping_table$File.Column[i]}")),
          label = NULL,
          choices = db_cols
        ))
      }
 

обратите внимание на ns() вызов вокруг inputId .