Shiny: How to disable download button when there no data? - r

I'm working inside a module which queries some data and then shows it on a DT::datatable, I added a download button so I can download the data with the filters applied.
I already called useShinyjs() in the main ui file of the app.
But I want to disable the download button in case there is no data.
I've tried the following.
observeEvent(data(), {
if (!nrow(data()) > 0) {
shinyjs::disable("download")
} else {
shinyjs::enable("download")
})
However the next error message shows up, and the app crashes as soon as I run it.
Expecting a single string value: [type=character; extent=0]
ui Code:
module_ui <- function(id) {
ns <- NS(id)
tagList(
fluidRow(
tabBox(
title = tagList(
downloadButton(ns("download"), label = "Download data")
),
width = 12,
tabPanel(
title = HTML("Documentation"),
div(style = 'overflow-x: scroll;font-size:90%', DTOutput(ns("table")))
)
)
)
)
}
server Code:
module_server <- function(id,
connection,
update_button,
update_button_name) {
moduleServer(
id = id,
module = function(input, output, session) {
ns <- session$ns
# 1 . Data -----
data <- eventReactive(list(update_button()), {
data <- dbGetQuery(
connection,
glue::glue("SELECT * FROM Process;)
return(data)
}, ignoreNULL = FALSE, ignoreInit = FALSE)
# 2 . Table -----
output$table<- renderDT({
shiny::validate(
shiny::need(!is_null(data()) && nrow(data()) > 0, 'No data...')
)
datatable(
data = data(),
selection = "single",
style = "bootstrap",
rownames = FALSE,
filter = 'top',
options = list(
searchHighlight = TRUE,
dom = 'tipr',
pageLength = 20,
columnDefs = list(
list(visible = F, targets = c(0)),
list(width = "200px", targets = "_all")
)
)
)
}, server = TRUE)
# 3 . Download -----
observeEvent(data(), {
if (nrow(data()) > 0) {
shinyjs::enable("download")
} else {
shinyjs::disable("download")
}
})
output$download <- downloadHandler(
filename = "Documentation.xlsx",
content = function(file) {
openxlsx::write.xlsx(
x = data() %>% slice(input$tabla_rows_all),
file = file,
asTable = FALSE,
row.names = FALSE
)
}
)
Many thanks in advance to whoever can help!

Use shinyjs::toggleState() instead. Here is a reproducible example:
library(shiny)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
fileInput(
inputId = "file1", label = "Choose a file to upload:", accept = ".csv"
),
tableOutput(
outputId = "table1"
),
downloadButton(
outputId = "download_data", class = "btn-success"
)
)
server <- function(input, output, session) {
the_data <- reactive({
req(input$file1)
read.csv(input$file1$datapath)
})
output$table1 <- renderTable({
the_data() |> head()
})
# <-- observe if there's any input file -->
observe({
# mandatory condition: there should be an input file
mand_condition <- \() {
!is.null(input$file1)
}
shinyjs::toggleState(
id = "download_data", condition = mand_condition()
)
})
output$download_data <- downloadHandler(
filename = \() {
input$file1$name
},
content = function(file) {
write.csv(the_data(), file)
}
)
}
shinyApp(ui, server)

Related

Enabling download all with server = TRUE datatable by using invisible download button

The goal is to enable download all data from datatable even when server = TRUE. I'm very close already thanks to this post on Github.
This works:
library(shiny)
library(DT)
callback <- JS(
"var a = document.createElement('a');",
"$(a).addClass('dt-button');",
"a.href = document.getElementById('download1').href;",
"a.download = '';",
"$(a).attr('target', '_blank');",
"$(a).text('Download');",
"$('div.dwnld').append(a);",
"$('#download1').hide();"
)
ui <- basicPage(
downloadButton("download1", ""), # no label: this button will be hidden
numericInput("nrows", "Number of rows", 10),
DTOutput("dtable")
)
server <- function(input, output, session){
output$dtable <- renderDT(
datatable(iris[1:input$nrows,],
callback = callback,
extensions = 'Buttons',
options = list(
dom = 'B<"dwnld">frtip',
buttons = list(
"copy"
)
)
)
)
output$download1 <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(iris, file)
}
)
}
shinyApp(ui, server)
The problem is that the download button is briefly visible on app load. How do I ensure the download button is always invisible?
I tried using shinyjs::hidden(), but it causes the download to fail:
library(shiny)
library(shinyjs)
library(DT)
callback <- JS(
"var a = document.createElement('a');",
"$(a).addClass('dt-button');",
"a.href = document.getElementById('download1').href;",
"a.download = '';",
"$(a).attr('target', '_blank');",
"$(a).text('Download');",
"$('div.dwnld').append(a);",
"$('#download1').hide();"
)
ui <- basicPage(
useShinyjs(),
hidden(downloadButton("download1", "")), # no label: this button will be hidden
numericInput("nrows", "Number of rows", 10),
DTOutput("dtable")
)
server <- function(input, output, session){
output$dtable <- renderDT(
datatable(iris[1:input$nrows,],
callback = callback,
extensions = 'Buttons',
options = list(
dom = 'B<"dwnld">frtip',
buttons = list(
"copy"
)
)
)
)
output$download1 <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(iris, file)
}
)
}
shinyApp(ui, server)
EDIT
Using div(style = 'display: none;', ...) also results in a failed download.
library(shiny)
library(DT)
callback <- JS(
"var a = document.createElement('a');",
"$(a).addClass('dt-button');",
"a.href = document.getElementById('download1').href;",
"a.download = '';",
"$(a).attr('target', '_blank');",
"$(a).text('Download');",
"$('div.dwnld').append(a);",
"$('#download1').hide();"
)
ui <- basicPage(
div(style = "display: none;", downloadButton("download1", "")), # no label: this button will be hidden
numericInput("nrows", "Number of rows", 10),
DTOutput("dtable")
)
server <- function(input, output, session){
output$dtable <- renderDT(
datatable(iris[1:input$nrows,],
callback = callback,
extensions = 'Buttons',
options = list(
dom = 'B<"dwnld">frtip',
buttons = list(
"copy"
)
)
)
)
output$download1 <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(iris, file)
}
)
}
shinyApp(ui, server)
You can use the hidden style in the downloadButton as shown below.
library(shiny)
library(shinyjs)
library(DT)
callback <- JS(
"var a = document.createElement('a');",
"$(a).addClass('dt-button');",
"a.href = document.getElementById('download1').href;",
"a.download = '';",
"$(a).attr('target', '_blank');",
"$(a).text('Download');",
"$('div.dwnld').append(a);",
"$('#download1').hide();"
)
ui <- basicPage(
# useShinyjs(),
# hidden(downloadButton("download1", "")), # no label: this button will be hidden
downloadButton("download1", "", style = "visibility: hidden;"),
numericInput("nrows", "Number of rows", 10),
DTOutput("dtable")
)
server <- function(input, output, session){
output$dtable <- renderDT(
datatable(iris[1:input$nrows,],
callback = callback,
extensions = 'Buttons',
options = list(
dom = 'B<"dwnld">frtip',
buttons = list(
"copy"
)
)
)
)
output$download1 <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(iris, file)
}
)
}
shinyApp(ui, server)
Include the button in an invisible div :
ui <- basicPage(
div(
style = "display: none;",
downloadButton("download1", ""), # no label: this button will be hidden
),
numericInput("nrows", "Number of rows", 10),
DTOutput("dtable")
)

