Creación dinámica de tabs con gráficos en shiny sin volver a crear tabs existentes

Me gustaría crear tabs dinámicas, donde cada vez que el usuario haga clic en un botón, se creará una nueva pestaña. Cada pestaña tiene el mismo contenido, con una variedad de widgets que el usuario puede usar para seleccionar qué conjuntos de datos se trazarán.

Actualmente, estoy usando la solución aquí para crear dinámicamente mis tabs, pero con el cambio que lapply está llamando a una función que llama a tabPanel y agrega contenido a las tabs

`

renderUI({ some_data <- # Dataframe that data is extracted goes here createTabs <- function(tabNum, some_data) { tabPanel(title = paste("Map", tabNum, sep=" "), fluidRow( column( width = 3, wellPanel( #widgets are added here } mTabs <- lapply(0:input$map, createTabs, some_data) do.call(tabsetPanel, mTabs) }) 

`

Y los métodos de bucles for publicados aquí para crear las gráficas en cada pestaña.

Sin embargo, parece que en lugar de crear una nueva pestaña, las dos soluciones anteriores vuelven a crear todas las tabs existentes. Entonces, si actualmente hay 10 tabs abiertas, las 10 tabs se vuelven a crear. Desafortunadamente, esto también restablece todas las configuraciones de usuario en cada pestaña (además de ralentizar la aplicación), y se deben tomar medidas adicionales como se muestra aquí , lo que ralentiza aún más la aplicación debido a la gran cantidad de objetos de entrada que se deben crear. .

Vi una solución para los elementos del menú que parece resolver este problema simplemente almacenando todos los elementos del menú en una lista, y cada vez que se genera un nuevo elemento del menú, simplemente se agrega a la lista para que todos los otros elementos existentes no se pongan en venta. Necesito ser creado. ¿Es posible algo como esto para tabs y renderizaciones?

Este es el código:

  newTabs <- renderMenu({ menu_list <- list( menu_vals$menu_list) sidebarMenu(.list = menu_list) }) menu_vals = reactiveValues(menu_list = NULL) observeEvent(eventExpr = input$placeholder, handlerExpr = { menu_vals$menu_list[[input$placeholder]] <- menuSubItem(paste("Saved Simulation", length(menu_vals$menu_list) + 1, sep = " "), tabName = paste("saved_sim", length(menu_vals$menu_list) + 1)) }) 

Si alguien puede explicarme qué hace la lista_menú <- list (menu_vals $ menu_list), por qué Rstudio dice que debe estar dentro de una expresión reactiva, y por qué se crea una nueva lista llamada menu_vals con menu_list = null, sería muy apreciada como bien 🙂

