I am trying to create a table using Shiny, where the user can click on a row in order to see further information about that row. I thought I understood how to do this (see code attached).
However, right now as soon as the user clicks the "getQueue" action button, the observeEvent(input$fileList_cell_clicked, {}) seems to get called. Why would this be called before the user even has the chance to click on a row? Is it also called when the table is generated? Is there any way around this?
I need to replace "output$devel <- renderText("cell_clicked_called")" with code that will have all sorts of errors if there isn't an actual cell to refer to.
Thank you for any advice!
ui <- fluidPage(
actionButton("getQueue", "Get list of queued files"),
verbatimTextOutput("devel"),
DT::dataTableOutput("fileList")
)
shinyServer <- function(input, output) {
observeEvent(input$getQueue, {
#get list of excel files
toTable <<- data.frame("queueFiles" = list.files("queue/", pattern = "*.xlsx")) #need to catch if there are no files in queue
output$fileList <- DT::renderDataTable({
toTable
}, selection = 'single') #, selection = list(mode = 'single', selected = as.character(1))
})
observeEvent(input$fileList_cell_clicked, {
output$devel <- renderText("cell_clicked_called")
})}
shinyApp(ui = ui, server = shinyServer)
minimal error code
DT initializes input$tableId_cell_clicked as an empty list, which causes observeEvent to trigger since observeEvent only ignores NULL values by default. You can stop the reactive expression when this list is empty by inserting something like req(length(input$tableId_cell_clicked) > 0).
Here's a slightly modified version of your example that demonstrates this.
library(shiny)
ui <- fluidPage(
actionButton("getQueue", "Get list of queued files"),
verbatimTextOutput("devel"),
DT::dataTableOutput("fileList")
)
shinyServer <- function(input, output) {
tbl <- eventReactive(input$getQueue, {
mtcars
})
output$fileList <- DT::renderDataTable({
tbl()
}, selection = 'single')
output$devel <- renderPrint({
req(length(input$fileList_cell_clicked) > 0)
input$fileList_cell_clicked
})
}
shinyApp(ui = ui, server = shinyServer)
Related
The app below contains a selectInput with two options iris and mtcars and a header that displays the current selection.
If the user selects iris, a DT of the corresponding dataset is rendered below the header.
If the user selects mtcars, nothing is rendered below the header.
Here is a screenshot:
I store the selected dataset in a reactive expression, sel_df. The expression checks if the user has selected iris using req(input$dataset=='iris') before returning the corresponding dataset:
sel_df = reactive({
req(input$dataset=='iris')
iris
})
sel_df is passed to renderDT which renders the datatable:
output$df = renderDT({
sel_df()
})
I then render some UI to display the current value of the selectInput using an h3 header, the datatable and a label for the datatable:
output$tbl = renderUI({
tagList(
h3(paste0('Selected:', input$dataset)), # Header should be visible regardless of the value of input$dataset
tags$label(class = 'control-label', style = if(!isTruthy(isolate(sel_df()))) 'display:none;', `for` = 'df', 'Data:'), # Label should only show if input$dataset == 'iris'
DTOutput('df')
)
})
I would like the datatable and its label to only be visible if sel_df outputs a dataset. But due to the way the app is structured, this requires output$tbl (the renderUI above) to take a dependency on sel_df, so that the entire UI chunk disappears whenever input$dataset == 'mtcars'.
My desired output requires output$tbl to only take a dependency on input$dataset, so that the h3 header is always visible regardless of the value of input$dataset. To do this, I tried 'isolating' sel_df using isolate, but output$tbl still calls sel_df each time it's invalidated.
I am not sure where I am going wrong here. I think I may be using isolate incorrectly but I don't know why and was wondering if someone could shed some light.
Here is the app in full:
library(shiny)
library(DT)
ui <- fluidPage(
selectInput('dataset', 'Dataset', c('iris', 'mtcars')),
uiOutput('tbl')
)
server <- function(input, output, session) {
sel_df = reactive({
req(input$dataset=='iris')
iris
})
output$df = renderDT({
sel_df()
})
output$tbl = renderUI({
tagList(
h3(paste0('Selected:', input$dataset)), # Header should be visible regardless of the value of input$dataset
tags$label(class = 'control-label', style = if(!isTruthy(isolate(sel_df()))) 'display:none;', `for` = 'df', 'Data:'), # Label should only show if input$dataset == 'iris'
DTOutput('df')
)
})
}
shinyApp(ui, server)
output$tbl depends on input$dataset, so naturally it is called each time the value of input$dataset changes. sel_df() also depends on input$dataset and gets called whenever it changes. This is all how it is expected to be, I don't think your label is called because it depends on sel_df().
However, please note that when sel_df is NULL, the taglist() call will also return NULL. This is because your sel_df() call fails silently when input$dataset != "iris", and consequently tagList fails as well:
If any of the given values is not truthy, the operation is stopped by raising a
"silent" exception (not logged by Shiny, nor displayed in the Shiny app's UI).
Try this:
server <- function(input, output, session) {
sel_df = reactive({
if(input$dataset=='iris') {
iris
} else {
NULL
}
})
You will find that with mtcars, the h3() tag is shown, but the label is hidden as desired.
If you would like to use req in sel_df() you could use a trycatch in renderDT this addresses the problem mentioned by #January, of tagsList failing when you do not select iris.
You will also need to modify the if statement to use is.null rather, as I use this as the default return value in the trycatch.
library(shiny)
library(DT)
ui <- fluidPage(
selectInput('dataset', 'Dataset', c('iris', 'mtcars')),
uiOutput('tbl')
)
server <- function(input, output, session) {
sel_df = reactive({
req(input$dataset=='iris')
iris
})
output$df = renderDT({
out <- tryCatch(sel_df(), error = function(e) NULL)
return(out)
})
output$tbl = renderUI({
tagList(
tags$h3(paste0('Selected:', input$dataset)), # Header should be visible regardless of the value of input$dataset
tags$label(class = 'control-label', style = if(is.null('df')) 'display:none;', `for` = 'df', 'Data:'), # Label should only show if input$dataset == 'iris'
DTOutput('df')
)
})
}
shinyApp(ui, server)
I have a problem with my code. I have 2 input files which I want to read with click of button and a numeric input which contains a filter value for the output of the table being created from the 2 files (after manipulating the data). The whole process (read files + create table + filter) right now is executed every time the user click the button. I want to do only the filter action if the input files doesn't change, because the process takes long time.
After the first click I want to do only the filtering command when the numeric input changes, unless the input files is also changed by the user.
The following code reproduces my problem:
library(shiny)
library(data.table)
server <- function(input, output, session) {
output$table1 <- renderDataTable({
input$gobtn
isolate({
infile1 <<- input$f1
infile2 <<- input$f2
if (is.null(infile1) || is.null(infile1)) {
return (NULL)
}
else {
calc()
}
})
})
calc <- function() {
inf1 <<- fread(infile1$datapath)
inf2 <<- fread(infile2$datapath)
# do some process with files data.....
my_table <- as.data.table(rbind(inf1, inf2))
setnames(my_table, c('name', 'rank'))
result <- my_table[rank > input$rank]
return(result)
}
}
ui <- basicPage(
fileInput("f1", "f1"),
fileInput("f2", "f2"),
numericInput("rank", "show rank only above :", value = 6),
actionButton("gobtn", "show"),
dataTableOutput('table1')
)
shinyApp(ui = ui, server = server)
The way to use reactivity is to break things into parts, so that you only need to update what is necessary. The first step in your pipeline is reading and processing the files. This seems like a good reactive: if they don't change, nothing happens, but when they change, everything that needs to be recalculated is recalculated. The next step is filtering, when the filter variable changes we want to refilter the data. Then we can just put that in the output.
server <- function(input, output, session) {
processedData <- reactive({
req(input$f1,input$f2)
inf1 <- fread(input$f1$datapath)
inf2 <- fread(input$f2$datapath)
# do some process with files data.....
my_table <- as.data.table(rbind(inf1, inf2))
setnames(my_table, c('name', 'rank'))
my_table
}
filteredData <- reactive({
req(input$rank)
processedData()[processedData()$rank > input$rank]
})
output$table1 <- renderDataTable({
input$gobtn
isolate({
filteredData()
})
})
}
In my shiny app, I have a render that I want to only execute after a radio button changes values, but only the first time this happens. Is there a way to make it reactive to the first change in value, but not subsequent ones?
Below you will find that observeEvent has arguments such as ignoreInit and once, I would advise that you go and have a look at the function definitions on the official website Event handler. I have also added the shinyjs library with its disable function which I think is handy here.
rm(list=ls())
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
radioButtons("nums", "Will Execute only Once",c("1000" = 1000,"10000" = 10000), selected = 0),
plotOutput("Plot")
)
server <- function(input, output) {
v <- reactiveValues()
observeEvent(input$nums, {
v$data <- rnorm(input$nums)
},ignoreInit = TRUE, once = TRUE)
output$Plot <- renderPlot({
if(is.null(v$data)){
return()
}
disable('nums')
hist(v$data)
box()
})
}
shinyApp(ui, server)
I am struggling to understand how isolate() and reactive() should be used in R Shiny.
I want to achieve the following:
Whenever the "Refresh" action button is clicked:
Perform a subset on a data.frame and,
Feed this into my function to recalculate values.
The subset depends on a group of checkboxes that the user has ticked, of which there are approximately 40. I cannot have these checkboxes "fully reactive" because the function takes about 1.5 sec to execute. Instead, I want to give the user a chance to select multiple boxes and only afterwards click a button to (a) subset and (b) call the function again.
To do so, I load the data.frame in the server.R function:
df1 <- readRDS("D:/././df1.RData")
Then I have my main shinyServer function:
shinyServer(function(input, output) {
data_output <- reactive({
df1 <- df1[,df1$Students %in% input$students_selected]
#Here I want to isolate the "students_selected" so that this is only
#executed once the button is clicked
})
output$SAT <- renderTable({
myFunction(df1)
})
}
How about something like
data_output <- eventReactive(input$button, {
df1[,df1$Students %in% input$students_selected]
})
Here is my minimal example.
library(shiny)
ui <- list(sliderInput("num", "rowUpto", min= 1, max = 10, value = 5),
actionButton("btn", "update"),
tableOutput("tbl"))
server <- function(input, output) {
data_output <- eventReactive(input$btn, {
data.frame(id = 1:10, x = 11:20)[seq(input$num), ]
})
output$tbl <- renderTable({
data_output()})
}
runApp(list(ui = ui, server = server))
Edit
Another implementation, a bit more concise.
renderTable by default inspects the changes in all reactive elements within the function (in this case, input$num and input$button).
But, you want it to react only to the button. Hence you need to put the elements to be ignored within the isolate function.
If you omit the isolate function, then the table is updated as soon as the slider is moved.
library(shiny)
ui <- list(sliderInput("num", "rowUpto", min= 1, max = 10, value = 5),
actionButton("btn", "update"),
tableOutput("tbl"))
server <- function(input, output) {
output$tbl <- renderTable({
input$btn
data.frame(id = 1:10, x = 11:20)[seq(isolate(input$num)), ]
})
}
runApp(list(ui = ui, server = server))
Use eventReactive instead:
data_output <- eventReactive(input$updateButton, {
df1 <- df1[,df1$Students %in% input$students_selected] #I think your comments are messed up here, but I'll leave the filtering formatting to you
})
output$SAT <- renderTable({
data_output()
})
And in your UI you should have something like:
actionButton('updateButton',label = "Filter")
Looking at ?shiny::eventReactive:
Use eventReactive to create a calculated value that only updates in
response to an event. This is just like a normal reactive expression
except it ignores all the usual invalidations that come from its
reactive dependencies; it only invalidates in response to the given
event.
I have a shinyTable in a shiny app. It is editable, but because of a submitButton elsewhere in the app the edits are not saved until the button is pressed. If more than one change is made and the button is pressed only the last change is saved.
My question is how can I get it to save all the changes that have been made ?
Perhaps there is a way that I can get at the contents of the whole table in the UI so I can workaround ?
Or would I be better off using shinysky or something else ?
Below is a reproducible example based on an example from the package. You'll see that if you make 2 changes to the upper table and then press the button only the 2nd change gets copied to the lower table.
library(shiny)
library(shinyTable)
server <- function(input, output, session) {
rv <- reactiveValues(cachedTbl = NULL)
output$tbl <- renderHtable({
if (is.null(input$tbl)){
#fill table with 0
tbl <- matrix(0, nrow=3, ncol=3)
rv$cachedTbl <<- tbl
print(tbl)
return(tbl)
} else{
rv$cachedTbl <<- input$tbl
print(input$tbl)
return(input$tbl)
}
})
output$tblNonEdit <- renderTable({
rv$cachedTbl
})
}
ui <- shinyUI(pageWithSidebar(
headerPanel("Simple Shiny Table!"),
sidebarPanel(
helpText(HTML("A simple editable matrix with an update button.
Shows that only most recent change is saved.
<p>Created using shinyTable."))
),
# Show the simple table
mainPanel(
#editable table
htable("tbl"),
#update button
submitButton("apply table edits"),
#to show saved edits
tableOutput("tblNonEdit")
)
))
shinyApp(ui = ui, server = server)
Thanks for your time.
Andy
Following advice from Joe Cheng at RStudio on a related question, it appears that submitButton is not advised and can cause pain.
Switching to actionButton and isolate was relatively straightforward in this simple example and in my application.
Solution below.
library(shiny)
library(shinyTable)
server <- function(input, output, session) {
rv <- reactiveValues(cachedTbl = NULL)
output$tbl <- renderHtable({
if (is.null(input$tbl)){
#fill table with 0
tbl <- matrix(0, nrow=3, ncol=3)
rv$cachedTbl <<- tbl
return(tbl)
} else{
rv$cachedTbl <<- input$tbl
return(input$tbl)
}
})
output$tblNonEdit <- renderTable({
#add dependence on button
input$actionButtonID
#isolate the cached table so it only responds when the button is pressed
isolate({
rv$cachedTbl
})
})
}
ui <- shinyUI(pageWithSidebar(
headerPanel("shinyTable with actionButton to apply changes"),
sidebarPanel(
helpText(HTML("A simple editable matrix with a functioning update button.
Using actionButton not submitButton.
Make changes to the upper table, press the button and they will appear in the lower.
<p>Created using shinyTable."))
),
# Show the simple table
mainPanel(
#editable table
htable("tbl"),
#update button
actionButton("actionButtonID","apply table edits"),
#to show saved edits
tableOutput("tblNonEdit")
)
))
shinyApp(ui = ui, server = server)