How to create a button that will create a pdf file of a table

I currently have a table being generated and I would like the user to be able to create a pdf file when they click the download button.
I am currently getting an error where when I click the download button I get an html file that downloads the entire page of the app. I thought that using pdf(file) would work but it ignores the function.
Here is currently what I have.
library(shiny)
library(xlsx)
library(shinyWidgets)
population <- read.xlsx("population.xlsx", 1)
fieldsMandatory <- c("selectedCountry")
labelMandatory <- function(label) {
tagList(
label,
span("*", class = "mandatory_star")
)
}
appCSS <-
".mandatory_star {color: red;}"
ui <- fluidPage(
navbarPage(title = span("Spatial Tracking of COVID-19 using Mathematical Models", style = "color:#000000; font-weight:bold; font-size:15pt"),
tabPanel(title = "Model",
sidebarLayout(
sidebarPanel(
shinyjs::useShinyjs(),
shinyjs::inlineCSS(appCSS),
div(
id = "dashboard",
pickerInput(
inputId = "selectedCountry",
labelMandatory ("Country"),
choices = population$Country,
multiple = FALSE,
options = pickerOptions(
actionsBox = TRUE,
title = "Please select a country")
),
sliderInput(inputId = "agg",
label = "Aggregation Factor",
min = 0, max = 50, step = 5, value = 10),
actionButton("go","Run Simulation"),
)
),
mainPanel(
tabsetPanel(
tabPanel("Input Summary", verbatimTextOutput("summary"),
tableOutput("table"),
downloadButton(outputId = "downloadSummary", label = "Save Summary"))
)
)
)
)
)
)
server <- function(input, output, session){
observeEvent(input$resetAll, {
shinyjs::reset("dashboard")
})
values <- reactiveValues()
values$df <- data.frame(Variable = character(), Value = character())
observeEvent(input$go, {
row1 <- data.frame(Variable = "Country", Value = input$selectedCountry)
row2 <- data.frame(Variable = "Aggregation Factor", Value = input$agg)
values$df <- rbind(row1, row2)
})
output$table <- renderTable(values$df)
observe({
# check if all mandatory fields have a value
mandatoryFilled <-
vapply(fieldsMandatory,
function(x) {
!is.null(input[[x]]) && input[[x]] != ""
},
logical(1))
mandatoryFilled <- all(mandatoryFilled)
# enable/disable the submit button
shinyjs::toggleState(id = "go", condition = mandatoryFilled)
})
output$downloadSummary <- downloadHandler(
filename = function(file) {
paste('my-report.pdf', )
},
content = function(file) {
pdf(file)
}
)
}
shinyApp(ui,server)
Here's a minimal example:
library(shiny)
ui <- fluidPage(
downloadButton("savepdf", "Save pdf")
)
server <- function(input, output, session) {
output$savepdf <- downloadHandler(
filename = "test.pdf",
content = function(file) {
pdf(file)
plot(iris$Sepal.Length, iris$Sepal.Width)
dev.off()
}
)
}
shinyApp(ui, server)
Also see here.
Here is a minimal example with the package latexpdf. It will create the pdf table in the folder of the app.
library(shiny)
library(latexpdf)
dat <- head(iris, 5)
ui <- fluidPage(
br(),
actionButton("dwnld", "Create pdf"),
tableOutput("mytable")
)
server <- function(input, output, session){
output[["mytable"]] <- renderTable({
dat
})
observeEvent(input[["dwnld"]], {
as.pdf(dat)
})
}
shinyApp(ui, server)