Editar: creo que pude evitar que las ttwigs se vuelvan a crear cada vez que se crea una pestaña nueva y también omitir la necesidad de un número máximo de ttwigs usando

 observeEvent(eventExpr = input$map, handlerExpr = { output[[paste0("outputComparePlot",simNum,"-",input$map)]] <- outputComparePlot(sessionEnv, config, react, input, simNum, input$map) #This function contains the call to renderPlot }) 

Sin embargo, todavía no puedo entender cómo usar esto para crear tabs. Probé el mismo método, pero no funcionó.

Me gustaría presentar una solución que agrega una característica shiny que debería haberse implementado en la base shiny hace mucho tiempo. Una función para agregar tabPanels a tabulados existentes . Ya probé cosas similares aquí y aquí , pero esta vez, siento que esta solución es mucho más estable y versátil.

Para esta función, debe insertar 4 partes de código en su shiny aplicación. Luego puede agregar cualquier conjunto de tabPanels tengan contenido a un tabsetPanel existente llamando a addTabToTabset . Sus argumentos son un tabPanel (o una lista de tabPanels ) y el nombre (id) de su tabsetPanel objective. Incluso funciona para navbarPage , si solo quiere agregar tabPanels normales.

El código que debe copiarse, está dentro de “¡Importante!” comentarios

Mis comentarios probablemente no sean suficientes para captar lo que realmente está sucediendo (y por qué, por supuesto). Entonces, si quiere profundizar más en los detalles, deje un mensaje y lo trataré de explicar.

Copiar-Pegar-Ejecutar-Jugar!

 library(shiny) ui < - shinyUI(fluidPage( # Important! : JavaScript functionality to add the Tabs tags$head(tags$script(HTML(" /* In coherence with the original Shiny way, tab names are created with random numbers. To avoid duplicate IDs, we collect all generated IDs. */ var hrefCollection = []; Shiny.addCustomMessageHandler('addTabToTabset', function(message){ var hrefCodes = []; /* Getting the right tabsetPanel */ var tabsetTarget = document.getElementById(message.tabsetName); /* Iterating through all Panel elements */ for(var i = 0; i < message.titles.length; i++){ /* Creating 6-digit tab ID and check, whether it was already assigned. */ do { hrefCodes[i] = Math.floor(Math.random()*100000); } while(hrefCollection.indexOf(hrefCodes[i]) != -1); hrefCollection = hrefCollection.concat(hrefCodes[i]); /* Creating node in the navigation bar */ var navNode = document.createElement('li'); var linkNode = document.createElement('a'); linkNode.appendChild(document.createTextNode(message.titles[i])); linkNode.setAttribute('data-toggle', 'tab'); linkNode.setAttribute('data-value', message.titles[i]); linkNode.setAttribute('href', '#tab-' + hrefCodes[i]); navNode.appendChild(linkNode); tabsetTarget.appendChild(navNode); }; /* Move the tabs content to where they are normally stored. Using timeout, because it can take some 20-50 millis until the elements are created. */ setTimeout(function(){ var creationPool = document.getElementById('creationPool').childNodes; var tabContainerTarget = document.getElementsByClassName('tab-content')[0]; /* Again iterate through all Panels. */ for(var i = 0; i < creationPool.length; i++){ var tabContent = creationPool[i]; tabContent.setAttribute('id', 'tab-' + hrefCodes[i]); tabContainerTarget.appendChild(tabContent); }; }, 100); }); "))), # End Important tabsetPanel(id = "mainTabset", tabPanel("InitialPanel1", "Some Text here to show this is InitialPanel1", actionButton("goCreate", "Go create a new Tab!"), textOutput("creationInfo") ), tabPanel("InitialPanel2", "Some Text here to show this is InitialPanel2 and not some other Panel") ), # Important! : 'Freshly baked' tabs first enter here. uiOutput("creationPool", style = "display: none;") # End Important )) server <- function(input, output, session){ # Important! : creationPool should be hidden to avoid elements flashing before they are moved. # But hidden elements are ignored by shiny, unless this option below is set. output$creationPool <- renderUI({}) outputOptions(output, "creationPool", suspendWhenHidden = FALSE) # End Important # Important! : This is the make-easy wrapper for adding new tabPanels. addTabToTabset <- function(Panels, tabsetName){ titles <- lapply(Panels, function(Panel){return(Panel$attribs$title)}) Panels <- lapply(Panels, function(Panel){Panel$attribs$title <- NULL; return(Panel)}) output$creationPool <- renderUI({Panels}) session$sendCustomMessage(type = "addTabToTabset", message = list(titles = titles, tabsetName = tabsetName)) } # End Important # From here: Just for demonstration output$creationInfo <- renderText({ paste0("The next tab will be named NewTab", input$goCreate + 1) }) observeEvent(input$goCreate, { nr <- input$goCreate newTabPanels <- list( tabPanel(paste0("NewTab", nr), actionButton(paste0("Button", nr), "Some new button!"), textOutput(paste0("Text", nr)) ), tabPanel(paste0("AlsoNewTab", nr), sliderInput(paste0("Slider", nr), label = NULL, min = 0, max = 1, value = 1)) ) output[[paste0("Text", nr)]] <- renderText({ if(input[[paste0("Button", nr)]] == 0){ "Try pushing this button!" } else { paste("Button number", nr , "works!") } }) addTabToTabset(newTabPanels, "mainTabset") }) } shinyApp(ui, server) 

Esto quizás esté más relacionado con la respuesta de @ k-rohde que con la publicación original. Ahora está disponible un conjunto de métodos para agregar / eliminar / agregar tabs en un conjunto de tabs:

 library(shiny) runApp(list( ui=fluidPage( fluidRow( actionLink("newTab", "Append tab"), actionLink("removeTab", "Remove current tab") ), tabsetPanel(id="myTabs", type="pills") ), server=function(input, output, session){ tabIndex < - reactiveVal(0) observeEvent(input$newTab, { tabIndex(tabIndex() + 1) appendTab("myTabs", tabPanel(tabIndex(), tags$p(paste("I'm tab", tabIndex()))), select=TRUE) }) observeEvent(input$removeTab, { removeTab("myTabs", target=input$myTabs) }) } )) 

Espero que esto ayude a cualquiera que aterrice aquí buscando "cómo agregar / eliminar dinámicamente tabs en un tabset".