Shiny: insertTab with modules - module

I am trying to insert tabs dynamically calling the insertTab() function within a module. For some reason my approach does not work. I guess the problem is how I pass the tabsetPanel id and the value of an existing tabPanel (next to which a tab should be added) to the module.
actionButUI = function(id, label=NULL) {
ns = NS(id)
tagList(
actionButton(ns("button"), label = label)
)
}
actionBut = function(input, output, session, tabsetPanel_id, target) {
observeEvent(input$button, {
insertTab(
inputId = tabsetPanel_id(),
tabPanel(
"Dynamic", "This a dynamically-added tab"
),
target = target
)
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButUI("append_tab", "Insert Tab")
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Hello", "This is the hello tab"),
tabPanel("Bar", "This is the bar tab")
)
)
)
)
server <- function(input, output, session) {
callModule(actionBut, "append_tab", reactive({input$tabs}), "Bar")
}
shinyApp(ui, server)

There seems to be an issue with namespaces. The followig modification fixes the issue
tabsetPanel(id = "append_tab-tabs",
tabPanel("Hello", "This is the hello tab"),
tabPanel("Bar", "This is the bar tab"))
The insertTab function tries to add a ui element in the module namespace rather than the global one. If you look at the source code of insertTab you'll see the line
inputId <- session$ns(inputId)
which causes this behavior.
Another way is to pass the session variable from the main app to insetTab rather than the module's session.
actionBut = function(input, output, session, tabsetPanel_id = "tabs", target) {
## do some environment hacking: Get the `session` variabe from the
## environment that invoked `callModule`.
parentSession <- get("session", envir = parent.frame(2))
observeEvent(input$button, {
insertTab(
inputId = tabsetPanel_id,
tabPanel(
"Dynamic", "This a dynamically-added tab"
),
target = target,
session = parentSession
)
})
}
This approach can get quite messy however if you work with nested modules.

An alternative to the InsertTab function, you can follow Ramnath solution here.
I have made it into modules.
library(shiny)
#---- Module Add dynamic tab ---
SidebarUi <- function(id) {
ns <- NS(id)
uiOutput(ns("sidebar"))
}
MainpanelUi <- function(id) {
ns <- NS(id)
uiOutput(ns("mainpanel"))
}
DynamicTabserver <- function(input, output, session) {
ns <- session$ns
output$sidebar <- renderUI({
actionButton(ns("nTabs"), label = "Add tab")
})
output$mainpanel <- renderUI({
uiOutput(ns('mytabs'))
})
output$mytabs <- renderUI({
nTabs = input$nTabs
myTabs = lapply(paste('Tab', 0:nTabs), tabPanel)
do.call(tabsetPanel, myTabs)
})
}
#---- App.R ---
ui = pageWithSidebar(headerPanel('Dynamic Tabs'),
sidebarPanel(SidebarUi("tabdemo")),
mainPanel(MainpanelUi("tabdemo")))
server = function(input, output, session) {
callModule(DynamicTabserver, "tabdemo")
}
shinyApp(ui, server)

Related

RShiny Limit for Dropdown [duplicate]

