[英]shinyStore cannot restore the selected values of the selectizeInput if the choices are depends on another input
[英]shinyStore cannot restore the selected values of the selectizeInput if the choices depend on another input and server = TRUE
這和我之前的問答一樣,只是我為第一個updateSelectizeInput<\/code>設置了server = TRUE<\/code> ,這使得本地存儲無法正常工作。
如果我可以使用server = TRUE<\/code>那就太好了,因為在我的實際示例中,我的selectizeInput<\/code>的選擇很多。
我們不必使用自定義 JavaScript 或添加更多依賴項來解決這個問題 -
input$store<\/code>是
shinyStore's<\/code>內置方法,用於從
localStorage 對象<\/a>檢索數據並為我們提供會話啟動所需的所有信息(而且它已經在@www 在示例代碼中使用)。
shiny 中的
會話對象<\/a>為服務器(除其他外)提供客戶端(或瀏覽器)信息 - 例如
session$clientData$url_search<\/code>或此處感興趣的:
session$input$store<\/code> 。
我們必須確保,當使用
updateSelectizeInput<\/code>時,我們嘗試設置的選項在
choices<\/code>中可用 - 例如:
不會工作。
此外,我們需要使用
freezeReactiveValue<\/code>在會話開始恢復后停止觸發下游的其他觀察者,以避免再次覆蓋更新。
請參閱Mastering Shiny<\/em>中的相關章節<\/a>。freezeReactiveValue<\/code>順便說一句。
在閃亮中使用
update*<\/code>函數時幾乎總是適用的。
### 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$store, {
freezeReactiveValue(input, "Select1") # required
freezeReactiveValue(input, "Select2") # not required but should be used before calling any update function which isn't intended to trigger further reactives
updateSelectizeInput(session, inputId = "Select1", selected = input$store$Select1)
updateSelectizeInput(session, inputId = "Select2", selected = input$store$Select2, choices = dat$Letter[dat$Number %in% input$store$Select1], server = TRUE)
}, once = TRUE, ignoreInit = FALSE)
observeEvent(input$Select1, {
updateSelectizeInput(session, inputId = "Select2",
choices = dat$Letter[dat$Number %in% input$Select1],
server = TRUE)
}, ignoreInit = TRUE)
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)
我們要解決的最大問題是server = TRUE
,閱讀我們知道store choices on the server-side, and load the select options dynamically on searching
。 這意味着您的客戶 (UI) 一開始並不知道有哪些選項。 HTML5 localstore(shinystore 背后的東西)是一種客戶端技術,它只能改變一開始就存在的東西。 如果啟動應用程序時未提供選項,則無法更改。 這就是它失敗的原因。
如果select2
在 select1 之后/基於select1
更新,我們可以在我們解決select1
之后從shinystore
檢索值,然后將值分配給select2
嗎?
答案是否定的。 不,因為原始shinystore
沒有為您提供任何用於 R-Javascript 通信的 API 來檢索值。 它只允許設置,不允許獲取。 是的,因為如果你了解 html5 localstorage 和 Shiny 的 JS-R 是如何通信的,我們可以編寫自己的 API 來獲取值。
這是工作流程:
select1
select1
更新,更新select2
的選項select2
的值並從 JS 發送到 Rselect2
的選定選項讓我們看看它在代碼中是如何工作的:
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 = "bind_1_2"
)
)
# updated 2's choices based on 1
# we don't update the selected choice yet, because we don't know
# at this moment
updateSelectizeInput(session, inputId = "Select2",
choices = dat$Letter[dat$Number %in% input$Select1],
server = TRUE)
}, ignoreInit = TRUE)
R-JS 如何通信, 請閱讀此頁面。 在這里,我得到的不是Select2
而是bind_1_2
的存儲值。 這是一個小小的安全機制。 這意味着我使用shinystore
將select1
和select2
的當前值一起保存為插槽。 稍后我們在更新select2
選定值時,存儲的select1
值必須與當前select1
值匹配。 我們要確保 1 和 2 配對。 例如,1 的值為1
-> 它將 a,b,c 與 2 配對。如果我存儲的 2 值為d
但當前 1 的值為1
,那么我們將 2 更新為d
是不正確的。 因此,我們必須驗證當前 1 的值(不太可能發生,但以防萬一)。
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);
});
獲取查詢到的shinystore
值,並將其作為可以直接observe
的輸入值發送給 R shiny。 此處不再解釋詳細信息,如果您想了解更多信息,請閱讀上面的鏈接。
observeEvent(input$shinystore_bind_1_2, {
val1_2 <- unlist(strsplit(input$shinystore_bind_1_2, "-"))
spsComps::onNextInput({
updateSelectizeInput(
session,
inputId = "Select2",
# validation here, if stored 1's value is not same with current 1's
# value, we don't select anything
selected = if(val1_2[1] == input$Select1) val1_2[2] else NULL
)
})
})
你會問什么是spsComps::onNextInput
。 簡而言之,閃亮的反應式是異步的。 如果我們立即更新 2 的選擇,它不會起作用,因為更新 2 的選擇還沒有完成。 我們必須等到我們更新了 2 的選擇,然后再更新 2 的選擇。 onNextInput
是一個等待另一個輸入結算事件的回調。 閱讀本文了解詳情。 它還不是 CRAN 版本的spsComps
,首先從 github remotes::install_github("lz100/spsComps")
安裝。
這是完整的代碼。 我還為bind_1_2
更改了其他幾行,但核心代碼在上面列出。
### This script creates an example of the shinystore package
# Load packages
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 = "bind_1_2"
)
)
# 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_bind_1_2, {
val1_2 <- unlist(strsplit(input$shinystore_bind_1_2, "-"))
spsComps::onNextInput({
updateSelectizeInput(
session,
inputId = "Select2",
# validation here, if stored 1's value is not same with current 1's
# value, we don't select anything
selected = if(val1_2[1] == input$Select1) val1_2[2] else NULL
)
})
})
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))
updateStore(session, name = "bind_1_2", paste0(isolate(input$Select1), "-", 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)
updateStore(session, name = "bind_1_2", NULL)
}
})
}
shinyApp(ui, server)
原始答案足夠長,我不想在那里添加更多內容。 正如評論中提到的,前兩個答案是使用
input$store<\/code> 。
在這里,我為您提供了一個純<\/strong>Javascript 的解決方案。 是的,不需要服務器代碼版本。 這是代碼,閱讀內聯注釋以了解它是如何工作的。
我們沒有向服務器添加代碼,而是刪除了 2 個冗余觀察者。 這是完整的代碼。
在性能方面,與前 2 個沒有明顯差異,但如前所述,稍后可能對一些重度應用程序有用。 好消息是這段代碼在客戶端運行。 它運行的速度主要取決於您用戶的計算機,這減少了您的服務器負擔,只是一點點,但對於每個用戶來說,一點點可以總結為一個很大的數字。 想象一下,成千上萬的人同時使用該應用程序。 不好的是需要學JS,對初學者不太友好。
因此,這完全取決於您的實際需求。 如果您想要一些快速而簡短的解決方案,請使用我的第一篇文章; 如果您關心性能但不想使用 JS,請使用@ismirsehregal post; 如果你有一個很重的應用程序並且想要有很多用戶,這個 JS 解決方案可能會更好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.