Adding buttons to Shiny DT to pull up modal

I am trying to add a column of buttons in my datatable that when clicked will pull up a modal but I am having trouble using the examples I found online here and here.
Some of my requirements:
Needs to work with an unknown number of rows in the dataset (could be 5, could be 10, could be 500)
Each button needs to be unique id which I can use to reference the row (in the example you can see I am pulling in the row number into the modal - real life I am using the row number to subset my data and actually put information in the modal)
Code:
library(shiny)
library(shinydashboard)
library(DT)
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
DTOutput('x1'),
verbatimTextOutput("test")
)
)
server = function(input, output) {
##DATA TABLE WHERE I NEED A BUTTON##
output$x1 = renderDT(
iris,
selection = 'single',
options = list(
)
)
##MODAL CALLED BASED ON BUTTON CLICK
observeEvent(input$x1_cell_clicked, {
row = input$x1_cell_clicked$row
if (is.null(row) || row == '') {} else{
showModal(modalDialog(
title = paste0("Timeline!",row),
size = "s",
easyClose = TRUE,
footer = NULL
))
}
})
output$test <- renderPrint({
input$x1_cell_clicked$row
})
}
shinyApp(ui, server)
In your comment, you asked for the case of multiple datatables. Is it what you want ?
library(shiny)
library(DT)
button <- function(tbl){
function(i){
sprintf(
'<button id="button_%s_%d" type="button" onclick="%s">Click me</button>',
tbl, i, "Shiny.setInputValue('button', this.id);")
}
}
dat1 <- cbind(iris,
button = sapply(1:nrow(iris), button("tbl1")),
stringsAsFactors = FALSE)
dat2 <- cbind(mtcars,
button = sapply(1:nrow(mtcars), button("tbl2")),
stringsAsFactors = FALSE)
ui <- fluidPage(
fluidRow(
column(
width = 6,
DTOutput("tbl1", height = "500px")
),
column(
width = 6,
DTOutput("tbl2", height = "500px")
)
)
)
server <- function(input, output){
output[["tbl1"]] <- renderDT({
datatable(dat1, escape = ncol(dat1)-1, fillContainer = TRUE)
})
output[["tbl2"]] <- renderDT({
datatable(dat2, escape = ncol(dat2)-1, fillContainer = TRUE)
})
observeEvent(input[["button"]], {
splitID <- strsplit(input[["button"]], "_")[[1]]
tbl <- splitID[2]
row <- splitID[3]
showModal(modalDialog(
title = paste0("Row ", row, " of table ", tbl, " clicked"),
size = "s",
easyClose = TRUE,
footer = NULL
))
})
}
shinyApp(ui, server)
Was able to figure it out using this.
library(shiny)
library(shinydashboard)
library(DT)
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
DTOutput('x1'),
verbatimTextOutput("test")
)
)
server = function(input, output) {
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
iris_rows <- nrow(iris)
iris$Timeline = shinyInput(actionButton, iris_rows, 'button_', label = "Timeline", onclick = 'Shiny.setInputValue(\"select_button\", this.id, {priority: \"event\"})' )
##DATA TABLE WHERE I NEED A BUTTON##
output$x1 = renderDT(
iris,
selection = 'single',
escape = FALSE,
options = list(
)
)
##MODAL CALLED BASED ON BUTTON CLICK
observeEvent(input$select_button, {
row <- as.numeric(strsplit(input$select_button, "_")[[1]][2])
if (is.null(row) || row == '') {} else{
showModal(modalDialog(
title = paste0("Timeline!",row),
size = "s",
easyClose = TRUE,
footer = NULL
))
}
})
output$test <- renderPrint({
as.numeric(strsplit(input$select_button, "_")[[1]][2])
})
}
shinyApp(ui, server)
Code with multiple data tables to show a separate answer than the one chosen.
library(shiny)
library(shinydashboard)
library(DT)
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
DTOutput('x1'),
DTOutput('x2'),
verbatimTextOutput("test")
)
)
server = function(input, output) {
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
iris2 <- iris
iris_rows <- nrow(iris)
iris$Timeline = shinyInput(actionButton, iris_rows, 'button_x1_', label = "Timeline", onclick = 'Shiny.setInputValue(\"select_button1\", this.id, {priority: \"event\"})' )
iris2_rows <- nrow(iris2)
iris2$Timeline = shinyInput(actionButton, iris2_rows, 'button_x2_', label = "Timeline", onclick = 'Shiny.setInputValue(\"select_button2\", this.id, {priority: \"event\"})' )
##DATA TABLE WHERE I NEED A BUTTON##
output$x1 = renderDT(
iris,
selection = 'single',
escape = FALSE,
options = list(
)
)
output$x2 = renderDT(
iris2,
selection = 'single',
escape = FALSE,
options = list(
)
)
##MODAL CALLED BASED ON BUTTON CLICK
observeEvent(input$select_button1, {
row <- as.numeric(strsplit(input$select_button1, "_")[[1]][3])
if (is.null(row) || row == '') {} else{
showModal(modalDialog(
title = paste0("Timeline!",row),
size = "s",
easyClose = TRUE,
footer = NULL
))
}
})
observeEvent(input$select_button2, {
row <- as.numeric(strsplit(input$select_button2, "_")[[1]][3])
if (is.null(row) || row == '') {} else{
showModal(modalDialog(
title = paste0("Timeline!",row),
size = "s",
easyClose = TRUE,
footer = NULL
))
}
})
output$test <- renderPrint({
as.numeric(strsplit(input$select_button1,"_")[[1]][3])
})
}
shinyApp(ui, server)