I have written a simple example of what I am doing. I have 3000 numbers that I want to show in a selectInput. The numbers have to be in a reactive function, since in my original work, the data is from a file.
My problem is that when I run the app it only appears 1000 numbers, not the entire data (3000 numbers).
I have seen this post Updating selection of server-side selectize input with >1000 choices fails but I don't know how can I do it using uiOutput and renderUI.
Can anyone help me?
Thanks very much in advance
The code:
library(shiny)
ui <- fluidPage(
titlePanel("Numbers"),
sidebarLayout(
sidebarPanel(
uiOutput('selectUI')
),
mainPanel(
)
)
)
server <- function(input, output) {
num <- reactive({
data = c(1:3000)
return(data)
})
output$selectUI <- renderUI({
selectInput(inputId = 'options', "Select one", choices = num())
})
}
# Run the application
shinyApp(ui = ui, server = server)
Use selectizeInput instead of selectInput with the argument options = list(maxOptions = 3000).
Thanks to Stéphane Laurent's answer, the example will be solved like this:
library(shiny)
ui <- fluidPage(
titlePanel("Numbers"),
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "options", label = "Select one", choices=character(0)),
),
mainPanel(
)
)
)
server <- function(input, output, session) {
num <- reactive({
data = c(1:3000)
return(data)
})
observe({
updateSelectizeInput(
session = session,
inputId = "options",
label = "Select one",
choices= num(), options=list(maxOptions = length(num())),
server = TRUE)
})
}
# Run the application
shinyApp(ui = ui, server = server)
This code will work if you have more than 3000 entries. It will show you ALL the choices that you have. However, if you have a long list of choices (e.g. 60000) it will decrease the speed of your app.

Setting overwrite == TRUE using memdb and dbplyr

The following shiny app works the first time you run it, but then errors if you change the species input because the table name already exists in memory. I was wondering how to set overwrite == TRUE given the code below?
library(shiny)
library(tidyverse)
library(dbplyr)
ui <- fluidPage(
selectInput("species", "Species", choices = unique(iris$Species),
selected = "setosa"),
tableOutput("SQL_table"),
actionButton("code", "View SQL"),
)
server <- function(input, output) {
# render table
output$SQL_table <- renderTable(
head(iris %>% filter(Species == input[["species"]]))
)
# generate query
SQLquery <- reactive({
sql_render(
show_query(
tbl_memdb(iris) %>%
filter(Species == local(input$species))
)
)
})
# see query
observeEvent( input$code, {
showModal(
modalDialog(
SQLquery()
)
)
})
}
shinyApp(ui = ui, server = server)
since memdb_frame is just a function call of copy_to we can use it directly to set overwrite = TRUE
copy_to(src_memdb(), iris, name = 'iris', overwrite=TRUE)

How to return a dataframe as a datatable item on a shiny app

