我想这篇文章还是有一定意义的。所以这是我关于如何将几乎所有可能的界面输出添加到传单弹出窗口的解决方案。
我们可以通过以下步骤来实现:
在传单标准弹出字段中插入弹出 UI 元素作为字符。作为字符的意思,它不是shiny.tag,而只是一个普通的div。例如。经典的uiOutput("myID") 变成了<div id="myID" class="shiny-html-output"><div>。
弹出窗口被插入到一个特殊的div,leaflet-popup-pane。我们添加一个 EventListener 来监控其内容是否发生变化。 (注意:如果弹出窗口消失,则表示此div 的所有子级都已删除,因此这不是可见性问题,而是存在性问题。)
当一个孩子被附加时,即出现一个弹出窗口,我们将所有闪亮的输入/输出绑定到弹出窗口中。因此,毫无生气的uiOutput 充满了应有的内容。 (人们希望 Shiny 自动执行此操作,但它无法注册此输出,因为它是由 Leaflets 后端填充的。)
当弹出窗口被删除时,Shiny 也无法取消绑定它。如果您再次打开弹出窗口并引发异常(重复 ID),那就有问题了。一旦从文档中删除,就不能再解除绑定。所以我们基本上将删除的元素克隆到一个可以正确解绑的disposal-div,然后将其永久删除。
我创建了一个示例应用程序,(我认为)展示了此解决方法的全部功能,我希望它的设计足够简单,任何人都可以适应它。这个应用程序大部分是为了展示,所以请原谅它有不相关的部分。
library(leaflet)
library(shiny)
runApp(
shinyApp(
ui = shinyUI(
fluidPage(
# Copy this part here for the Script and disposal-div
uiOutput("script"),
tags$div(id = "garbage"),
# End of copy.
leafletOutput("map"),
verbatimTextOutput("Showcase")
)
),
server = function(input, output, session){
# Just for Show
text <- NULL
makeReactiveBinding("text")
output$Showcase <- renderText({text})
output$popup1 <- renderUI({
actionButton("Go1", "Go1")
})
observeEvent(input$Go1, {
text <<- paste0(text, "\n", "Button 1 is fully reactive.")
})
output$popup2 <- renderUI({
actionButton("Go2", "Go2")
})
observeEvent(input$Go2, {
text <<- paste0(text, "\n", "Button 2 is fully reactive.")
})
output$popup3 <- renderUI({
actionButton("Go3", "Go3")
})
observeEvent(input$Go3, {
text <<- paste0(text, "\n", "Button 3 is fully reactive.")
})
# End: Just for show
# Copy this part.
output$script <- renderUI({
tags$script(HTML('
var target = document.querySelector(".leaflet-popup-pane");
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.addedNodes.length > 0){
Shiny.bindAll(".leaflet-popup-content");
};
if(mutation.removedNodes.length > 0){
var popupNode = mutation.removedNodes[0].childNodes[1].childNodes[0].childNodes[0];
var garbageCan = document.getElementById("garbage");
garbageCan.appendChild(popupNode);
Shiny.unbindAll("#garbage");
garbageCan.innerHTML = "";
};
});
});
var config = {childList: true};
observer.observe(target, config);
'))
})
# End Copy
# Function is just to lighten code. But here you can see how to insert the popup.
popupMaker <- function(id){
as.character(uiOutput(id))
}
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addMarkers(lat = c(10, 20, 30), lng = c(10, 20, 30), popup = lapply(paste0("popup", 1:3), popupMaker))
})
}
), launch.browser = TRUE
)
注意:可能有人会想,为什么要从服务器端添加脚本。我遇到了,否则,添加 EventListener 会失败,因为 Leaflet 映射尚未初始化。我打赌有一些 jQuery 知识没有必要做这个技巧。
解决这个问题是一项艰巨的工作,但我认为值得花时间,因为 Leaflet 地图有了一些额外的实用性。玩得开心,如果有任何问题,请询问!