Блестящие модули: доступ и изменение реактивных значений, расположенных внутри серверной функции модуля извне? - PullRequest
0 голосов
/ 14 декабря 2018

У меня есть блестящий модуль, который использует реактивные значения для хранения своего внутреннего состояния.В приведенном ниже примере это используется только для вывода числа, которое изменилось на входе, но мои реальные сценарии использования более сложные.

Теперь я хочу создать функцию, которую можно использовать для установки одного из этих модулей в состояние другого, в том числе во внутреннее состояние - или, в более общем случае: я хочу создать функцию updateModule, которая также можетобновите внутреннее состояние.

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

Другой связанный вопрос для моей специальной целиявляется: Как я могу предотвратить обновление внутреннего responsetiveValue при обновлении Input - или как я могу сбросить его (возвращаясь к основному вопросу?

На данный момент я знаю о двух возможных обходных путях:

  1. Сохранить внутреннее состояние в скрытом вводе
  2. Использовать логику вызова по ссылке в data.table A , B (см. Ниже). Существуют другие способы реализации вызова по ссылке , но они еще не использовались.

Однако мне было бы интересно узнать,Есть более прямое решениеКроме того, внутренние компоненты, которые я хочу обновить, представляют собой более сложные списки.

Пример кода

#Problem: How to change reactiveValues from the outside?

library(shiny)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
        column(width=9,
               sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
               ),
        column(width=2,
               textOutput(ns("changesCount") )
               )
        )
}

synchModule<-function(session, targetModule, oldModule){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule() )

  ##Accessing and changing internal Value of targetModule??

}

module<- function(input, output, session){
  rv<-reactiveValues(changesCount=0)

  observeEvent(input$sl,rv$changesCount<-rv$changesCount+1)

  output$changesCount=renderText(rv$changesCount)

  return(reactive({
    ret <- input$sl
    attr(ret,"changesCount")<-rv$changesCount
    ret
  }))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, synchModule(session,"module2",module1)
               )

  output$module1state=renderPrint(module1() )
  output$module2state=renderPrint(module2() )

}

shinyApp(ui, server)

Обходной путь 1: использование скрытого NumericInput

#Problem: How to change reactiveValues from the outside?
##Workaround using hidden input

library(shiny)
library(shinyjs)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
        column(width=9,
               sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
               ),
        column(width=2,
               textOutput(ns("changesCount") ),
                          hidden(numericInput(
                            ns("changesCountNumeric"), "If you can see this, you forgot useShinyjs()", 0)
                          )
               )
        )
}

synchModule<-function(session, targetModule, oldModule){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule() )

  updateNumericInput(session,ns("changesCountNumeric"), 
                     value=attr(oldModule(),"changesCount")-1) #-1 to account for updating slider itself, 

}

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

  observeEvent(input$sl,
               updateNumericInput(session,"changesCountNumeric", 
                                  value=input$changesCountNumeric+1)
  )

  output$changesCount=renderText(input$changesCountNumeric)

  return(reactive({
    ret <- input$sl
    attr(ret,"changesCount")<-input$changesCountNumeric
    ret
  }))

}



ui=fluidPage(
  useShinyjs(),
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, synchModule(session,"module2",module1)
               )

  output$module1state=renderPrint(module1() )
  output$module2state=renderPrint(module2() )

}

shinyApp(ui, server)

Ps: IЯ не уверен, стоит ли ставить мои обходные пути как решения или нет.

Ответы [ 2 ]

0 голосов
/ 15 декабря 2018

Я не прочитал весь ваш пост, потому что он, кажется, содержит несколько вопросов, но я решу главный вопрос, первый, выделенный жирным шрифтом: Как я могу получить доступ к внутренним реактивным значениям модуля и изменить их извне?

Прежде всего, чтобы перейти к предложенному мною решению, я хочу предложить другой способ возврата информации из модуля.Вместо использования значения и атрибута значения вы можете вернуть список, с которым гораздо проще работать.Вот немного измененное приложение:

library(shiny)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
    column(width=9,
           sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
    ),
    column(width=2,
           textOutput(ns("changesCount") )
    )
  )
}

synchModule<-function(session, targetModule, oldModule){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule$value() )

  ##Accessing and changing internal Value of targetModule??

}

module<- function(input, output, session){
  rv<-reactiveValues(changesCount=0)

  observeEvent(input$sl,rv$changesCount<-rv$changesCount+1)

  output$changesCount=renderText(rv$changesCount)

  return(list(
    value = reactive({ input$sl }),
    changes = reactive({ rv$changes }),
    print = reactive({ paste0("Num: ", input$sl, "; changes: ", rv$changesCount) })
  ))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, 
               synchModule(session,"module2",module1)
  )

  output$module1state=renderPrint(module1$print() )
  output$module2state=renderPrint(module2$print() )

}

shinyApp(ui, server)

Надеюсь, вы поймете, что его легче читать, работать и расширять.

Теперь к вашему основному вопросу: как получить доступ и изменить внутренние реактивные значения модуля?