in the following code I managed to return the result of a data summary as a printed text, however I want to return it in a datatable and I never get the wanted result can anyone be of my help?
ui <- fluidPage(
fileInput('file1', 'Upload your CSV File'),
htmlOutput("variables"),
numericInput(inputId="n", label="n", value=4, min=3,step=1),
htmlOutput("facteurs"),
verbatimTextOutput("res"),
DT::dataTableOutput("Tab")
)
server <- function(input, output) {
myData1 <- reactive({
inFile <- input$file1
if (is.null(inFile)) return(NULL)
data <- read.csv(inFile$datapath, header = TRUE,row.names=1)
data
})
output$variables <- renderUI({
req(myData1())
df_init <- myData1()
for(i in c(1:ncol(df_init))){
if((class(df_init[,i])=="integer") && length(unique(df_init[,i]))<=input$n){df_init[,i]<-factor(df_init[,i])}}
x=sapply(df_init,class)
x=(x=="numeric")
df=df_init[,x]
if (identical(df, '') || identical(df,data.frame())) return(NULL)
selectInput(inputId = "V1", label = "Variables to use: Y", choices=names(df), selected=names(df[1]))
})
output$facteurs <- renderUI({
req(myData1())
df_init <- myData1()
for(i in c(1:ncol(df_init))){
if((class(df_init[,i])=="integer") && length(unique(df_init[,i]))<=input$n){df_init[,i]<-factor(df_init[,i])}}
x=sapply(df_init,class)
x=(x=="factor")
df=df_init[,x]
if (identical(df, '') || identical(df,data.frame())) return(NULL)
selectInput(inputId = "F1", label = "Factors to use: X", choices=names(df), selected=names(df))
})
output$res<-renderPrint({
data<-myData1()
if (is.null(data)) return("Enter your data!")
v=c(input$V1,input$F1)
x=data[,v]
print(tapply(x[,1],x[,2],summary))
})
output$Tab<-renderDataTable({
data<-myData1()
if (is.null(data)) return("Enter your data!")
v=c(input$V1,input$F1)
x=data[,v]
DT::datatable(data=tapply(x[,1],x[,2],summary))
})
}
shinyApp(ui = ui, server = server)
I also add to this reproducible code a data sample so you can verify its functionalities
,Var,Lo,ES,Acidity,K232,K270,IP,OS,C 14:0,C 16:0,C 16:1,C 17:0,C 17:1,C 18:0,C 18:1,C 18:2,C 18:3,C 20:0,C 20:1,total,LLL,LnLO,LnLP,LLO,LnOO,PLL,LOO,LOP,PLP,OOO,POP,POO,AOL,SOO,SOP,Chlorophyll,b carotène,polyphenols ,Ethyl acetate,2- Methyl butanal,3- Methyl butanal,1-Penten-3-one,3-Hexanone,Hexanal,3-Pentanol,Trans-2-pentenal,1-Penten-3-ol,Cis-3-hexenal,Trans-2-hexenal,1-Pentanol,Hexyl acetate,Cis-3-hexenyl acetate,Cis-2-pentenol,6-Methyl-5-hepten-2-one,1-Hexanol,Trans-3-hexenol,Cis-3-hexenol,Trans-2-hexenol,Acetic acid,Butyric acid,H- Tyr ,Tyr ,DFOA,DFLA,Ac-Pin,Pin,EAA,OA,LA,total phenols (HPLC)
P1,chetoui,beja,sp,0.93,1.49,0.2,9.2,16.51,0.01,13.5,0.57,0.07,0.08,2.57,67.04,18.56,1.16,0.02,0.39,103.95,0.72,0.45,0.1,7.87,2.63,0.38,22.14,8.8,0.7,33.47,15.84,1.77,0.37,3.95,0.8,5.05,4.1,491.6,2.72,0.29,0.11,0.08,0.1,15.27,1.35,1.55,3.77,22.68,133.13,0.36,9.92,7.14,2.37,0.5,10.03,0.78,121.11,14.12,0.05,0.08,1.91,4.15,10.33,40.52,1.21,5.5,2.92,30.65,2.35,99.53
P2,chetoui,beja,sp,0.36,1.24,0.2,8.2,16.81,0.01,13.39,0.18,0.05,0.07,1.23,69.23,18.63,0.91,0.02,0.28,104,0.69,0.43,0.15,7.81,2.57,0.42,22.21,8.84,0.71,33.87,15.6,2.01,0.38,4.14,1.01,5.88,6.161,457.04,2.52,0.34,0.12,0.09,0.22,15.2,1.32,1.52,3.67,22.61,133.19,0.35,9.89,7.18,2.34,0.51,10.17,0.75,121.21,14.29,0.05,0.02,1.92,4.05,10.45,40.63,1.25,5.55,2.95,31.042,2.17,100.01
P3,chetoui,beja,sp,0.84,1.87,0.21,8.6,16.73,0.01,13.31,0.45,0.06,0.08,2.54,69.29,17.03,0.84,0.02,0.37,104,0.72,0.42,0.12,7.82,2.61,0.43,21.22,8.83,0.72,33.85,15.52,1.92,0.39,4.05,0.95,5.92,6.241,482.12,2.72,0.25,0.08,1.12,0.42,15.01,1.3,1.44,3.93,22.51,133.07,0.39,9.87,7.16,2.31,0.52,10.1,0.76,121.29,14.21,0.06,0.01,1.93,4.12,10.6,40.71,1.26,5.54,2.96,30.43,2.26,99.81
You were not very clear on what you need your output to look like, so here's one way to do it:
output$Tab<-renderDataTable({
data<-myData1()
if (is.null(data)) return("Enter your data!")
v=c(input$V1,input$F1)
x=data[,v]
do.call(cbind, tapply(x[,1],x[,2], summary))
})
or if you'd like it transposed change the cbind to rbind

