R Shiny - 动态创建书签 UI

R Shiny - bookmarking dynamically created UI

我在使用 Shiny 的书签时遇到了一个小问题(可能是错误?) - 我动态创建 UI - 新的文本和数字输入。这些输入是通过单击 ActionButton 创建的。保存书签时 URL - 数字和文本输入的值都在这个 URL 中。到目前为止,一切都很好。但是,在加载保存的 URL 后,我只看到第一个动态创建的 UI。我必须单击操作按钮才能添加下一个数字和文本输入。这些值保存在 URL 中,一旦添加了这些输入,它们就会填充正确的保存值。但是,如果您有 20 个这样的按钮,并且必须单击 19 次才能将它们全部显示在屏幕上,那就有点不方便了。 这是一个简短的可重现示例。

library(shiny)

ui <- function(request){
  shinyUI(fluidPage(
    bookmarkButton(),
    sidebarLayout(
      actionButton('addElement', 'Add Element', icon("plus"), class="btn-success text-white"),
    mainPanel(
        id ="content"
      )
    )
  ))
}


shinyServer(function(input, output) {
  enableBookmarking("url")
  countvar <<- 0
  observeEvent(input$addElement, {
    countvar <<- countvar + 1
    element <- paste0("var", countvar)
    insertUI(
      selector = "#content",
      where = "beforeEnd",
      ui = tagList(
        wellPanel(
          style = "display:-webkit-inline-box;width:100%",
          id = element,
          column(3,
                 textInput(element, "Element name")
          ),
          column(3,
                 numericInput(paste0(element, "Value"), "Element Value", NULL)
          )
        )
      )
    )
  })

})

我在 SO 上发现了类似的问题,不幸的是没有答案 - Shiny app bookmarking: dynamic UI input lost

这是根据您的应用改编的最小示例。没有神奇的方法可以在添加书签后在页面上获取动态内容(除非您以直接的方式使用 renderUI——即没有任何非输入状态)。因此,对于您的情况,您只需在 onRestore() 回调中再次重复您在应用程序中所做的工作。执行此操作的最佳方法是重构您的代码,以便可以调用一个函数来在普通应用程序和 onRestore() 回调中添加项目。

library(shiny)

ui <- function(request) {
  fluidPage(
    bookmarkButton(),
    sidebarLayout(
      actionButton("add", "Add Element"),
      mainPanel(id = "content")
    )
  )
}

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

  addItem <- function(id, textId, numberId, textVal = "", numberVal = NULL) {
    insertUI(
      selector = "#content",
      where = "beforeEnd",
      ui = tagList(
        wellPanel(style = "display:-webkit-inline-box;width:100%",
          id = id,
          column(3, textInput(textId, "Element name", textVal)),
          column(3, numericInput(numberId, "Element value", numberVal))
        )
      )
    )
  }

  observeEvent(input$add, {
    id <- paste0("var", input$add)
    textId <- paste0(id, "text")
    numberId <- paste0(id, "number")
    addItem(id, textId, numberId)
  }, ignoreInit = TRUE)

  onRestore(function(state) {
    for (i in seq_len(input$add))  {
      id <- paste0("var", i)
      textId <- paste0(id, "text")
      numberId <- paste0(id, "number")
      addItem(id, textId, numberId, input[[textId]], input[[numberId]])
    }
  })
}

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

这是对 Barbara 的答案进行扩展的新答案。看来我们需要为 onRestore 函数引用状态变量才能正确处理用户的输入。所以我更改了她的代码以包含一个似乎有效的 state$input$var。希望对你有帮助。

library(shiny)

ui <- function(request) {
    fluidPage(
        bookmarkButton(),
        sidebarLayout(
            actionButton("add", "Add Element"),
            mainPanel(id = "content")
        )
    )
}

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

    addItem <- function(id, textId, numberId, textVal = "", numberVal = NULL) {
        insertUI(
            selector = "#content",
            where = "beforeEnd",
            ui = tagList(
                wellPanel(style = "display:-webkit-inline-box;width:100%",
                          id = id,
                          column(3, textInput(textId, "Element name", textVal)),
                          column(3, numericInput(numberId, "Element value", numberVal))
                )
            )
        )
    }

    observeEvent(input$add, {
        id <- paste0("var", input$add)
        textId <- paste0(id, "text")
        numberId <- paste0(id, "number")
        addItem(id, textId, numberId)
    }, ignoreInit = TRUE)

    onRestore(function(state) {
        for (i in seq_len(input$add))  {
            id <- paste0("var", i)
            textId <- paste0(id, "text")
            numberId <- paste0(id, "number")
            addItem(id, textId, numberId, state$input[[textId]], state$input[[numberId]])
        }
    })

}

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