In the following example, I use an actionLink() in "Tab1" to switch the display to "Tab2", then to update the text in the textInput() with "iris" and finally to click the button to display the actual data.
For an unknown reason, this switch is executed after the Sys.sleep() which is after. This lead to a non-desired behavior since shinyjs::click() for instance try to trigger a button that does not exist yet and thereby the 'iris' table is not displayed. I would have expected the tab to be switched first, then to get the Sys.sleep().
Can someone explain to me why Shiny is behaving like that and how to perform the expected behavior please?
library(shiny)
library(shinydashboard)
library(shinydashboardPlus) # shinydashboardPlus_0.7.5 // devtools::install_version("shinydashboardPlus", version = "0.7.5", repos = "http://cran.us.r-project.org")
library(shinyjs)
available_data_sets <- c("mtcars", "iris")
ui_welcome <- fluidPage(actionLink(inputId = "switch_tab", label = "Switch tab, update text with 'iris' & click button", icon = icon("hand-point-right")))
ui_search <- fluidPage(
textInput(inputId = "dataset_name", label = "Select data set", value = ""),
actionButton(inputId = "display_tab", label = "Display selected set"),
br(),
tableOutput('table'))
ui <- dashboardPagePlus(
collapse_sidebar = TRUE,
title = "Minimal",
header = dashboardHeaderPlus(
title = "Test",
enable_rightsidebar = FALSE,
rightSidebarIcon = "gears",
fixed = FALSE),
sidebar = dashboardSidebar(
sidebarMenu(
id = "left_sidebar",
menuItem("Tab1", tabName = "tab1", icon = icon("home")),
menuItem("Tab2", tabName = "tab2", icon = icon("search")))
),
body = dashboardBody(
useShinyjs(),
tabItems(tabItem(tabName = "tab1", ui_welcome),
tabItem(tabName = "tab2", ui_search)))
)
server <- function(input, output, session) {
# RENDER DATA
observeEvent(input$display_tab, {
print("Within observeEvent 'display_tab'")
selected <- input$dataset_name
print(paste0("Value of 'input$dataset_name' received: ", selected))
if(!isTruthy(selected)) return(NULL)
if(!selected %in% available_data_sets) return(NULL)
output$table <- renderTable(if(selected == "iris") iris else mtcars)
})
# SWITCH TAB & TRIGGER BTN
observeEvent(input$switch_tab, {
print("Within observeEvent 'switch_tab'")
print("updateTabItems"); updateTabItems(session, inputId = "left_sidebar", selected = "tab2")
print("updateTextInput 1"); updateTextInput(inputId = "dataset_name", value = "")
print("updateTextInput 2"); updateTextInput(inputId = "dataset_name", value = "iris")
print("Sys.sleep"); Sys.sleep(3)
print("shinyjs::click"); shinyjs::click(id = "display_tab")
})
}
shinyApp(ui, server)
Shiny reactivity is based on reaction on single events. You are basically trying to do two actions in reaction to a single event (reconfigure interface then act on it). It won't work as the reconfiguration will actually be done in between reaction code. You are trying to circumvent this by waiting using Sys.sleep function. This will only pause the whole application and it will wake up as if nothing happened. Try to put a longer sleep time and you will notice that the UI will be non responsive.
The solution is to refactor your code to be data centric and not user centric. What you want is to be able to select and display a dataset in two different ways. One "automatically" by clicking on a link the other manually by providing the dataset name and click on a button.
Here I give some flesh to the dataset that will be able to redisplay on change and provide two ways to set it's state.
server <- function(input, output, session) {
# reactive value to store selected dataset
state <- reactiveValues(selected=NULL)
# AUTO RENDER DATA on selected dataset change
output$table <- renderTable({
if(!is.null(state$selected)) {
if(state$selected == "iris") iris else mtcars
}
})
# manual select dataset
# already on tab so no need to switch
observeEvent(input$display_tab, {
if(!isTruthy(input$dataset_name) || !(input$dataset_name %in% available_data_sets)) {
state$selected <- NULL
} else {
state$selected <- input$dataset_name
}
})
# automatic selection through actionlink
# select dataset and switch tab
observeEvent(input$switch_tab, {
state$selected <- "iris"
updateTabItems(session, inputId = "left_sidebar", selected = "tab2")
})
}
Related
I am new to shiny and am currently trying to develop my first shinyapp.
This apps contains multiple actionButtons and nested observeEvents statements, which I think are the cause of my problem.
The app should allow the user to add observations of species by clicking on a add button, that updates the UI. Within each observation, more details can be asked, but I only showed the species name in the REPREX below (textinput).
Each observation can be deleted individually via a delete button.
Until here, it works! However, I also want a modal dialog to confirm the deletion when the delete button is clicked. To do this, I used a nested observeEvent and it doesn't seem to work (or maybe only for the first time). What am I doing wrong ?
Thanks in advance to anyone who tries to help me.
library(shiny)
library(random)
ui <- fluidPage(
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete")
)
)
)
)
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input[[removeSpeciesId]], {
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
# Delete observation if user confirms
observeEvent(input$confirm, {
id_to_remove <- substring(removeSpeciesId,1, nchar(removeSpeciesId)-8)
rv$GridId_list <- rv$GridId_list[rv$GridId_list!=id_to_remove]
removeUI(selector = paste("#", id_to_remove, sep = ""))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
})
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
Referencing dynamic input id's is a pain. I find it best to add a last clicked input identifier to reference. You can add a class to those inputs to just listen to them and not others in your app:
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});")))
That little piece of code will allow you to get an input$last_btn id, that you can use for your event listeners. In this case you don't need to nest your event listeners; it is better to think about the events in sequence and program those reactions. So, with some tweakings in your code, your app now looks like this:
library(shiny)
library(random)
ui <- fluidPage(
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});"))),
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete", class="needed")
)
)
)
)
})
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input$last_btn, {
observeEvent(input[[input$last_btn]] > 0,{#We want the modal to show when any "remover" id is clicked
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
})
}, ignoreNULL = TRUE, ignoreInit = TRUE)
# Delete observation if user confirms
observeEvent(input$confirm, {
#The following selector is for the parent id of the parent id of the last_btn id
removeUI(selector = paste0("div:has(>div:has(>#", input$last_btn, "))"))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
Overview
Hello, I am trying to work with displaying different plots using checkboxes within tabsetPanels. I am working with a dynamic amount number of panels, so that is the reason for creating the UI contents within the server portion.
Ideal Output
For each tabPanel:
iris plot outputs if no checkboxes are selected
mtcars plot outputs if Box One is selected
islands plot outputs if Box Two is selected
sleep plot outputs if both Box One and Box Two are selected
What I have tried
-I have tried to use condtionalPanels to try to & capture the cases. I was under the impression that the value returns 'TRUE' if checked & 'FALSE' if unchecked, however I receive NULL for each of the boxed values, even if I set the default value to be checked.
-I believe my underlying issue is my lack of ability to trigger the dynamic checkboxes for each tabPanel
Disclaimer
This is a reproducible example, the default values of originally created tabPanels is set to 5. I did not accommodate proper code if the value were to change for the sake of simplicity.
Sample Code:
ui <- navbarPage(title="Dynamic tabsetPanels",id="navbar",
tabPanel("Home",
textInput(inputId = "numPanels",
label = "Enter # of Panels to produce",
value = 5)
),tabPanel("Analysis",
tabsetPanel(id = "tabs"))
)
server <- function(input, output) {
plotOne = renderPlot({plot(iris)})
plotTwo = renderPlot({plot(mtcars)})
plotThree = renderPlot({plot(islands)})
plotFour = renderPlot({plot(sleep)})
observe({
req(input$numPanels)
lapply(1:input$numPanels,function(i){
tabName = paste("Tab",i,sep=" ")
first = paste0("first",i)
second = paste0("second",i)
appendTab(inputId = "tabs",
tab = tabPanel(
tabName,
fluidPage(
sidebarLayout(
sidebarPanel(
#side-panel code
h2("Features"),
checkboxInput(inputId=first,label="Box One"),
checkboxInput(inputId=second,label="Box Two")
),mainPanel(
#output when nothing clicked
conditionalPanel(
condition = "!glue(input.{first} && !glue(input.{second})",
plotOutput(iris)
),
#output when box one is clicked
conditionalPanel(
condition = "glue(input.{first})",
plotOutput(mtcars)
),
#output when box two is clicked
conditionalPanel(
condition = "glue(input.{second})",
plotOutput(islands)
),
#output when box one and two are clicked
conditionalPanel(
condition = "glue(input.{first}) && glue(input.{second})",
plotOutput(sleep)
)
)
)
)
)
)
})
})
}
shinyApp(ui=ui, server=server)
Any suggestions would be greatly appreciated!
First issue with your code is the use of glue to create your conditions, i.e. you have to do e.g. condition = glue("input.{first}") instead of condition = "glue(input.{first})" to evaluate the glue string. Second issue is that in the plotOutputs you have to use the names of the outputs, e.g. plotOutput("plotOne") instead of plotOutput(iris). Finally, even after fixing these issues your app will not work as desired as you can't use outputs with the same id in several places or tabs, i.e. you get a duplicated id error. To fix that you also have to create a dynamic list of outputs so that the ids are unique.
library(shiny)
library(glue)
ui <- navbarPage(
title = "Dynamic tabsetPanels", id = "navbar",
tabPanel(
"Home",
textInput(
inputId = "numPanels",
label = "Enter # of Panels to produce",
value = 5
)
), tabPanel(
"Analysis",
tabsetPanel(id = "tabs")
)
)
server <- function(input, output) {
observe({
req(input$numPanels)
lapply(1:input$numPanels, function(i) {
output[[paste0("plotOne", i)]] <- renderPlot(plot(iris))
output[[paste0("plotTwo", i)]] <- renderPlot(plot(mtcars))
output[[paste0("plotThree", i)]] <- renderPlot(plot(islands))
output[[paste0("plotFour", i)]] <- renderPlot(plot(sleep))
})
})
observe({
req(input$numPanels)
lapply(1:input$numPanels, function(i) {
tabName <- paste("Tab", i, sep = " ")
first <- paste0("first", i)
second <- paste0("second", i)
appendTab(
inputId = "tabs",
tab = tabPanel(
tabName,
fluidPage(
sidebarLayout(
sidebarPanel(
# side-panel code
h2("Features"),
checkboxInput(inputId = first, label = "Box One"),
checkboxInput(inputId = second, label = "Box Two")
), mainPanel(
# output when nothing clicked
conditionalPanel(
condition = glue("!input.{first} && !input.{second}"),
plotOutput(paste0("plotOne", i))
),
# output when box one is clicked
conditionalPanel(
condition = glue("input.{first}"),
plotOutput(paste0("plotTwo", i))
),
# output when box two is clicked
conditionalPanel(
condition = glue("input.{second}"),
plotOutput(paste0("plotThree", i))
),
# output when box one and two are clicked
conditionalPanel(
condition = glue("input.{first} && input.{second}"),
plotOutput(paste0("plotFour", i))
)
)
)
)
)
)
})
})
}
shinyApp(ui = ui, server = server)
I have an app which creates boxes. Each box has a button that triggers a modal. The modal has inputs which the user changes and then a button which triggers an action based on those inputs (basically just uploading to a database). Because each box has a different specification, I wrote a module and then loop thru a list, creating a box for each element. This works fine.
However, the flow in the modal and observeEvent has a flaw: the first run thru I get the desired results, but on the second occasion in the same box (same id module), after pressing the modal button to update, it will not use the new inputs, but rather what happened in the first run. I am guessing it has something to do with the namespace/observeEvent combination as I might be triggering the event with a "stored" namespace? Would I need to somehow "flush" the namespace after every update? Anyway, any help appreciated as it gets confusing fast with all the namespace/modules combinations.
library(shiny)
library(shinyWidgets)
ui <- navbarPage(
'page', collapsible = TRUE,
tabPanel("test",
useSweetAlert(),
sidebarLayout(
sidebarPanel(),
mainPanel(
uiOutput('all_products_ui')
)
)
)) # end navbar
server <- shinyServer(function(input, output) {
list_products <- c(1,2,3,4,5)
# Now, I will create a UI for all the products
output$all_products_ui <- renderUI({
r <- tagList()
progress_move <- 0
for(k in 1:length( list_products )){
r[[k]] <- ExistingProductUI(id = k, product = list_products[[k]] )
}
r
})
# handlers duplicate a call to module depending on the id of ExistingProductUI
handlers <- list()
observe(
handlers <<- lapply(seq.int(length( list_products )),
function(i) {
callModule(ExistingProductUpdate,
id = i,
product = list_products[[i]] )
})
)
handlers
}) # end of server ----
# UI module ------------------------------------------------------
ExistingProductUI <- function(id, product){
ns <- NS(id)
box(title = as.character(p$title),
product["title"],
footer = tagList(
actionBttn(
inputId = ns("change_selected"), label = "change"),
)
)
}
# server module ------------------------------------------------------
ExistingProductUpdate <- function(input, output, session, product){
ns <- session$ns
observeEvent(input$change_selected, {
# when box button is clicked for this product (id)
# FIRST: show a modal
showModal(
modalDialog(
title = "what do you want to change?",
tagList(
radioGroupButtons(inputId = ns("change_selected_choice"), labels = "change x", choices = c(1,2,3,4)),
sliderInput(ns("change_selected_pct"), "change y:", min = -50, max = 100, value = 0, step = 5)
),
easyClose = TRUE,
footer = tagList(
actionButton(ns("change_selected_submit"), "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
# SECOND: when change_selected_submit is clicked,
observeEvent(input$change_selected_submit, {
# do some calculations with product using what I inputed in modal ---
# then, update a table ----
functionToUploadThings(product, input$change_selected_choice)
# THIRD: Close with a confirmation
sendSweetAlert(
session,
title = "Success!",
type = "success",
btn_labels = "Ok",
closeOnClickOutside = TRUE,
width = NULL
)
})
})
}
Below is a solution that works. The problem was that you nested your observeEvent in the module. I'm not entirely sure why this led to problems, some values weren't processed correctly. However, you don't need to nest the observeEvent, the second one gets also triggered by the actionButton in the modal when it is by its own. Additionally, I included a removeModal before the success notification is shown:
library(shiny)
library(shinyWidgets)
library(shinydashboard)
ui <- navbarPage(
'page', collapsible = TRUE,
tabPanel("test",
useSweetAlert(),
sidebarLayout(
sidebarPanel(),
mainPanel(
uiOutput('all_products_ui')
)
)
)) # end navbar
server <- shinyServer(function(input, output) {
list_products <- c(1,2,3,4,5)
# Now, I will create a UI for all the products
output$all_products_ui <- renderUI({
r <- tagList()
progress_move <- 0
for(k in 1:length( list_products )){
r[[k]] <- ExistingProductUI(id = k, product = list_products[[k]] )
}
r
})
# handlers duplicate a call to module depending on the id of ExistingProductUI
handlers <- list()
observe(
handlers <<- lapply(seq.int(length( list_products )),
function(i) {
callModule(ExistingProductUpdate,
id = i,
product = list_products[[i]] )
})
)
handlers
}) # end of server ----
# UI module ------------------------------------------------------
ExistingProductUI <- function(id, product){
ns <- NS(id)
box(title = as.character(product),
product,
footer = tagList(
actionBttn(
inputId = ns("change_selected"), label = "change"),
)
)
}
# server module ------------------------------------------------------
ExistingProductUpdate <- function(input, output, session, product){
ns <- session$ns
observeEvent(input$change_selected, {
# when box button is clicked for this product (id)
# FIRST: show a modal
showModal(
modalDialog(
title = "what do you want to change?",
tagList(
radioGroupButtons(inputId = ns("change_selected_choice"), label = "change x", choices = c(1,2,3,4)),
sliderInput(ns("change_selected_pct"), "change y:", min = -50, max = 100, value = 0, step = 5)
),
easyClose = TRUE,
footer = tagList(
actionButton(ns("change_selected_submit"), "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
})
# SECOND: when change_selected_submit is clicked,
observeEvent(input$change_selected_submit, {
# do some calculations with product using what I inputed in modal ---
# then, update a table ----
# functionToUploadThings(product, input$change_selected_choice)
# THIRD: Close with a confirmation
removeModal()
sendSweetAlert(
session,
title = "Success!",
type = "success",
btn_labels = "Ok",
closeOnClickOutside = TRUE,
width = NULL
)
})
}
shinyApp(ui, server)
Please note: I made some modifications to make your MWE work:
include library(shinydashboard)
p$title and product["title"] to product
change labels to label in radioGroupButtons
comment out functionToUploadThings(product, input$change_selected_choice)
Edit
I'm still not super sure what happens when nesting the observeEvents. I made a small toy example and played around with the reactlog. It seems that nesting the observers generates a new observer for button2 every time button1 is clicked. These observers are not removed and lead to unwanted behaviour. In contrast, when using separate observeEvents, the observer for button2 is only created once.
library(shiny)
library(reactlog)
ui <- fluidPage(
actionButton("button1", "click")
)
server <- function(input, output, session) {
observeEvent(input$button1, {
print("from first observer")
print(input$button2)
showModal(
modalDialog(
title = "what do you want to change?",
"some text",
easyClose = TRUE,
footer = tagList(
actionButton("button2", "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
# nested observer -> leads to remaining observers
observeEvent(input$button2, {
print("from second observer")
print(input$button2)
removeModal()
})
})
# independent observer -> generates only one observer
# observeEvent(input$button2, {
# print("from second observer")
# print(input$button2)
# removeModal()
# })
}
shinyApp(ui, server)
I am trying to create a shiny code that is able to filter a table non pre-determined number of times. When the user uploads a different (new) table, unfortunately the code breaks as I need to restart a lapply loop somehow, throwing out the previously stored column names.
I would like to create an non pre-defined filtering options for a table within Shiny. The user can select a column and filter a table choosing different categorical variables within that column. It is possible to add additional selection fields by pressing the 'Add' button.
the UI:
library(shiny)
library(shinydashboard)
library(dplyr)
ui <- shinyUI(
pageWithSidebar(
headerPanel("testing of dynamic number of selection"),
sidebarPanel(
uiOutput("buttons")),
mainPanel(
uiOutput("drops")
,tableOutput("table")
)
))
The server:
A table (test.csv) is automatically stored in a reactive values and a first searching field appears with 3 buttons (Add = to add a new searching field by reading in the colnames and a multiselect that stores the unique variables from that columns. The filtering function is activated by the Calculate button)
server<-function(input, output, session) {
###### read in test file
values<-reactiveValues(number = 1,
upload = NULL,
input = NULL)
values$upload<-read.csv("test.csv")
#just the "add" button, in this instance it shouldn't be a uiOutput
output$buttons <- renderUI({
div(
actionButton(inputId = "add", label = "Add"), actionButton(inputId = "calc", label = "Calculate"),
actionButton(inputId = "new", label = "new table")
)
})
#pressing the add button
observeEvent(input$add, {
cat("i adding a new record\n")
values$number <- values$number + 1L })
daStuff <- function(i){
inputName<-paste0("drop", i)
inputName2<-paste0("select", i)
inputText<-if(values$number>0){input[[paste0("drop",i)]]}else{F} # previously selected value for dropdown
inputSelect <- if(values$number>1){input[[paste0("select",i)]]}else{F} # previously selected value for dropdown
fluidRow(
column(6,selectInput(inputName, inputName, c(colnames(values$upload)), selected = inputText)),
column(6,selectInput(inputName2, inputName2,
na.omit(unique(as.vector(values$upload[,input[[paste0("drop",i)]]]))),
multiple=TRUE, selectize=TRUE, selected=inputSelect)) )}
output$drops<- renderUI({
lapply(seq_len(values$number), daStuff)})
By pressing the Calculate button, the uploaded table is subjected to filtering, depending on the selected unique values and shown in the output$table
observeEvent(input$calc, {
values$input<-NULL
for (i in 1:values$number){
if(!is.null(input[[paste0("select",i)]])){
if(is.null(values$input)){
values$input<- filter(values$upload,values$upload[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])}
else{
values$input<- filter(values$input,values$input[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])}
} }
if (is.null(values$input)){values$input<-values$upload}
output$table <- renderTable({values$input})
})
My problem is when I upload a new table (test2.csv), I don't know how to erase the previously stored selections (drop* and select* values) and gives back an error message.
observeEvent(input$new,{
values$upload<-read.csv("test2.csv")
})
}
shinyApp(ui=ui, server = server)
I suppose I should stop somehow the lapply loop and restart it over, so the previously stored values are replaced depending on the new selection, but I am a bit stuck on how I could achieve that.
Just in case you might still be looking for solutions, I wanted to share something that was similar and could potentially be adapted for your needs.
This uses observeEvent for all select inputs. If it detects any changes, it will update all inputs, including the possibilities for select based on drop.
In addition, when a new file is read, the selectInput for drop and select are reset to first value.
Edit: I forgot to keep selected = input[[paste0("drop",i)]] in place for the dropdown (see revised code). It seems to keep the values now when new filters are added - let me know if this is what you had in mind.
library(shiny)
library(shinydashboard)
library(dplyr)
myDataFrame <- read.csv("test.csv")
ui <- shinyUI(
pageWithSidebar(
headerPanel("Testing of dynamic number of selection"),
sidebarPanel(
fileInput("file1", "Choose file to upload", accept = ".csv"),
uiOutput("buttons")
),
mainPanel(
uiOutput("inputs"),
tableOutput("table")
)
)
)
server <- function(input, output, session) {
myInputs <- reactiveValues(rendered = c(1))
myData <- reactive({
inFile <- input$file1
if (is.null(inFile)) {
d <- myDataFrame
} else {
d <- read.csv(inFile$datapath)
}
d
})
observeEvent(lapply(paste0("drop", myInputs$rendered), function(x) input[[x]]), {
for (i in myInputs$rendered) {
updateSelectInput(session,
paste0('select', i),
choices = myData()[input[[paste0('drop', i)]]],
selected = input[[paste0("select",i)]])
}
})
output$buttons <- renderUI({
div(
actionButton(inputId = "add", label = "Add"),
actionButton(inputId = "calc", label = "Calculate")
)
})
observeEvent(input$add, {
myInputs$rendered <- c(myInputs$rendered, max(myInputs$rendered)+1)
})
observeEvent(input$calc, {
showData <- NULL
for (i in 1:length(myInputs$rendered)) {
if(!is.null(input[[paste0("select",i)]])) {
if(is.null(showData)) {
showData <- filter(myData(), myData()[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])
}
else {
showData <- filter(showData, showData[,input[[paste0("drop",i)]]] %in% input[[paste0("select",i)]])
}
}
}
if (is.null(showData)) { showData <- myData() }
output$table <- renderTable({showData})
})
observe({
output$inputs <- renderUI({
rows <- lapply(myInputs$rendered, function(i){
fluidRow(
column(6, selectInput(paste0('drop',i),
label = "",
choices = colnames(myData()),
selected = input[[paste0("drop",i)]])),
column(6, selectInput(paste0('select',i),
label = "",
choices = myData()[1],
multiple = TRUE,
selectize = TRUE))
)
})
do.call(shiny::tagList, rows)
})
})
}
shinyApp(ui, server)
I am trying to select a specific tab of my navbarPage and at the same time selecting a row of my datatable. This works fine for the first tab but not for a different tab. Can anyone help me on this?
library(DT)
library(shiny)
ui <- navbarPage(
id = "tabs",
'URL GET test',
tabPanel(
'welcome',
value='welcome',
h2('hi'),
DT::dataTableOutput("mytable2")
),
tabPanel(
"mtcars",
value='mtcars',
textInput("text", "Text", ""),
DT::dataTableOutput("mytable1")
)
)
server <- function(input, output, session) {
observe({
query <- parseQueryString(session$clientData$url_search)
print(query)
if (!is.null(query[['data']])) {
text_string <- query[['data']]
updateNavbarPage(session, inputId="tabs", selected=query[['data']])
}
if (!is.null(query[['text']])) {
text_string <- query[['text']]
updateTextInput(session, "text", value = text_string)
}
if (!is.null(query[['row']])) {
DT::selectRows(mytable_proxy1, as.numeric(query[['row']]))
DT::selectRows(mytable_proxy2, as.numeric(query[['row']]))
}
})
output$mytable1 = DT::renderDataTable({mtcars})
mytable_proxy1 = DT::dataTableProxy('mytable1')
output$mytable2 = DT::renderDataTable({mtcars})
mytable_proxy2 = DT::dataTableProxy('mytable2')
}
shinyApp(ui, server)
So by using the following URL (IP and port might be different), I can select a row on the first tab http://127.0.0.1:6583/?row=2 (working), with http://127.0.0.1:6583/?data=mtcars&text=bla (working) I can directly select the second tab and also update the text field.
But my goal is to select the second tab and select a specific row like I did for the first tab http://127.0.0.1:6583/?data=mtcars&row=2 (not working).
For example explicitly selecting the first tab and a row http://127.0.0.1:6583/?data=welcome&row=2 (works).
I suspect that the issue here is the order of operations in which Shiny executes the statements. In this case, when you try http://127.0.0.1:6583/?data=mtcars&row=2, the observer for the query first selects the mtcars tab and immediately tries to set the selected row through the DT proxy. However, the second table has not been rendered yet, it is only rendered after the observer finishes running. This is also why http://127.0.0.1:6583/?data=welcome&row=2 runs fine, since the app opens on the welcome tab and it renders the datatable before the observer fires.
One work around would be to store the selected row value in a reactiveVal, and use this value while rendering the datatable. A working example is given below, hope this helps!
library(DT)
library(shiny)
ui <- navbarPage(
id = "tabs",
'URL GET test',
tabPanel(
'welcome',
value='welcome',
h2('hi'),
DT::dataTableOutput("mytable2")
),
tabPanel(
"mtcars",
value='mtcars',
textInput("text", "Text", ""),
DT::dataTableOutput("mytable1")
)
)
server <- function(input, output, session) {
observe({
query <- parseQueryString(session$clientData$url_search)
print(query)
if (!is.null(query[['data']])) {
text_string <- query[['data']]
updateNavbarPage(session, inputId="tabs", selected=query[['data']])
}
if (!is.null(query[['text']])) {
text_string <- query[['text']]
updateTextInput(session, "text", value = text_string)
}
if (!is.null(query[['row']])) {
selected_row(as.numeric(query[['row']]))
}
})
selected_row <- reactiveVal()
output$mytable1 = DT::renderDataTable({
datatable(mtcars ,selection = list(mode = 'multiple', selected = selected_row()))
})
mytable_proxy1 = DT::dataTableProxy('mytable1')
output$mytable2 = DT::renderDataTable({
datatable(mtcars ,selection = list(mode = 'multiple', selected = selected_row()))
})
mytable_proxy2 = DT::dataTableProxy('mytable2')
}
shinyApp(ui, server)