Usage of ellipsis within a shiny module

I wonder, if it is possible to use ellipsis (...) in a shiny server module. I think the problem is that i cannot call the reactive value (as it is usual done with parentheses - value() ) within the server module.
Trying to make the ellipsis reactive ...() did also not work out. Anyone an idea how to solve this issue?
Thanks in advance!
renderPlotsUI = function(id) {
ns = NS(id)
tagList(plotOutput(ns("plot")))
}
renderPlots = function(input, output, session, FUN, ...) {
output$plot = renderPlot({FUN(...)})
}
# APP BEGINS
ui = fluidPage(
renderPlotsUI("plot1")
)
server = function(input, output, session) {
callModule(renderPlots, "plot1", FUN=plot, x = reactive(mtcars))
}
shinyApp(ui, server)
You can convert the ellipsis to a list with list and then use lapply and do.call to call your function. I slightly changed your example to showcase how to pass inputs from the ui to the function.
library(shiny)
renderPlotsUI = function(id) {
ns = NS(id)
tagList(plotOutput(ns("plot")))
}
renderPlots = function(input, output, session, FUN, ...) {
output$plot = renderPlot({
args_evaluated <- lapply(list(...), function(x){x()})
do.call(FUN, args_evaluated)
})
}
shinyApp(
fluidPage(
sliderInput("n", "n", 1, 10, 5),
renderPlotsUI("plot1")
) ,
function(input, output, session) {
callModule(renderPlots, "plot1", FUN = plot, x = reactive({1:input$n}))
}
)

How to add/remove input fields dynamically by a button in shiny