Upload a csv file with actionbutton and display a corrplot

I tried to make a web application with R::shiny but I met a problem with a piece of code. Indeed, I would like to upload a csv file and display a correlogram.
I tried to set up the correlogram with the actionbutton() followed by the updateSelectizeInput()
However an error has been occured :
Error: Unsupported index type: NULL
Anybody have a solution ? thanks
NB - I don't want to use the fileInput widget to upload the csv file ! Only by the actionbutton !
library(shiny)
library(readr)
library(corrplot)
library(DT)
# File used for the example
data(iris)
write.csv(x = iris, file = "iris.csv")
#UI
ui <- shinyUI(
fluidPage(
navbarPage(
id = "navbar",
tabPanel(
title = "UPLOAD",
br(),
actionButton(inputId = "file", label = "ADD A FILE")
)
)
)
)
#SERVER
server <- function(input, output, session) {
path <- reactiveValues(pth = NULL)
file.choose2 <- function(...) {
pathname <- NULL;
tryCatch({
pathname <- file.choose();
}, error = function(ex) {
})
pathname;
}
observeEvent(input$file,{
path$pth <- file.choose2()
})
observeEvent(input$file, {
newvalue <- "B"
updateNavbarPage(session, "navbar", newvalue)
})
data <- reactive({
df <- readr::read_csv(file = path$pth)
return(df)
})
observeEvent(input$file, {
appendTab(
inputId = "navbar",
tabPanel(
value = "B",
title = "Corr",
sidebarLayout(
sidebarPanel(
selectizeInput(
inputId = "select04",
label = "Select features",
choices = NULL,
multiple = TRUE)
),
mainPanel(
plotOutput(
outputId = "corrplot01", height = "650px")
)
)
)
)
}, once = TRUE)
# I suppose there is a problem with this line
observeEvent(input$select04, {
col <- names(data())
col.num <- which(sapply(data(), class) == "numeric")
col <- col[col.num]
updateSelectizeInput(session = session, inputId = "select04", choices = col)
})
output$corrplot01 <- renderPlot({
df <- data()
df1 <- df[,input$select04]
corr <- cor(x = df1, use = "pairwise.complete.obs")
corrplot(corr = corr,
title = "")
})
}
shinyApp(ui, server)
I changed your ui and server a bit, but I think that might solve your problem.
I deleted the observeEvent(input$file, ...{}) from the server and added the ui part in the Ui directly.
I also added 3 req() calls in the data reactive, in the second observeEvent(input$select04, ...{}) which I changed to a normal observe and in the renderPlot call.
library(shiny)
library(readr)
library(corrplot)
library(DT)
# File used for the example
data(iris)
write.csv(x = iris, file = "iris.csv", row.names = F)
#UI
ui <- shinyUI(
fluidPage(
navbarPage(
id = "navbar",
tabPanel(
title = "UPLOAD",
br(),
actionButton(inputId = "file", label = "ADD A FILE"),
tabPanel(
value = "B",
title = "Corr",
sidebarLayout(
sidebarPanel(
selectizeInput(width = "300px",
inputId = "select04",
label = "Select features",
choices = NULL,
multiple = TRUE)
),
mainPanel(
plotOutput(
outputId = "corrplot01", height = "650px")
)
)
)
)
)
)
)
#SERVER
server <- function(input, output, session) {
path <- reactiveValues(pth = NULL)
file.choose2 <- function(...) {
pathname <- NULL;
tryCatch({
pathname <- file.choose();
}, error = function(ex) {
})
pathname;
}
observeEvent(input$file,{
path$pth <- file.choose2()
})
observeEvent(input$file, {
newvalue <- "B"
updateNavbarPage(session, "navbar", newvalue)
})
data <- reactive({
req(path$pth)
df <- readr::read_csv(file = path$pth)
return(df)
})
# I suppose there is a problem with this line
observe({
req(names(data()))
col <- names(data())
col.num <- which(sapply(data(), class) == "numeric")
col <- col[col.num]
updateSelectizeInput(session = session, inputId = "select04", choices = col)
})
output$corrplot01 <- renderPlot({
req(input$select04)
df <- data()
df1 <- df[,input$select04]
corr <- cor(x = df1, use = "pairwise.complete.obs")
corrplot(corr = corr,
title = "")
})
}
shinyApp(ui, server)

