Как добавить Shiny selectizeInput в закладки с помощью динамических параметров?

Блестящий обеспечивает selectizeInput это обертывания selectize.js, который создает виджет ввода текста / поля со списком. Есть несколько причин, по которым я бы хотел отложить загрузку параметров / значений для selectizeInput: может быть длинный список значений или возможные значения зависят от других параметров. Кроме того, я бы хотел, чтобы пользователь мог создавать новые значения (например, выбирать существующее значение или создавать свои собственные).

Но во всех этих случаях значение selectizeInput не может быть добавлено в закладки с помощью серверной закладки Shiny. Когда объект selectizeInput инициализируется, значение закладки, сохраненное в input еще не являются действительными значениями параметров.

У кого-нибудь есть какие-нибудь умные обходные пути для закладки значений selectizeInput?

Вот самый простой пример, касающийся только create опция:

library(shiny)

ui <- function(request) {
  fluidPage(

    selectizeInput("foo", "Created Values Cannot Be Bookmarked", choices=c("Choose From List or Type New Value"="", "bar", "baz"),
                   options=list(create=TRUE)),
    bookmarkButton("Update URL")

  )}

server <- function(input, output) {
  onBookmarked(function(url) {
    updateQueryString(url)
  })
}

shinyApp(ui = ui, server = server, enableBookmarking = "server")

Если вы введете новое значение в поле выбора, нажмите кнопку закладки, чтобы обновить URL-адрес в строке адреса в браузере, и нажмите "Перезагрузить", то созданное значение будет потеряно. Попробуйте то же самое с существующей опцией, и она работает, конечно.

Это усложняется, если загрузка параметров задерживается, но проблема та же, что выбор пользователя недействителен, когда selectizeInput рендеринг.

2 ответа

Решение

Вот лучшее решение, использующее серверную поддержку для выбора. Важным шагом для поддержки параметров create=TRUE является использование onRestored обновить варианты с новыми значениями, которые были созданы пользователем.

library(shiny)

init.choices <- c("Choose From List or Type New Value"="")

ui <- function(request) {
  fluidPage(
    titlePanel("Bookmarking Created Values"),
    selectizeInput("foo", NULL, 
                   choices=init.choices,
                   multiple=TRUE,
                   options=list(create=TRUE)),
    bookmarkButton("Update URL")

  )}

server <- function(input, output, session) {
  updateSelectizeInput(session, "foo", choices=c(init.choices, rownames(mtcars)), server=TRUE)
  onBookmarked(function(url) {
    updateQueryString(url)
  })
  onRestored(function(state) {
    updateSelectizeInput(session, "foo", selected=state$input$foo, choices=c(init.choices, rownames(mtcars), state$input$foo), server=TRUE)
  })
}


shinyApp(ui = ui, server = server, enableBookmarking = "server")

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

Двойное значение selectizeInput хранится в скрытом текстовом вводе. Когда загруженные в закладки значения загружены, скрытое резервное значение используется для восстановления реального. Чтобы установить значение, которого нет среди текущих опций, selectize.js API вызывается используя addOption а потом addItem, Обратите внимание, что ignoreInit=TRUE на входе $foo наблюдателя, чтобы скрытая резервная копия не перезаписывалась при загрузке. С другой стороны, once=TRUE устанавливается на входе $foo.bak обозревателя, так что скрытая резервная копия используется только для обновления реального значения при запуске. Смотрите документацию для observeEvent,

Выбор для selectizeInput загружаются асинхронно только тогда, когда фокус на входе для ускорения начальной загрузки страницы. Файл JSON с возможностью выбора хранится локально в каталоге www/.

Этот пример также позволяет множественный выбор. Выбранные элементы сохраняются через запятую в скрытом текстовом поле, а затем раскрываются во время инициализации.

library(shiny)
library(shinyjs)

# create JSON file with selectizeInput choices to be retrieved via ajax during load
# normally this would be run once outside of app.R
dir.create("www")
foo.choices.json <- paste('{ "cars": [\n', paste0('{ "value": "', rownames(mtcars), '", "label": "',rownames(mtcars), '" }', collapse=",\n" ), "\n]}")
writeChar(foo.choices.json, "www/foo.choices.json", eos=NULL)

# javascript to set one or more values
jsCode <- "shinyjs.setfoo = function(params){ 
  x=$('#foo')[0].selectize; 
  $.each(params.items, function(i,v) { x.addOption({value:v,label:v}); x.addItem(v) })
}"

# javascript to retrieve the choices asynchronously after focus
foo.load <- "function(query, callback) {
  if (query.length) return callback(); // not dynamic by query, just delayed load
  $.ajax({
    url: 'foo.choices.json',
    type: 'GET',
    error: function() { callback() },
    success: function(res) { callback(res.cars) }
  })
}"

ui <- function(request) {
  fluidPage(
    useShinyjs(), 
    extendShinyjs(text=jsCode),
    titlePanel("Bookmarking Created Values"),
    selectizeInput("foo", NULL, 
                   choices=c("Choose From List or Type New Value"=""),
                   multiple=TRUE,
                   options=list(create=TRUE, preload="focus", load=I(foo.load))),
    bookmarkButton("Update URL"),
    div(style="display:none", textInput("foo.bak", NULL))

  )}

server <- function(input, output, session) {
  onBookmarked(function(url) {
    updateQueryString(url)
  })
  observeEvent(input$foo, { 
    if (is.null(input$foo)) {
      updateTextInput(session, "foo.bak", value="")
    } else {
      updateTextInput(session, "foo.bak", value=input$foo)
    }
  }, ignoreInit=TRUE, ignoreNULL = FALSE)
  observeEvent(input$foo.bak, {
    js$setfoo(items=as.list(strsplit(input$foo.bak,",")[[1]]))
  }, once=TRUE)
}


shinyApp(ui = ui, server = server, enableBookmarking = "server")
Другие вопросы по тегам