I'm dynamically generating inputs using a custom function render_panels that creates a wellPanel with a selectizeInput and actionButton contained within, the actionButton removes the entire wellPanel using removeUI by using the id of the div as the selector. I also have a global add button to add new wellPanel.
I have a method to remove the wellPanel by observing the remove button event for each panel, then using removeUI and specifying corresponding div id as selector, but I'm wondering if there is a more efficient method to do this with either for loop or vectorized approach.
Edit Note: Instead of insertUI, I'm specifically using this approach in order to provide the ability to initialize the app with panels already inserted. The shiny app will be executed as a function where users could provide a character vector of dropdown selection values, for example. I've added a character vector prevInputs inside server, a reactive value counter$n which has replaced input$add in order to create initial panels of length(prevInputs) if !is.null(prevInputs) and a method to initialize the selected values argument for selectizeInput with existing values inside make_panels to illustrate the point.
See reprex:
library(shiny)
render_panels <- function(n, removed_panels, inputs){
make_panels <- function(n, inputs){
panels <- tags$div(id = n,
wellPanel(
selectizeInput(inputId = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = inputs[[paste0("dropdown", n)]]),
actionButton(paste0("remove", n), label = paste0("remove", n))
)
)
}
ui_out <- vector(mode = "list", length = n)
for(i in seq_along(ui_out)){
if(i %in% removed_panels) next
ui_out[[i]] <- tagList(
make_panels(n = i, inputs)
)
}
return(ui_out)
}
ui <- fluidPage(
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
removed <- reactiveValues(
values = list()
)
prevInputs <- c("a", "b", "c")
reactiveInputs <- reactiveValues(values = list())
observe({
reactiveInputs$values$dropdown1 = prevInputs[[1]]
reactiveInputs$values$dropdown2 = prevInputs[[2]]
reactiveInputs$values$dropdown3 = prevInputs[[3]]
})
counter <- reactiveValues(n = ifelse(!is.null(prevInputs), length(prevInputs), 0))
observeEvent(input$add, {
counter$n <- counter$n + 1
})
observeEvent(input$remove1,{
removed$values <- c(removed$values, 1)
removeUI(
selector = "div#1", immediate = TRUE,
)
}, once = TRUE)
observeEvent(input$remove2,{
removed$values <- c(removed$values, 2)
removeUI(
selector = "div#2", immediate = TRUE,
)
}, once = TRUE)
observeEvent(input$remove3,{
removed$values <- c(removed$values, 3)
removeUI(
selector = "div#3", immediate = TRUE,
)
}, once = TRUE)
output$mypanels <- renderUI({
render_panels(n = counter$n, removed_panels = removed$values, inputs = reactiveInputs$values)
})
}
shinyApp(ui, server)
As you can see, if there are 100 wellPanels generated, I'd have to use 100 observeEvent, not what we want...here is my attempt at for loop:
I'd like to replace all observeEvent calls with something like below, but cannot seem to get things working.
observe({
req(input$remove1)
for(i in seq_len(input$add)){
if(input[[paste0("remove", i)]] == 1){
removeUI(selector = paste0("div#", i), immediate = TRUE)
}
}
})
Edit:
Here is an attempt from a provided answer using shinymaterial package for alternative UI. Note shinymaterial package requires you to wrap ui elements in render_material_from_server inside renderUI for any UI generated on the server side i.e.
output$dropdown <- renderUI({
render_material_from_server(
material_dropdown(input_id = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = "a")
)
})
This function render_material_from_server is newly available and only exists in current development version of package on GH: shinymaterial
In any case, insertUI does not render UI elements as expected using material_page UI of from shinymaterial
library(shiny)
library(shinymaterial)
make_panels <- function(n, selected){
tags$div(
material_card(
material_dropdown(input_id = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = selected),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- material_page(
tags$script("
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
"),
material_row(
material_column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
choices = c("a", "b", "c")
init_counter <- reactiveVal(3)
observe({
for(i in seq_len(isolate(init_counter()))){
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(i, choices[i]))
}
})
observeEvent(input$add, {
panel_index <- init_counter() + input$add
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(panel_index, choices[panel_index]))
})
}
shinyApp(ui, server)
I think that this situation is a good usecase for modules. Basically, you only write the code once how to generate a panel and then call this module every time you want a new panel. Inside the module, the observeEvent is automatically generated so you don't have to repeat code.
2 things to add:
if you want to access the data returned by the module, you need to store the output of the module call in the main server function
having a lot of modules generates a lot of observers. These observers also stay when a module ui is removed. See this blog post how to deal with this if it should get a problem.
library(shiny)
mod_panel_ui <- function(id) {
ns <- NS(id)
panel_number <- regmatches(id,
regexpr("[0-9]+", id))
tags$div(id = id,
wellPanel(
selectizeInput(inputId = ns("dropdown"),
label = paste0("dropdown ", panel_number),
choices = c("a", "b", "c"),
selected = NULL),
actionButton(ns("remove"), label = paste0("remove ", panel_number))
)
)
}
mod_panel <- function(id) {
moduleServer(id,
function(input, output, session) {
observeEvent(input$remove, {
removeUI(selector = paste0("div#", id))
})
})
return(list(
dropdown = reactive(input$dropdown)
))
}
ui <- fluidPage(
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
div(id = "add_panels_here")
)
)
)
server <- function(input, output, session) {
counter_panels <- 1
observeEvent(input$add, {
current_id <- paste0("panel_", counter_panels)
mod_panel(current_id)
insertUI(selector = "#add_panels_here",
ui = mod_panel_ui(current_id))
# update counter
counter_panels <<- counter_panels + 1
})
}
shinyApp(ui, server)
Edit
Here is a solution that uses shinymaterial and already shows 2 panels on startup. The selected element can be specified by an additional argument to the module server function:
library(shiny)
library(shinymaterial)
mod_panel_ui <- function(id) {
ns <- NS(id)
uiOutput(ns("placeholder"))
}
mod_panel <- function(id, selection = NULL) {
moduleServer(id,
function(input, output, session) {
# generate the UI on the server side
ns <- session$ns
panel_number <- regmatches(id,
regexpr("[0-9]+", id))
output$placeholder <- renderUI({render_material_from_server(tags$div(id = id,
material_card(
material_dropdown(input_id = ns("dropdown"),
label = paste0("dropdown ", panel_number),
choices = c("a", "b", "c"),
selected = selection),
actionButton(ns("remove"), label = paste0("remove ", panel_number))
)
))
})
# remove the element
observeEvent(input$remove, {
removeUI(selector = paste0("div#", id))
})
})
return(list(
dropdown = reactive(input$dropdown)
))
}
ui <- material_page(
material_row(
material_column(width = 6,
actionButton("add", label = "add"),
div(id = "add_panels_here")
)
)
)
server <- function(input, output, session) {
counter_panels <- 1
panels_on_startup <- 2
selected_on_startup <- c("b", "c")
# add counters on startup
lapply(seq_len(panels_on_startup), function(i) {
current_id <- paste0("panel_", counter_panels)
mod_panel(current_id, selected_on_startup[i])
insertUI(selector = "#add_panels_here",
ui = mod_panel_ui(current_id))
# update counter
counter_panels <<- counter_panels + 1
})
observeEvent(input$add, {
current_id <- paste0("panel_", counter_panels)
mod_panel(current_id)
insertUI(selector = "#add_panels_here",
ui = mod_panel_ui(current_id))
# update counter
counter_panels <<- counter_panels + 1
})
}
shinyApp(ui, server)
There is a very simple way to do so if you know some javascript.
There is no need to use for loop
There is no need to save things in a list.
There is no need for renderUI
There is no need to observe every panel
All you need to do is add a js listener to the remove button and add a class in R class = "mybtn" for js to listen to.
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
In your server, you need to think the reverse way, using insertUI rather than removeUI. You only need one observer for the add button. When every time you click on add, add a panel to a div. In my case, I'm lazy, so I just directly select your uiOutput("mypanels")
library(shiny)
make_panels <- function(n){
tags$div(
wellPanel(
selectizeInput(inputId = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = NULL),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- fluidPage(
tags$script("
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
"),
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
observeEvent(input$add, {
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(input$add))
})
observe({
print(input$dropdown5)
})
}
shinyApp(ui, server)
To make sure this works, I add a test observer to watch the dropdown5 (the dropdown when you add the 5th panel). You will see the dropdown value in console once you add the 5th panel.
EDIT for your note:
You can still insert with preset panels. Add a reactive counter for how many panels you want to initiate. Just make sure you isolate the counter and the choice if that is reactive too. In my example choice is hard-coded so I didn't isolate. This is to prevent the panel initialization been run later. The observe I added will only run once.
I also use [] instead of [[]] which gives NA instead of error when out of boundary.
library(shiny)
make_panels <- function(n, selected){
tags$div(
wellPanel(
selectizeInput(inputId = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = selected),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- fluidPage(
tags$script("
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
"),
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
choices = c("a", "b", "c")
init_counter <- reactiveVal(3)
observe({
for(i in seq_len(isolate(init_counter()))){
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(i, choices[i]))
}
})
observeEvent(input$add, {
panel_index <- init_counter() + input$add
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(panel_index, choices[panel_index]))
})
}
shinyApp(ui, server)
To work with materialUI:
change the tags$script() to this one
library(shiny)
library(shinymaterial)
make_panels <- function(n, selected){
tags$div(
material_card(
material_dropdown(input_id = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = selected),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- material_page(
HTML("<script>
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
var formatDropdown = function() {
function initShinyMaterialDropdown(callback) {
$('.shiny-material-dropdown').formSelect();
callback();
}
initShinyMaterialDropdown(function() {
var shinyMaterialDropdown = new Shiny.InputBinding();
$.extend(shinyMaterialDropdown, {
find: function(scope) {
return $(scope).find('select.shiny-material-dropdown');
},
getValue: function(el) {
var ans;
ans = $(el).val();
if (ans === null) {
return ans;
}
if (typeof(ans) == 'string') {
return ans.replace(new RegExp('_shinymaterialdropdownspace_', 'g'), ' ');
} else if (typeof(ans) == 'object') {
for (i = 0; i < ans.length; i++) {
if (typeof(ans[i]) == 'string') {
ans[i] = ans[i].replace(new RegExp('_shinymaterialdropdownspace_', 'g'), ' ');
}
}
return ans;
} else {
return ans;
}
},
subscribe: function(el, callback) {
$(el).on('change.shiny-material-dropdown', function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off('.shiny-material-dropdown');
}
});
Shiny.inputBindings.register(shinyMaterialDropdown);
});
}
$(document).ready(function(){
setTimeout(formatDropdown, 500);
})
$(document).on('click', '#add', function(){
setTimeout(formatDropdown, 100);
})
</script>"),
material_row(
material_column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
choices = c("a", "b", "c")
init_counter <- reactiveVal(3)
observe({
for(i in seq_len(isolate(init_counter()))){
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(i, choices[i]))
}
})
observeEvent(input$add, {
panel_index <- init_counter() + input$add
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(panel_index, choices[panel_index]))
})
}
shinyApp(ui, server)
Related
I have been trying to create a dashboard with up to 3 inputs and then plot some data. I have done this part but the requirement now has changed that every time there is a selection of a new variable they should also be able to filter the data based on the new input. Here has been my attempt so far:
UI:
library(shiny)
ui <- fluidPage(
sidebarPanel(
tags$br(),
uiOutput("textbox_ui"),
uiOutput("filter_ui"),
tags$br(),
actionButton("add_btn", "Add Factor"),
actionButton("rm_btn", "Remove Factor"),
tags$br(),
actionButton("make","Create Graph and Tables")
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Data stuff")
)
)
)
Server:
server <- function(input, output) {
# Track the number of input boxes to render
counter <- reactiveValues(n = 0)
AllInputs <- reactive({
x <- reactiveValuesToList(input)
})
observeEvent(input$add_btn, {
if(counter$n >2){
2
}else{
counter$n <- counter$n + 1
}
})
observeEvent(input$rm_btn, {
if (counter$n > 0) counter$n <- counter$n - 1
})
textboxes <- reactive({
n <- counter$n
if (n > 0) {
isolate({
lapply(seq_len(n), function(i) {
selectInput(inputId = paste0("var", i+1),
label = "",
choices = colnames(mtcars),
selected = AllInputs()[[paste0("var", i+1)]])
})
})
}
})
filterboxes <- reactive({
n <- counter$n
extrainputs <- sapply(seq_len(n), function(i) {
AllInputs()[[paste0("var", i+1)]]
})
summvar <- c(input$var1, extrainputs)
if(n > 0 ){
isolate({
lapply(1:length(summvar), function(x){
text <- summvar[x]
val_level <- unique(mtcars[[text]])
selectInput(inputId = paste0("fil",x+1),
label = paste0("Filter for ", text),
choices = val_level,
multiple = TRUE,
selected = val_level)
})
})
}
})
output$textbox_ui <- renderUI({ textboxes() })
output$filter_ui <- renderUI({ filterboxes() })
}
Two problems arise with this set up so far. One I cannot unselect any of the values when they appear in the filter second I see this warning on the sever side "Warning: Error in .subset2: invalid subscript type 'list'". My reactive skills are quite poor and any suggestions (reactive or not) would be appreciated.
As suggested in my comment...
library(shiny)
myfun <- function(df, var1) {
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
ui <- fluidPage(
sidebarPanel(
tags$br(),
# uiOutput("textbox_ui"),
# uiOutput("filter_ui"),
tags$br(),
tags$div(id = 'placeholder'),
actionButton("add_btn", "Add Factor"),
actionButton("removeBtn", "Remove Factor"),
tags$br(),
actionButton("make","Create Graph and Tables")
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Data stuff")
)
)
)
server <- function(input, output, session) {
# Track the number of variables
numvars <- reactiveVal(0)
### keep track of elements/lines inserted and not yet removed
inserted <- c()
observeEvent(input$add_btn, {
if(input$add_btn==0) {
return(NULL)
}
else {
if (numvars()<0) {
numvars(0) # clicking on remove button too many times yields negative number; reset it to zero
}
newValue <- numvars() + 1 # newValue <- rv$numvars + 1
numvars(newValue) # rv$numvars <- newValue
# btn needs to be adjusted if removing and adding factors
if (input$removeBtn==0){
btn <- input$add_btn
}else {
if (input$add_btn > input$removeBtn) {
btn <- input$add_btn - input$removeBtn # add_btn counter does not decrease
}else btn <- numvars()
}
id <- paste0('txt', btn)
insertUI(
selector = '#placeholder',
## wrap element in a div with id for ease of removal
ui = tags$div(
selectInput(inputId = paste0("var", btn),
label = "",
choices = colnames(mtcars)
),
selectInput(inputId = paste0("fil",btn),
label = paste0("Filter for ", id),
choices = "",
multiple = TRUE),
id = id
)
)
}
# inserted <<- c(id, inserted) ## removes first one first
inserted <<- c(inserted, id) ## removes last one first
}, ignoreInit = TRUE) ## end of observeevent for add_btn
observe({
#print(numvars())
lapply(1:numvars(), function(i){
observeEvent(input[[paste0("var",i)]], {
mydf <- mtcars
mydf2 <- myfun(mydf,input[[paste0("var",i)]])
mysub <- unique(mydf2$newvar)
nam <- as.character(input[[paste0("var",i)]])
updateSelectInput(session = session,
inputId = paste0("fil",i),
label = paste0("Filter for ", nam),
choices = mysub,
selected = mysub
)
})
})
})
observeEvent(input$removeBtn, {
newValue <- numvars() - 1
numvars(newValue)
removeUI(
## pass in appropriate div id
selector = paste0('#', inserted[length(inserted)])
)
inserted <<- inserted[-length(inserted)]
print(inserted)
}, ignoreInit = TRUE)
}
shinyApp(ui = ui, server = server)
I encountered the following problem that I have tried to summarize in this minimal reproducible example.
The app should be able to dynamically create modules and render the UI of the module - obj_UI in my example - in a tab of the tabsetpanel objTP. Each of this modules should render a R6 object of type objR6. I would like to save the resulting R6 objects into a reactiveValues variable called objCollection and display it in the verbatimTextOutput called displayValues.
When clicking on the input$addObject button, I get the error message "Error in <-: cannot add bindings to a locked environment". I believe the problem lies in the observeEvent at the very end of the example, but cannot figure what it is.
Any help would be much appreciated!
library(shiny)
library(R6)
# Simple R6 object
objR6 <- R6::R6Class(
"objR6",
public = list(
identifier = NULL,
selected_value = NULL,
initialize = function(identifier) {
self$identifier <- identifier
}
)
)
# Module Ui
obj_UI <- function(id) {
tagList(
selectInput(NS(id, "value"), "Chose Value", letters)
)
}
# Module Server
obj_Server <- function(id) {
moduleServer(id, function(input, output, session) {
obj <- reactiveVal(objR6$new(id))
observeEvent(input$value, {
newObj <- obj()$clone()
newObj$selectec_value <- input$value
obj(newObj)
})
return(reactive(obj()))
})
}
# Shiny App
ui <- fluidPage(
fluidPage(
selectInput("objSelection", "Select Object",
choices = "",
selectize = FALSE,
size = 10),
actionButton("addObject", "Add Object"),
actionButton("rmvObject", "Remove Object"),
tabsetPanel(id = "objTP"),
verbatimTextOutput("displayValues")
)
)
server <- function(input, output, session) {
objCount <- reactiveVal(0)
objCollection <- reactiveValues(objects = list())
# Reaction on action button "addObject"
observeEvent(input$addObject, {
# Add another item
objCount(objCount() + 1)
newObjName <- paste0("Object_", objCount())
updateSelectInput(session, "objSelection", choices = c(paste0("Object_", 1:objCount())))
# Append the object tabset panel
appendTab("objTP", tabPanel(newObjName, obj_UI(newObjName)), select = TRUE)
})
# Reaction on action button "rmvObject"
observeEvent(input$rmvObject, {
delObjName <- paste0("Object_", objCount())
objCount(objCount() - 1)
updateSelectInput(session, "objSelection", choices = c(paste0("Object_", 1:objCount())))
removeTab("objTP", target = delObjName)
})
# Implement the server side of module
observeEvent(objCount(), {
if (objCount() > 0) {
for (i in 1:objCount()) {
identifier <- paste0("Object_", i)
observeEvent(obj_Server(identifier), {
objCollection$objects[[identifier]] <- obj_Server(identifier)
})
}
}
# Ouput the selected values
output$displayValues <- renderPrint({
reactiveValuesToList(objCollection)
})
})
}
shinyApp(ui, server)
The following minimal reproducible example is an answer to the problem above. In comparison to the code above I corrected a typo in the server function of the module and also put the initialization of the server part in the observeEvent for the input$addObject and removed the observeEvent for objCount().
library(shiny)
library(R6)
# Simple R6 object
objR6 <- R6::R6Class(
"objR6",
public = list(
identifier = NULL,
selected_value = NULL,
initialize = function(identifier) {
self$identifier <- identifier
}
)
)
# Module Ui
obj_UI <- function(id) {
tagList(
selectInput(NS(id, "value"), "Chose Value", letters)
)
}
# Module Server
obj_Server <- function(id) {
moduleServer(id, function(input, output, session) {
obj <- reactiveVal(objR6$new(id))
observeEvent(input$value, {
newObj <- obj()$clone()
newObj$selected_value <- input$value
obj(newObj)
})
return(reactive(obj()))
})
}
# Shiny App
ui <- fluidPage(
fluidPage(
selectInput("objSelection", "Select Object",
choices = "",
selectize = FALSE,
size = 10),
actionButton("addObject", "Add Object"),
actionButton("rmvObject", "Remove Object"),
tabsetPanel(id = "objTP"),
verbatimTextOutput("displayValues")
)
)
server <- function(input, output, session) {
objCount <- reactiveVal(0)
objCollection <- reactiveValues(objects = list())
# Reaction on action button "addObject"
observeEvent(input$addObject, {
# Add another item
objCount(objCount() + 1)
newObjName <- paste0("Object_", objCount())
updateSelectInput(session, "objSelection", choices = c(paste0("Object_", 1:objCount())))
# Append the object tabset panel
appendTab("objTP", tabPanel(newObjName, obj_UI(newObjName)), select = TRUE)
# Add the server component of the module
observeEvent(obj_Server(newObjName), {
objCollection$objects[[newObjName]] <- obj_Server(newObjName)
})
})
# Reaction on action button "rmvObject"
observeEvent(input$rmvObject, {
delObjName <- paste0("Object_", objCount())
if (objCount() > 0) {
objCount(objCount() - 1)
removeTab("objTP", target = delObjName)
objCollection$objects[[delObjName]] <- NULL
if (objCount() > 0) {
updateSelectInput(session, "objSelection", choices = c(paste0("Object_", 1:objCount())))
} else {
updateSelectInput(session, "objSelection", choices = "")
}
}
})
# Ouput the selected values
output$displayValues <- renderPrint({
lapply(reactiveValuesToList(objCollection)$objects, function(i) {i()})
})
}
shinyApp(ui, server)
I tried to combine editing table by adding, deleting row in DT table with checkboxInput(). It is not quite correct.
If I didn't add editing feature, it returned correct, but if I added editing feature,it didn't response after I added another row. I got stuck for a while, I will appreciate any help from you guys
library(shiny)
library(shinyjs)
library(DT)
# Tab 2 UI code.
tab2UI <- function(id) {
ns <- NS(id)
tabPanel(
"Tab 2",
fluidRow(
#uiOutput(ns('cars')),
h2('The mtcars data'),
DT::dataTableOutput(ns('mytable2')),
uiOutput(ns("edit_1")),
h2("Selected"),
tableOutput(ns("checked"))
)
)
}
# Tab 2 server code.
tab2Server <- function(input, output, session) {
ns <- session$ns
# Helper function for making checkboxes.
shinyInput = function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(ns(paste0(id, i)), label = NULL, ...))
}
inputs
}
# Update table records with selection.
subsetData <- reactive({
sel <- mtcars[1:5,]
})
values <- reactiveValues(df = NULL)
observe({
values$df <- subsetData()
})
# Datatable with checkboxes.
output$mytable2 <- DT::renderDataTable(
datatable(
data.frame(values$df,Favorite=shinyInput(checkboxInput,nrow(values$df), "cbox_", width = 10)),
editable = TRUE,
selection = 'single',
escape = FALSE,
options = list(
paging = FALSE,
preDrawCallback = JS('function() {Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() {Shiny.bindAll(this.api().table().node()); }')
)
)
)
observeEvent(input$add.row_1,{
# print(paste0("Row selected",input$mytable2_rows_selected))
if (!is.null(input$mytable2_rows_selected)) {
td <- values$df
tid_n = as.numeric(input$mytable2_rows_selected)
tid = as.numeric(input$mytable2_rows_selected) + 1
if(tid_n == nrow(td)){
td<- rbind(data.frame(td[1:tid_n, ]),
data.frame(td[tid_n, ]))
}else{
td<- rbind(data.frame(td[1:tid_n, ]),
data.frame(td[tid_n, ]),
data.frame(td[tid: nrow(td), ]))
}
td <- data.frame(td)
print(td)
values$df <- td
}
})
output$edit_1 <- renderUI({
tagList(
actionButton(inputId = ns("add.row_1"), label = "Add Row", icon = icon("plus"),class = "example-css-selector",style = "background-color:gray; border-color:gray;color:white;height:31px;"),
actionButton(inputId = ns("delete.row_1"), label = "Delete Row", icon = icon("minus"),class = "example-css-selector",style = "background-color:gray; border-color:gray;color:white;height:31px;"),br(),br()
)
})
# Helper function for reading checkbox.
shinyValue = function(id, len) {
values <- unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
return(values)
}
# Output read checkboxes.
observe({
len <- nrow(values$df)
output$checked <- renderTable({
data.frame(selected=shinyValue("cbox_", len))
})
})
}
# Define UI for application.
ui <- fluidPage(
useShinyjs(),
navbarPage(
'Title',
tab2UI("tab2")
)
)
# Define server.
server <- function(input, output, session) {
# Call tab2 server code.
callModule(tab2Server, "tab2")
}
# Run the application
shinyApp(ui = ui, server = server)
I am trying to use if/then construct in main server function to determine which, out of a choice of two, modules to call, depending on user input. One choice uses an add/remove action button module to call another module, the other choice calls a different module not using the add/remove button module. Calling the module using add/ remove module is easy enough, as I am passing the UI to call as one of the parameters in the call to the add/remove button module, but I am not sure how to properly insertUI() in the server function after callModule(). So in my example (as simple as I could think to make it), the UI starts with a textInput() box, which defaults to 1. I have a "first" module, which just prepends the userInput() data to letters a,b,c d in selectInput() box. The "second" module prepends "Not 1" to a,b,c,d in selectInput() box. I use observeEvent({}) such that if user does nothing (textInput() stays at 1), then "first" module is called. If the user changes textInput() to anything at all other than default 1, "second" module is called. What I am not clear on is how to call the UI for the second module. I have a uiOutput("dummy") as a placeholder in the ui() function. However, my example does not work as described above, because it does not ever successfully call "second" module if the user changes the testInput() default value. Code below, thanks!
library(shiny)
firstUI <- function(id) { uiOutput(NS(id, "first")) }
firstServer <- function(input, output, session, a) {
ns = session$ns
output$first <- renderUI({
selectInput(ns("select"), h4("Select"), paste0(isolate(a()), letters[1:4]))
})
return(reactive({ c(paste0(input$select), paste0(input$select)) }))
}
removeFirstUI <- function(id) {
removeUI(selector = paste0('#', NS(id, "first")))
}
secondUI <- function(id) { uiOutput(NS(id, "second")) }
secondServer <- function(input, output, session, a) {
ns = session$ns
output$second <- renderUI({
selectInput(ns("select"), h4("Select"), paste0("Not 1", letters[1:4]))
})
return(reactive({ c(paste0(input$select), paste0(input$select)) }))
}
removeSecondUI <- function(id) {
removeUI(selector = paste0('#', NS(id, "second")))
}
addRmBtnUI <- function(id) {
ns <- NS(id)
tags$div(
actionButton(inputId = ns('insertParamBtn'), label = "Add"),
actionButton(ns('removeParamBtn'), label = "Remove"),
hr(),
tags$div(id = ns('placeholder'))
)
}
addRmBtnServer <- function(input, output, session, moduleToReplicate, ...) {
ns = session$ns
params <- reactiveValues(btn = 0, a = list())
observeEvent(input$insertParamBtn, {
params$btn <- params$btn + 1
params$a[[params$btn]] <- callModule(moduleToReplicate$server, id = params$btn, ...)
insertUI(
selector = paste0('#', ns('placeholder')),
ui = moduleToReplicate$ui(ns(params$btn))
)
})
observeEvent(input$removeParamBtn, {
moduleToReplicate$remover(ns(params$btn))
params$btn <- params$btn - 1
})
return(params)
}
ui <- fluidPage(
addRmBtnUI("addRm"),
textInput("a", label = "a", value = 1, width = '150px'),
verbatimTextOutput("text", placeholder = TRUE),
uiOutput("dummy")
)
server <- function(input, output, session) {
a <- reactive({ input$a })
comp <- reactiveValues()
observeEvent(a(), {
if (input$a == 1) {
comp <- callModule(
addRmBtnServer, id = "addRm",
moduleToReplicate = list(
ui = firstUI,
server = firstServer,
remover = removeFirstUI
),
a = a
)
} else {
comp <- callModule(
secondServer, id = 0,
a = a
)
}
}, ignoreNULL = TRUE)
output$text <- renderPrint({
if (!(is.null(comp$btn))) {
if (comp$btn > 0) {
paste(
comp$a[[comp$btn]](),
sep = " "
)
}
} else { paste0("") }
})
}
shinyApp(ui = ui, server = server)
I am trying to dynamically render multiple text output from multiple text input. I tried to use this very helpfull example and this one too.
This conversation is also helpfull.
But when I try to adapt this examples on the following script, I have a problem of output update. Apparently, only the last element was read and updated. It's probably a reactivity problem but it seems to be difficult to associate reactive{()} and renderUI{()}functions.
rm(list = ls())
library(shiny)
creatDataElem <- function(ne, input) {
x1 <- lapply(1:ne, function(i) {
textInput(paste0("elemName", i),
label = h4(strong("Name of dataset element")),
value = "")
})
return(x1)
}
ui = (fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("elemNb",
"Number of elements", value = 1, min = 1,
max = 3)
,
conditionalPanel(
condition = "input.elemNb == 1",
creatDataElem(1)
),
conditionalPanel(
condition = "input.elemNb == 2",
creatDataElem(2)
),
conditionalPanel(
condition = "input.elemNb == 3",
creatDataElem(3)
)
),
mainPanel(
uiOutput("nameElem")
)
)
)
)
server = function(input, output, session) {
max_elem <- 3
# Name
output$nameElem <-renderUI({
nameElem_output_list <- lapply(1:input$elemNb, function(i) {
elemName <- paste0("elemName", i)
tags$div(class = "group-output",
verbatimTextOutput(elemName)
)
})
do.call(tagList, nameElem_output_list)
})
for (i in 1:max_elem) {
local({
force(i)
my_i <- i
elemName <- paste0("elemName", my_i)
output[[elemName]] <- renderPrint(input[[elemName]])
})
}
}
runApp(list(ui = ui, server = server))
The idea with a reactive({}) function is to add an independant object (a function in this case) like:
nameElem <- reactive({
if (input$goElem == 0) {
return()
} else {
isolate({
if (is.null(input$elemName)) {
return()
} else if (test(input$elemName)) {
return("TEST RESULT")
} else {
return(input$elemName)
}
})
}
})
and to use renderUI on this object (with an ActionButton).
So, if someone knows why the output does not return the good object...
I think one of your problems is that your creatDataElem function is such that when it is called with argument ne=3, the first and second textInput elements are created again (and their value "lost").
Anyway, I think one solution would be to create those textInput elements as an "uiOutput".
You'll find a possible solution below which (I think) does what you want.
Lise
rm(list = ls())
library(shiny)
ui = (fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("elemNb",
"Number of elements", value = 1, min = 1,
max = 3),
uiOutput("myUI")
),
mainPanel(
uiOutput("nameElem")
)
)
)
)
server = function(input, output, session) {
output$myUI=renderUI({
w=""
for (i in 1:input$elemNb){
w=paste0(w,
textInput(paste0("elemName",i),label='Name of dataset element'))
}
HTML(w)
})
output$nameElem <-renderUI({
elems=c("<div>")
for(i in 1:input$elemNb){
elems=paste(elems,"</div><div>",input[[paste0("elemName",i)]])
}
elems=paste0(elems,"</div>")
HTML(elems)
})
}
runApp(list(ui = ui, server = server))
Found a solution:
library(readr)
library(dplyr)
library(shiny)
df <- data.frame(symbol = 1:10)
uiOutput("myUI")
createUI <- function(dfID, symbol) {
div(class="flex-box",paste0(symbol, " - 10"))
}
output$myUI <- renderUI({
w <- lapply(seq_len(nrow(df)), function(i) {
createUI(i, df[i,"symbol"])
})
do.call(fluidPage,w)
})