Нет.По крайней мере, не напрямую.

Внутреннее состояние, как правило, лучше не изменять кому-либо еще.Существует широко используемая парадигма, называемая методами получения и установки, которую я бы использовал здесь.Вы не заходите напрямую в другой модуль и не изменяете его состояние - это полностью нарушает принцип, лежащий в основе модулей (быть независимым и изолированным).Вместо этого у нас может быть модуль, возвращающий метод получения - в нашем случае это означает возвращение его значений (как я делал выше со списком value и changes), а также метод установки - который будет функцией, котораякто-то еще может позвонить, чтобы установить значения внутри модуля.

Если это пока не имеет смысла на 100%, вот суть того, что я имею в виду: добавьте этот «установщик» в список возвратаmodule:

setState = function(value, count) {
  updateSliderInput(session, "sl", value = value)
  rv$changesCount <- count - 1
}

И теперь нам больше не нужно заходить в модуль и напрямую изменять его состояние, мы можем просто вызвать setState()!Вот полный измененный код:

library(shiny)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
    column(width=9,
           sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
    ),
    column(width=2,
           textOutput(ns("changesCount") )
    )
  )
}

synchModule<-function(session, targetModule, oldModule){
  oldModule$setState(targetModule$value(), targetModule$count())
}

module<- function(input, output, session){
  rv<-reactiveValues(changesCount=0)

  observeEvent(input$sl,rv$changesCount<-rv$changesCount+1)

  output$changesCount=renderText(rv$changesCount)

  return(list(
    value = reactive({ input$sl }),
    count = reactive({ rv$changesCount }),
    print = reactive({ paste0("Num: ", input$sl, "; changes: ", rv$changesCount) }),
    setState = function(value, count) {
      updateSliderInput(session, "sl", value = value)
      rv$changesCount <- count - 1
    }
  ))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state")

)

server= function(input, output, session) {
  module1<-callModule(module,"module1")
  module2<-callModule(module,"module2")

  observeEvent(input$synchButton, 
               synchModule(session,module1,module2)
  )

  output$module1state=renderPrint(module1$print() )
  output$module2state=renderPrint(module2$print() )

}

shinyApp(ui, server)
0 голосов
/ 14 декабря 2018

Теперь, когда я убедил себя, что data.table также может хранить списки в качестве элементов, я публикую обходной путь 2 в качестве ответа.Тем не менее, я по-прежнему заинтересован в более прямых способах доступа к реактивным значениям внутри модулей.

Хранение списков в data.table

test=data.table(x=1:2, y=list(list(a="dsf", b="asf"), list("2dsf")))
test
test[1,y]
test[2,y

Обходной путь 2: использование data.table

#Problem: How to change reactiveValues from the outside?
## Using call-by-reference of data.table

library(shiny)
library(data.table)

moduleUI <- function(id, label=id,min = 0,max = 100,value = 30){
  ns <- NS(id)

  fluidRow(
        column(width=9,
               sliderInput(ns("sl"), label=label, min=min, max=max, value=value)
               ),
        column(width=2,
               textOutput(ns("changesCount") )
               )
        )
}

synchModule<-function(session, targetModule, oldModule, dt){
  ns<-NS(targetModule)
  updateSliderInput(session,ns("sl"),value=oldModule() )

  dt[name==targetModule, count:=attr(oldModule(),"changesCount")-1]

}

module<- function(input, output, session, dt, id){
  rv<-reactiveValues(changesCount=dt, 
                     triggerupdate=0)

  observeEvent(input$sl,{

    rv$changesCount[name==id,count:=count+1] 
    rv$triggerupdate=rv$triggerupdate+1
                     })

  output$changesCount=renderText({
    rv$triggerupdate
    rv$changesCount[name==id, count]
    })

  return(reactive({
    ret <- input$sl
    attr(ret,"changesCount")<-rv$changesCount[name==id,count]
    ret
  }))

}



ui=fluidPage(
  moduleUI("module1"),
  moduleUI("module2"),
  actionButton("synchButton", "Set Module 2 to state of Module 1."),

  textOutput("module1state"),
  textOutput("module2state"),
  p(),
  p("dt doesn't refresh if not triggered:"),
  tableOutput("dtstate"),
  actionButton("RefreshDtButton", "Show and refresh state of dt"),
  tableOutput("dtstate2")

)

server= function(input, output, session) {
  dt<-data.table(name=c("module1","module2"),
                 count=0)
  module1<-callModule(module,"module1",dt,"module1") #id must be repeated
  module2<-callModule(module,"module2", dt, "module2")

  observeEvent(input$synchButton, synchModule(session,"module2",module1, dt)
               )
  observeEvent(input$RefreshDtButton, output$dtstate2<-renderTable(dt))

  output$module1state=renderPrint(module1() )
  output$module2state=renderPrint(module2() )
  output$dtstate=renderTable(dt) ##No reactivity  without triggering with data.table


}

shinyApp(ui, server)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...