Print a number based on the selection or not of datatable row in a shiny app

I have a shiny app in which I have a table with 10 rows. What I want to achieve is to download a .txt file with a line of 0 and 1 based on the simple logic-if I choose the row I get 1 and if not I get O-. An example of all the rows selected would be:
#ui.r
library(shiny)
library(DT)
library(tidyverse)
navbarPage(
"Application",
tabPanel("General",
sidebarLayout(
sidebarPanel(
downloadButton("downloadData2", "Download")
),
mainPanel(
DT::dataTableOutput("hot3")
)
)))
#server.r
server <- function(input, output,session) {
hott<-reactive({
if(is.null(input$hot5_rows_selected)|| is.na(input$hot5_rows_selected)){
paste("0",collapse = " ")
}
else{
paste("1",collapse = " ")
}
})
output$downloadData2 <- downloadHandler(
filename = function(){
paste(input$file, ".txt", sep = "")
},
content = function(file) {
writeLines(paste(hott()
), file)
}
)
rt5<-reactive({
DF=data.frame(
Id= 1:10,
stringsAsFactors = FALSE
)
})
output$hot3 <-DT::renderDataTable(
rt5()%>%
rowid_to_column("Row") %>%
mutate(Row = ""),
rownames = FALSE,
extensions = "Select",
options = list(
columnDefs = list(list(className = "select-checkbox", targets = 0, orderable = FALSE)),
select = list(style = "multi", selector = "td:first-child")
)
)
}
The only solution I can see is:
library(shiny)
library(DT)
dat <- data.frame(X = LETTERS[1:10])
ui <- fluidPage(DTOutput("dt"))
server <- function(input, output){
rowSelected <- reactive({
x <- numeric(nrow(dat))
x[input$dt_rows_selected] <- 1
x
})
output$dt <- renderDT(datatable(cbind(id=rowSelected(), dat),
selection = list(mode = "multiple",
selected = (1:nrow(dat))[as.logical(rowSelected())],
target = "row")))
}
shinyApp(ui, server)

Resources