I've been trying to find a solution how to add and remove input fields with a button in shiny. I don't have a source code since I haven't made that much progress, but this jQuery example (http://www.mkyong.com/jquery/how-to-add-remove-textbox-dynamically-with-jquery/) gives a good idea on what I'm trying to accomplish. Is this possible in shiny or should I use shinyjs to do this? Thank you in advance!
EDIT: I read the jQuery example a bit more, and added a code snippet doing what I think you were looking for.
I don't know jQuery, so I couldn't make much out of the example link. I took a guess on what you wanted, but I think the key idea is the use of renderUI and uiOutput even if my suggestion here misses the point.
To toggle a ui element:
If you specifically don't want to use shinyjs, you could do something like this:
library(shiny)
ui <- shinyUI(fluidPage(
actionButton("btn", "Toggle Textbox"),
textOutput("btn_val"),
uiOutput("textbox_ui")
))
server <- shinyServer(function(input, output, session) {
output$btn_val <- renderPrint(print(input$btn))
textboxToggle <- reactive({
if (input$btn %% 2 == 1) {
textInput("textin", "Write something:", value = "Hello World!")
}
})
output$textbox_ui <- renderUI({ textboxToggle() })
})
shinyApp(ui, server)
To add and remove elements:
After reading a bit of the jQuery example, I think this is similar to what you were looking for:
library(shiny)
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("add_btn", "Add Textbox"),
actionButton("rm_btn", "Remove Textbox"),
textOutput("counter")
),
mainPanel(uiOutput("textbox_ui"))
))
server <- shinyServer(function(input, output, session) {
# Track the number of input boxes to render
counter <- reactiveValues(n = 0)
observeEvent(input$add_btn, {counter$n <- counter$n + 1})
observeEvent(input$rm_btn, {
if (counter$n > 0) counter$n <- counter$n - 1
})
output$counter <- renderPrint(print(counter$n))
textboxes <- reactive({
n <- counter$n
if (n > 0) {
lapply(seq_len(n), function(i) {
textInput(inputId = paste0("textin", i),
label = paste0("Textbox", i), value = "Hello World!")
})
}
})
output$textbox_ui <- renderUI({ textboxes() })
})
shinyApp(ui, server)
The problem with this approach is that each time you press the add or remove button, all of the input boxes get re-rendered. This means that any input you might have had on them disappears.
I think you could get around that by also saving the current input values of the input boxes into a reactiveValues object, and setting the values from the object as the starting values of the re-rendered input boxes by using the value option in textInput. I'll leave the implementation of that for now, though.
Thank you #Mikko Marttila for your answer. I was able to use it for my purpose. Also, referring to the issue of all input boxes getting re-rendered here I found a solution worked from this answer. You can save all user inputs using reactiveValuesToList(), then call the reactive list accordingly to set every value to the corresponding user's input in the lapply() statement.
library(shiny)
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("add_btn", "Add Textbox"),
actionButton("rm_btn", "Remove Textbox"),
textOutput("counter")
),
mainPanel(uiOutput("textbox_ui"))
))
server <- shinyServer(function(input, output, session) {
# Track the number of input boxes to render
counter <- reactiveValues(n = 0)
# Track all user inputs
AllInputs <- reactive({
x <- reactiveValuesToList(input)
})
observeEvent(input$add_btn, {counter$n <- counter$n + 1})
observeEvent(input$rm_btn, {
if (counter$n > 0) counter$n <- counter$n - 1
})
output$counter <- renderPrint(print(counter$n))
textboxes <- reactive({
n <- counter$n
if (n > 0) {
isolate({
lapply(seq_len(n), function(i) {
textInput(inputId = paste0("textin", i),
label = paste0("Textbox", i),
value = AllInputs()[[paste0("textin", i)]])
})
})
}
})
output$textbox_ui <- renderUI({ textboxes() })
})
shinyApp(ui, server)
EDIT: I wrapped the lapply() statement in isolate() because it gets annoying when boxes are being re-rendered as you're trying to type in the field
Instead of re-rendering the entire list of inputs, try the following
I keep track of all the ids that are created, I remove the last one created, and I reuse the ids of the deleted ones.
I start with an initial box (there is no real need for that, but I guess in a real work scenario you would expect at least 1 textBox to appear and increase thereafter). It is straightforward to start without the initial box.
Also, I keep track of the values of the textInput boxes that are currently active, in a reactive List. You would definitely need this
Lastly, I think for the two reactiveValues I have [inserted and counter], one of them is possibly redundant, but hey...
Hope it helps!
library(shiny)
ui <- fluidPage(
actionButton("insertBtn", "Insert"),
actionButton("deleteBtn", "Delete"),
h4("My boxes"),
# Initial box here to start with. Not needed but it is nice to have one :)
div(id = "box-1", textInput(inputId = "box-1", label = "box-1")),
div(id = "placeholder"),
h4('Box contents'),
verbatimTextOutput("my_inputs")
)
server <- function(input, output, session) {
## keep track of elements inserted and a counter of the elements
rv <- reactiveValues(
inserted = c("box-1"),
counter = 1
)
observeEvent(input$insertBtn, {
rv$counter <- rv$counter+1
serial <- rv$counter
id <- paste0('box-', serial)
rv$inserted <- c(rv$inserted, id)
insertUI(
selector = '#placeholder',
## wrap element in a div with id for ease of removal
ui = div(id = id,
textInput(inputId = id, label = paste0("box-", serial))
)
)
})
observeEvent(input$deleteBtn, {
req(rv$counter>0)
# removes the last one
id_to_remove <- rv$inserted[length(rv$inserted)]
removeUI(
## pass it in as JQuery selector
selector = paste0('#', id_to_remove)
)
rv$inserted <- rv$inserted[-length(rv$inserted)]
rv$counter <- rv$counter - 1
})
my_inputs <- reactive({
req(rv$inserted) # need to have some inputs
l <- reactiveValuesToList(input)
# regex of the union of all inputs. Note the starting input box-1
ids_regex <- paste(c("box-1", rv$inserted), collapse = "|")
l[grepl(ids_regex, names(l))]
})
output$my_inputs <- renderPrint({
my_inputs()
})
}
shinyApp(ui, server)
Many thanks to
this post
and this
and these SO posts one, two