如果选择依赖于另一个输入并且 server = TRUE,shinyStore 无法恢复 selectizeInput 的选定值

2024-03-22

这是这个问题的后续问题(如果选择取决于另一个输入,shinyStore 无法恢复 selectizeInput 的选定值 https://stackoverflow.com/q/68264817/7669809)我之前问过。我已经找到答案了(https://stackoverflow.com/a/68290227/7669809 https://stackoverflow.com/a/68290227/7669809)。然而,现在我意识到我的答案并不完整。请看下面的代码。这与我之前的问题和答案相同,只是我设置了server = TRUE为了第一updateSelectizeInput,这使得本地存储无法工作。如果我能使用那就太好了server = TRUE因为在我的现实世界的例子中我的选择selectizeInput有很多。

### This script creates an example of the shinystore package

# Load packages
library(shiny)
library(shinyStore)

ui <- fluidPage(
  headerPanel("shinyStore Example"),
  sidebarLayout(
    sidebarPanel = sidebarPanel(
      initStore("store", "shinyStore-ex1"),
      selectizeInput(inputId = "Select1", label = "Select A Number",
                     choices = as.character(1:3),
                     options = list(
                       placeholder = 'Please select a number',
                       onInitialize = I('function() { this.setValue(""); }'),
                       create = TRUE
                     ))
    ),
    mainPanel = mainPanel(
      fluidRow(
        selectizeInput(inputId = "Select2", 
                       label = "Select A Letter",
                       choices = character(0),
                       options = list(
                         placeholder = 'Please select a number in the sidebar first',
                         onInitialize = I('function() { this.setValue(""); }'),
                         create = TRUE
                       )),
        actionButton("save", "Save", icon("save")),
        actionButton("clear", "Clear", icon("stop"))
      )
    )
  )
)

server <- function(input, output, session) {
  
  dat <- data.frame(
    Number = as.character(rep(1:3, each = 3)),
    Letter = letters[1:9]
  )
  
  observeEvent(input$Select1, {
    updateSelectizeInput(session, inputId = "Select2", 
                         choices = dat$Letter[dat$Number %in% input$Select1],
                         # Add server = TRUE make the local storage not working
                         server = TRUE)
  }, ignoreInit = TRUE)
  
  observe({
    if (input$save <= 0){
      updateSelectizeInput(session, inputId = "Select1", selected = isolate(input$store)$Select1)
    }
  })
  
  observe({
    if (input$save <= 0){
      req(input$Select1)
      updateSelectizeInput(session, inputId = "Select2", selected = isolate(input$store)$Select2)
    }
  })
  
  observe({
    if (input$save > 0){
      updateStore(session, name = "Select1", isolate(input$Select1))
      updateStore(session, name = "Select2", isolate(input$Select2))
    }
  })

  observe({
    if (input$clear > 0){
      updateSelectizeInput(session, inputId = "Select1",
                           options = list(
                             placeholder = 'Please select a number',
                             onInitialize = I('function() { this.setValue(""); }'),
                             create = TRUE
                           ))
      updateSelectizeInput(session, inputId = "Select2",
                           choices = character(0),
                           options = list(
                             placeholder = 'Please select a number in the sidebar first',
                             onInitialize = I('function() { this.setValue(""); }'),
                             create = TRUE
                           ))

      updateStore(session, name = "Select1", NULL)
      updateStore(session, name = "Select2", NULL)
    }
  })
}

shinyApp(ui, server)

简单的解决方案

最简单的解决方案之一可以是:

只是改变你的observeEvent(input$Select1, ...对此的观察者

    once_flag <- reactiveVal(TRUE)
    observeEvent(input$Select1, {
        updateSelectizeInput(
            session, inputId = "Select2", server = TRUE,
            choices = dat$Letter[dat$Number %in% input$Select1], 
            selected = if(once_flag()) input$store$Select2 else NULL
        )
        once_flag(FALSE)
    }, ignoreInit = TRUE)

你就完成了。多么简单啊!一切问题都以同样的方式解决observeEvent,只需一次更新调用。这once_flag是为了确保设置Select2仅值一次。第二次以及当你改变时Select1,我们不设置值Select2.

感谢@ismirsehregal等其他用户的更正,所以我可以想出上面的简单解决方案。自从shinyStore直接为您提供了这个包装器 APIinput$store,你不需要像我下​​面那样编写API,但是工作流程和后面的内容是相同的。如果您对该解决方案的工作原理感兴趣,请继续阅读。

原答案

cause

我们要解决的最大问题是server = TRUE,阅读帮助文件我们知道store choices on the server-side, and load the select options dynamically on searching。这意味着您的客户端(UI)一开始并不知道有哪些选项。 HTML5 localstore(shinystore背后的东西)是一种客户端技术,它只能改变一开始就存在的东西。如果启动应用程序时未给出选项,则无法更改它。这就是它失败的原因。

详细解决方案

If the select2更新后/基于select1,我们可以从中检索值吗shinystore我们安顿下来之后select1然后将值赋给select2?

答案是否定的,也是肯定的。 不,因为原来的shinystore没有为您提供任何用于 R-Javascript 通信的 API 来检索值。它只允许set, not get (不是真的,请参阅评论,但下面有助于理解闪亮商店的工作原理)。是的,是因为如果你了解html5 localstorage如何以及Shiny的JS-R如何通信,我们就可以编写自己的API来获取值。

这是工作流程:

  1. 应用程序启动,闪亮的商店更新select1
  2. 服务器检测到select1更新了,更新了select2的选项
  3. 告诉客户得到的值select2并从 JS 发送到 R。(在shinyStore你可以通过以下方式访问它input$store$xxx(输入ID)。下面,我手动编写代码,向您展示它们如何将客户端值返回到input value.)
  4. 获取R中的值并更新select2的选择

让我们看看它在代码中是如何工作的:

1-2, R

    observeEvent(input$Select1, {
        # detect 1 changed 
        # send signal to client to get stored 2's value
        session$sendCustomMessage(
            "shinyStore_getvalue",
            list(
                namespace = "shinyStore-ex1",
                key = "Select2"
            )
        )
        # updated 2's choices based on 1
        updateSelectizeInput(session, inputId = "Select2",
                             choices = dat$Letter[dat$Number %in% input$Select1],
                             server = TRUE)
        
    }, ignoreInit = TRUE)

R-JS 如何通信,阅读此页 https://shiny.rstudio.com/articles/communicating-with-js.html.

3, JS

        Shiny.addCustomMessageHandler('shinyStore_getvalue', function(data) {
            var val = localStorage.getItem(`${data.namespace}\\${data.key}`);
            if(val === null) return false;
            val = JSON.parse(val);
            if(val.data === undefined) return false;
            Shiny.setInputValue(`shinystore_${data.key}`, val.data);
        });

获取查询到的shinystorevalue并将其作为输入值发送给Rshiny,可以直接observe。这里不再解释详细信息,如果您想了解更多信息,请再次阅读上面的链接。

4, R

    observeEvent(input$shinystore_Select2, {
        updateSelectizeInput(
            session, 
            inputId = "Select2", 
            choices = dat$Letter[dat$Number %in% input$Select1],
            server = TRUE,
            selected = input$shinystore_Select2
        )
    }, once = TRUE)

Add once仅设置该值一次。

详细解决方案的完整代码

library(shiny)
library(shinyStore)
ui <- fluidPage(
    headerPanel("shinyStore Example"),
    tags$script(HTML(
        '
        Shiny.addCustomMessageHandler(\'shinyStore_getvalue\', function(data) {
            var val = localStorage.getItem(`${data.namespace}\\\\${data.key}`);
            if(val === null) return false;
            val = JSON.parse(val);
            if(val.data === undefined) return false;
            Shiny.setInputValue(`shinystore_${data.key}`, val.data);
        });
        '
    )),
    sidebarLayout(
        sidebarPanel = sidebarPanel(
            initStore("store", "shinyStore-ex1"),
            selectizeInput(inputId = "Select1", label = "Select A Number",
                           choices = as.character(1:3),
                           options = list(
                               placeholder = 'Please select a number',
                               onInitialize = I('function() { this.setValue(""); }'),
                               create = TRUE
                           ))
        ),
        mainPanel = mainPanel(
            fluidRow(
                selectizeInput(inputId = "Select2", 
                               label = "Select A Letter",
                               choices = character(0),
                               options = list(
                                   placeholder = 'Please select a number in the sidebar first',
                                   onInitialize = I('function() { this.setValue(""); }'),
                                   create = TRUE
                               )),
                actionButton("save", "Save", icon("save")),
                actionButton("clear", "Clear", icon("stop"))
            )
        )
    )
)

server <- function(input, output, session) {
    
    dat <- data.frame(
        Number = as.character(rep(1:3, each = 3)),
        Letter = letters[1:9]
    )
    
    observeEvent(input$Select1, {
        # detect 1 changed 
        # send signal to client to get stored 2's value
        session$sendCustomMessage(
            "shinyStore_getvalue",
            list(
                namespace = "shinyStore-ex1",
                key = "Select2"
            )
        )
        # # updated 2's choices based on 1
        updateSelectizeInput(session, inputId = "Select2",
                             choices = dat$Letter[dat$Number %in% input$Select1],
                             server = TRUE)
        
    }, ignoreInit = TRUE)
    
    observeEvent(input$shinystore_Select2, {
        updateSelectizeInput(
            session, 
            inputId = "Select2", 
            choices = dat$Letter[dat$Number %in% input$Select1],
            server = TRUE,
            selected = input$shinystore_Select2
        )
    }, once = TRUE)
    
    observe({
        if (input$save <= 0){
            updateSelectizeInput(session, inputId = "Select1", selected = isolate(input$store)$Select1)
        }
    })
    
    observe({
        if (input$save <= 0){
            req(input$Select1)
            updateSelectizeInput(session, inputId = "Select2", selected = isolate(input$store)$Select2)
        }
    })
    
    observe({
        if (input$save > 0){
            updateStore(session, name = "Select1", isolate(input$Select1))
            updateStore(session, name = "Select2", isolate(input$Select2))
        }
    })
    
    observe({
        if (input$clear > 0){
            updateSelectizeInput(session, inputId = "Select1",
                                 options = list(
                                     placeholder = 'Please select a number',
                                     onInitialize = I('function() { this.setValue(""); }'),
                                     create = TRUE
                                 ))
            updateSelectizeInput(session, inputId = "Select2",
                                 choices = character(0),
                                 options = list(
                                     placeholder = 'Please select a number in the sidebar first',
                                     onInitialize = I('function() { this.setValue(""); }'),
                                     create = TRUE
                                 ))
            updateStore(session, name = "Select1", NULL)
            updateStore(session, name = "Select2", NULL)
        }
    })
}

shinyApp(ui, server)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如果选择依赖于另一个输入并且 server = TRUE,shinyStore 无法恢复 selectizeInput 的选定值 的相关文章

随机推荐