How to copy a plot into the clipboard for pasting? - r

In running the below reproducible code, the user can select to view either the actual data or a plot of the data via a click of the radio button at the top of the rendered Shiny screen (as coded it defaults to data). At the bottom of the rendered screen you'll see a "Copy" button. By selecting "Data" and then "Copy", you can easily paste the data into XLS.
However, if the user instead selects to view the plot, I'd like the user to also be able to copy/paste the plot in the same manner. How can this be done?
I've tried inserting plotPNG(...) inside the capture.output(...) function (and various iterations thereof) in the below observeEvent(...), using conditionals triggered by a conditional if input$view == 'Plot', but with no luck yet.
library(shiny)
library(ggplot2)
ui <- fluidPage(
radioButtons("view",
label = "View data or plot",
choiceNames = c('Data','Plot'),
choiceValues = c('Data','Plot'),
selected = 'Data',
inline = TRUE
),
conditionalPanel("input.view == 'Data'",tableOutput("DF")),
conditionalPanel("input.view == 'Plot'",plotOutput("plotDF")),
actionButton("copy","Copy",style = "width:20%;")
)
server <- function(input, output, session) {
data <- data.frame(Period = c(1,2,3,4,5,6),Value = c(10,20,15,40,35,30))
output$DF <- renderTable(data)
output$plotDF <- renderPlot(ggplot(data, aes(Period,Value)) + geom_line())
observeEvent(
req(input$copy),
writeLines(
capture.output(
write.table(
x = data,
sep = "\t",
row.names = FALSE
)
),
"clipboard")
)
}
shinyApp(ui, server)

Tested on Edge.
library(shiny)
library(ggplot2)
js <- '
async function getImageBlobFromUrl(url) {
const fetchedImageData = await fetch(url);
const blob = await fetchedImageData.blob();
return blob;
}
$(document).ready(function () {
$("#copybtn").on("click", async () => {
const src = $("#plotDF>img").attr("src");
try {
const blob = await getImageBlobFromUrl(src);
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
alert("Image copied to clipboard!");
} catch (err) {
console.error(err.name, err.message);
alert("There was an error while copying image to clipboard :/");
}
});
});
'
ui <- fluidPage(
tags$head(
tags$script(HTML(js))
),
br(),
actionButton("copybtn", "Copy", icon = icon("copy"), class = "btn-primary"),
br(),
plotOutput("plotDF")
)
server <- function(input, output, session){
output[["plotDF"]] <- renderPlot({
ggplot(
iris, aes(x = Sepal.Length, y = Sepal.Width)
) + geom_point()
})
}
shinyApp(ui, server)
EDIT
Alerts are not nice. I suggest shinyToastify instead.
library(shiny)
library(shinyToastify)
library(ggplot2)
js <- '
async function getImageBlobFromUrl(url) {
const fetchedImageData = await fetch(url);
const blob = await fetchedImageData.blob();
return blob;
}
$(document).ready(function () {
$("#copybtn").on("click", async () => {
const src = $("#plotDF>img").attr("src");
try {
const blob = await getImageBlobFromUrl(src);
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
Shiny.setInputValue("success", true, {priority: "event"});
} catch (err) {
console.error(err.name, err.message);
Shiny.setInputValue("failure", true, {priority: "event"});
}
});
});
'
ui <- fluidPage(
tags$head(
tags$script(HTML(js))
),
useShinyToastify(),
br(),
actionButton("copybtn", "Copy", icon = icon("copy"), class = "btn-primary"),
br(),
plotOutput("plotDF")
)
server <- function(input, output, session){
output[["plotDF"]] <- renderPlot({
ggplot(
iris, aes(x = Sepal.Length, y = Sepal.Width)
) + geom_point()
})
observeEvent(input[["success"]], {
showToast(
session,
input,
text = tags$span(
style = "color: white; font-size: 20px;", "Image copied!"
),
type = "success",
position = "top-center",
autoClose = 3000,
pauseOnFocusLoss = FALSE,
draggable = FALSE,
style = list(
border = "4px solid crimson",
boxShadow = "rgba(0, 0, 0, 0.56) 0px 22px 30px 4px"
)
)
})
observeEvent(input[["failure"]], {
showToast(
session,
input,
text = tags$span(
style = "color: white; font-size: 20px;", "Failed to copy image!"
),
type = "error",
position = "top-center",
autoClose = 3000,
pauseOnFocusLoss = FALSE,
draggable = FALSE,
style = list(
border = "4px solid crimson",
boxShadow = "rgba(0, 0, 0, 0.56) 0px 22px 30px 4px"
)
)
})
}
shinyApp(ui, server)

You may try shinyscreenshot: You can further tweak it https://daattali.com/shiny/shinyscreenshot-demo/
Here is an example:
library(shiny)
library(ggplot2)
library(shinyscreenshot)
ui <- fluidPage(
radioButtons("view",
label = "View data or plot",
choiceNames = c('Data','Plot'),
choiceValues = c('Data','Plot'),
selected = 'Data',
inline = TRUE
),
div(
id = "takemyscreenshot",
conditionalPanel("input.view == 'Data'",tableOutput("DF")),
conditionalPanel("input.view == 'Plot'",plotOutput("plotDF")),
actionButton("go","Go",style = "width:20%;")
)
)
server <- function(input, output, session) {
observeEvent(input$go, {
screenshot(id = "takemyscreenshot")
})
data <- data.frame(Period = c(1,2,3,4,5,6),Value = c(10,20,15,40,35,30))
output$DF <- renderTable(data)
output$plotDF <- renderPlot(ggplot(data, aes(Period,Value)) + geom_line())
observeEvent(
req(input$copy),
writeLines(
capture.output(
write.table(
x = data,
sep = "\t",
row.names = FALSE
)
),
"clipboard")
)
}
shinyApp(ui, server)

Related

Shiny DT datatable selectInput with reactive data

I recently asked a similar question (Shiny DT datatable input reactivity after table is reloaded). My issue was getting a selectInput in a DT datatable to work correctly after the table is reloaded. The solution worked, which was to use javascript to unbind before reloading the table. However, that example used a static dataframe. When the input data in the datatable are reactive, it doesn't work. In the example below, when the user clicks "Update data" the first time to load data, the selectInput works correctly and input$id1 responds to the user selection. However, when the user clicks "Update data" again to update the reactive data, the input$id no longer responds to the user selection. I've seen two potential approaches to address the issue. One is using dataTableProxy() and replaceData(), and the other is renaming the selectInput ids each time the reactive data are updated. I was wondering if I can avoid those two approaches and get this example working with minimal changes.
require(shiny)
require(DT)
shinyApp(
ui = fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
actionButton(inputId = "update", label = "Update data"),
uiOutput("resettable_table")
),
server = function(input, output, session) {
rv <- reactiveValues(
times = 1,
mydata = NULL
)
observeEvent(input$update, {
session$sendCustomMessage("unbindDT", "mytable")
rv$times <- rv$times + 1
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
preDrawCallback = JS('function(){Shiny.unbindAll(this.api().table().node());}'),
drawCallback = JS('function(){Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
output$resettable_table <- renderUI({
req(rv$times)
div(
id = paste0("mydiv", rv$times),
DT::dataTableOutput("mytable")
)
})
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)
Update
Thanks to #StephaneLaurent for pointing out that the reactive counter keeping track of the number of times the data were reloaded was causing the issue. It wasn't actually necessary to put the DT datatable inside a div with an id that updated each time. Here is working code:
require(shiny)
require(DT)
shinyApp(
ui = fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
actionButton(inputId = "update", label = "Update data"),
DT::dataTableOutput("mytable")
),
server = function(input, output, session) {
rv <- reactiveValues(mydata = NULL)
observeEvent(input$update, {
session$sendCustomMessage("unbindDT", "mytable")
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
preDrawCallback = JS('function(){Shiny.unbindAll(this.api().table().node());}'),
drawCallback = JS('function(){Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)
The problem is caused by the presence of rv$times in the renderUI. The simplest way to make this app work is to get rid of this renderUI.
However, for fun, and in order to understand what happens, I did the app below which works with the renderUI and which shows what happens. The key point was to remove the id1 element when the table is consecutively rendered two times, before the second rendering. To do so, I use a JavaScript counter i.
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
tags$head(tags$script(
HTML("var i = 1;")
)),
actionButton(inputId = "update", label = "Update data"),
uiOutput("resettable_table")
),
server = function(input, output, session) {
rv <- reactiveValues(
times = 1,
mydata = NULL
)
observeEvent(input$update, {
rv$times <- rv$times + 1
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
initComplete = JS('function(settings) { alert("initComplete - incrementing i"); i++; alert("i = " + i)}'),
preDrawCallback = JS('function() { alert("preDrawCallback triggered - unbinding"); Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { alert("drawCallback triggered - i = " + i); if(i===2) {alert("removing id1 and resetting i to 0"); $("#id1").remove(); i=0;} Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
output$resettable_table <- renderUI({
div(
id = paste0("mydiv", rv$times),
tags$p(paste0("mydiv", rv$times)),
DT::dataTableOutput("mytable")
)
})
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)

Stop 'downloadHandler` from executing in an ifelse statement

I am creating an app where the users can download the plot displayed. I want the user to be able to click download and ShinyAlert will pop up to ask for filename as the input.
So far the file gets downloaded if I press on save. However, when the CancelButton is clicked, shiny tries to download the page's HTML, with a "Failed - Server problem".
I placed an if function inside downloadHandler so that when input$shinyalert != FALSE the downloadHandler will execute the code, but I couldn't find the fault in my code. Any help will be appreciated, thanks.
Here is the code:
UI:
ui <- fluidPage(
useShinyalert(),
plotOutput("vmgraph"),
actionButton("downloadPlot", "Download Plot")
useShinyjs(),
## hide the downloadButton and only display actionButton
conditionalPanel(
"false",
downloadButton("downloadData")
)
)
Server:
observeEvent(input$downloadPlot, {
shinyalert("Save as:",
type = "input",
size = "m",
closeOnEsc = TRUE,
closeOnClickOutside = TRUE,
showConfirmButton = TRUE,
showCancelButton = TRUE,
confirmButtonText = "Save",
confirmButtonCol = "#0075B8",
animation = TRUE)
})
output$downloadData <- downloadHandler(
filename = function() {
if (input$shinyalert != FALSE) {
paste(input$shinyalert, ".png", sep = "")}},
content = function(file) {
if (input$shinyalert != FALSE) {
ggsave(file, plot = vmgraph(), width = 12, height = 7.7)
}})
observeEvent(input$shinyalert, {
## Click on the downloadButton when input$shinyalert is updated
shinyjs::runjs("$('#downloadData')[0].click();")
})
Perform your check for the cancel button before you get into the downloadHandler:
library(shiny)
library(shinyjs)
library(shinyalert)
library(ggplot2)
gg <- ggplot(mtcars, aes(mpg)) +
geom_boxplot()
ui <- fluidPage(
useShinyalert(),
plotOutput("vmgraph"),
actionButton("downloadPlot", "Download Plot"),
useShinyjs(),
## hide the downloadButton and only display actionButton
conditionalPanel(
"false",
downloadButton("downloadData")
)
)
server <- function(input, output) {
observeEvent(input$downloadPlot, {
shinyalert("Save as:",
type = "input",
size = "m",
closeOnEsc = TRUE,
closeOnClickOutside = TRUE,
showConfirmButton = TRUE,
showCancelButton = TRUE,
confirmButtonText = "Save",
confirmButtonCol = "#0075B8",
animation = TRUE
)
})
output$downloadData <- downloadHandler(
# do not perform check here
filename = function() {
paste(input$shinyalert, ".png", sep = "")
},
content = function(file) {
ggsave(file, plot = gg, width = 12, height = 7.7)
}
)
observeEvent(input$shinyalert, {
## Click on the downloadButton when input$shinyalert is updated
# perform the check here
if (input$shinyalert != FALSE) {
shinyjs::runjs("$('#downloadData')[0].click();")
}
})
}
shinyApp(ui = ui, server = server)

How to force inputs to take on the same values across modules

I'm working on a shiny dashboard that makes heavy use of shiny modules and my client has asked me to make it so that the same two inputs from my dashboard's various tabs take on the same values regardless of tab. I'm having a huge problem doing this and was able to recreate it using a toy example that you'll find below.
#app.R
library(data.table)
library(shiny)
library(ggplot2)
library(ggthemes)
library(shinythemes)
source("Modules.R")
penguins <<- as.data.table(palmerpenguins::penguins)
ui = uiOutput("ui")
inputs <<- reactiveValues(species = NULL, island = NULL)
server <- function(input, output, session) {
bill_species_server("tab1")
flipper_mass_scatter_server("tab2")
output$ui = renderUI({
fluidPage(
titlePanel("", "Penguin Dashboard"),
tabsetPanel(
tabPanel("Bill Length by Species",
ui_code("tab1")
),
tabPanel("Flipper Length by Body Mass",
ui_code("tab2")
)
)
)
})
}
# Run the application
shinyApp(ui = ui, server = server)
#Modules.R
ui_code = function (id) {
ns = NS(id)
sidebarLayout(position = "left",
sidebarPanel(
selectInput(ns("species"), "Choose 1+ species:", choices = penguins[, sort(unique(species))], multiple = TRUE),
selectInput(ns("island"), "Choose 1+ islands:", choices = penguins[, sort(unique(island))], multiple = TRUE)
),
mainPanel(
plotOutput(ns("plot"))
)
)
}
bill_species_server = function(id) {
moduleServer(id, function(input, output, session) {
observeEvent(inputs$species, ignoreInit = TRUE, ignoreNULL = TRUE, {
if (length(inputs$species) > 0) {
updateSelectInput(session = session, inputId = "species", selected = inputs$species)
}
})
observeEvent(inputs$island, ignoreInit = TRUE, ignoreNULL = TRUE, {
if (length(inputs$island) > 0) {
updateSelectInput(session = session, inputId = "island", selected = inputs$island)
}
})
output$plot = renderPlot({
if (length(input$species) > 0) {
penguins = penguins[species %in% input$species]
}
if (length(input$island) > 0) {
penguins = penguins[island %in% input$island]
}
ggplot(penguins) + geom_histogram(aes(x = `bill_length_mm`, fill = species)) + scale_fill_canva(palette = "Striking and energetic")
})
observeEvent(input$species, ignoreNULL = TRUE, ignoreInit = TRUE, {
inputs$species = input$species
})
observeEvent(input$island, ignoreNULL = TRUE, ignoreInit = TRUE, {
inputs$island = input$island
})
})
return(inputs)
}
flipper_mass_scatter_server = function (id) {
moduleServer(id, function(input, output, session) {
observeEvent(inputs$species, ignoreInit = TRUE, ignoreNULL = TRUE, {
if (length(inputs$species) > 0) {
updateSelectInput(session = session, inputId = "species", selected = inputs$species)
}
})
observeEvent(inputs$island, ignoreInit = TRUE, ignoreNULL = TRUE, {
if (length(inputs$island) > 0) {
updateSelectInput(session = session, inputId = "island", selected = inputs$island)
}
})
output$plot = renderPlot({
if (length(input$species) > 0) {
penguins = penguins[species %in% input$species]
}
if (length(input$island) > 0) {
penguins = penguins[island %in% input$island]
}
ggplot(penguins) + geom_point(aes(x = `flipper_length_mm`, y = body_mass_g, colour = species)) + scale_colour_canva(palette = "Striking and energetic")
})
observeEvent(input$species, ignoreNULL = TRUE, ignoreInit = TRUE, {
inputs$species = input$species
})
observeEvent(input$island, ignoreNULL = TRUE, ignoreInit = TRUE, {
inputs$island = input$island
})
})
return(inputs)
}
So the two inputs that I'm trying to link in this toy example are species and island. I've set it up so that when someone makes a new selection on either input, an observer should update a global variable which in this case I've labelled inputs. And then if inputs is updated, the other tab should then update its own selectInput.
Weirdly, I find that with this code, if I make my selections kind of slowly, all works just fine! However, the moment that I click 2+ choices in rapid succession, it causes an infinite loop to happen in the current tab where the second choice appears, then disappears, then appears... etc. Conversely, when I have 3 choices selected and try to delete options in rapid succession, it just doesn't let me delete all choices!!
So weird.
Anyone know what the problem is with my code, and how I can force the inputs in both tabs to keep the same values as chosen in the other tabs?
Thanks!
I significantly restructured how I approached this problem and came up with a solution. Basically, I used shinydashboard and decided that I would define the species and island selectInput controls outside of my modules.
The values to those controls were then passed to the modules as reactive objects that were then used to filter the data before the data got plotted. This works so much better now! Have a look at my two files:
#app.R
library(data.table)
library(shiny)
library(ggplot2)
library(ggthemes)
library(shinythemes)
library(shinydashboard)
source("Modules.R")
penguins <<- as.data.table(palmerpenguins::penguins)
ui = dashboardPage(header = dashboardHeader(title = "Penguin Dashboard"),
sidebar = dashboardSidebar(
sidebarMenu(id = "tabs",
selectInput("species", "Choose 1+ species:", choices = penguins[, sort(unique(species))], multiple = TRUE),
selectInput("island", "Choose 1+ islands:", choices = penguins[, sort(unique(island))], multiple = TRUE),
menuItem("Bill Length by Species", expandedName = "tab1", tabName = "tab1", startExpanded = TRUE,
sliderInput("mass", "Select a range of body masses:",
min = penguins[, min(body_mass_g, na.rm=TRUE)],
max = penguins[, max(body_mass_g, na.rm=TRUE)],
value = penguins[, range(body_mass_g, na.rm=TRUE)])
),
menuItem("Flipper Length by Body Mass", expandedName = "tab2", tabName = "tab2",
checkboxGroupInput("sex", "Choose sex of penguins:",
choices = c("male","female")))
)),
body = dashboardBody(
uiOutput("plots")
)
)
#inputs <<- reactiveValues(species = NULL, island = NULL)
server <- function(input, output, session) {
#inputs <- reactiveValues(species=input$species, island=input$island)
in_species = reactive({input$species})
in_island = reactive({input$island})
in_mass = reactive({input$mass})
in_sex = reactive({input$sex})
bill_species_server("tab1", in_species, in_island, in_mass)
flipper_mass_scatter_server("tab2", in_species, in_island, in_sex)
output$plots = renderUI({
validate(need(!is.null(input$sidebarItemExpanded), ""))
if (input$sidebarItemExpanded == "tab1") {
ui_code("tab1")
} else {
ui_code("tab2")
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
#Modules.R
ui_code = function (id) {
ns = NS(id)
plotOutput(ns("plot"))
}
bill_species_server = function(id, in_species, in_island, in_mass) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
output$plot = renderPlot({
if (length(in_species()) > 0) {
penguins = penguins[species %in% in_species()]
}
if (length(in_island()) > 0) {
penguins = penguins[island %in% in_island()]
}
penguins = penguins[body_mass_g %between% c(in_mass()[1], in_mass()[2])]
ggplot(penguins) + geom_histogram(aes(x = `bill_length_mm`, fill = species)) + scale_fill_canva(palette = "Striking and energetic")
})
})
}
flipper_mass_scatter_server = function (id, in_species, in_island, in_sex) {
moduleServer(id, function(input, output, session) {
output$plot = renderPlot({
if (length(in_species()) > 0) {
penguins = penguins[species %in% in_species()]
}
if (length(in_island()) > 0) {
penguins = penguins[island %in% in_island()]
}
if (length(in_sex()) > 0) {
penguins = penguins[sex %in% in_sex()]
}
ggplot(penguins) + geom_point(aes(x = `flipper_length_mm`, y = body_mass_g, colour = species)) + scale_colour_canva(palette = "Striking and energetic")
})
})
}

ReactiveValues trigger different observeEvents not working

Problem: I have the following app. Essentially, I want to press the button to load the data. After the first time I load the data via button press I want to get ask if I want to save my changes. If yes, confirmation that changes were successfully saved, else show some other data (other data not included).
Approach I tried to solve it with observeEvent expressions which are triggered via reactiveValues. However, as you will observe when running the script below, this does not work out as expected.
Question: Any idea on what is wrong?
library(shiny)
library(shinyWidgets)
library(rhandsontable)
shinyApp(
ui = fluidPage(
actionButton("show", "Show data", width = "100%"),
rHandsontableOutput("data_table")
),
server = function(input, output) {
rv <- reactiveValues(
# Triggers
pressed_first_time = 0,
confirm_module = TRUE,
save_module = TRUE,
table_change = TRUE
)
observeEvent(input$show, ignoreInit = TRUE, {
if (rv$pressed_first_time == 0){
rv$pressed_first_time <- isolate(rv$pressed_first_time + 1)
rv$table_change <- isolate(!rv$table_change)
cat("pressed_first time")
} else {
rv$pressed_first_time <- isolate(rv$pressed_first_time + 1)
rv$confirm_module <- isolate(!rv$confirm_module)
}
})
observeEvent(rv$confirm_module, ignoreInit = TRUE,{
confirmSweetAlert(
session = session,
inputId = session$ns("show_confirmation"),
title = "Be careful, your changes might be lost",
text = "Do you want to save your changes?",
type = "question",
btn_labels = c("Cancel", "Save"),
btn_colors = NULL,
closeOnClickOutside = FALSE,
showCloseButton = FALSE,
html = FALSE
)
cat("confirmation module")
rv$save_module <- isolate(!rv$save_module)
})
observeEvent(rv$save_module, ignoreInit = TRUE, {
if (isTRUE(input$show_confirmation)) {
sendSweetAlert(
session = session,
title = "Saved",
text = "Updated data has been successfully saved",
type = "success"
)
rv$table_change <- isolate(!rv$table_change)
cat("saving module")
} else {
return()
}
})
data_to_modify <- eventReactive(rv$table_change, ignoreInit = TRUE, {
mtcars
})
handson_df <- eventReactive(rv$table_change, ignoreInit = TRUE, {
cat("create handsons")
req(data_to_modify())
rhandsontable(data_to_modify())
})
output$data_table <- renderRHandsontable({
cat("plot module")
req(handson_df())
htmlwidgets::onRender(handson_df(),change_hook)
})
}
)
I think its just that you need session inside the server, as in:
server = function(input, output, session) {...
Actually, I found out the problem. The link from data_to_modify to handson_df was missing. In the below solution I put them together but in principle adding another reactiveValue triggering handson_df from data_to_modify will also work
library(shiny)
library(rhandsontable)
shinyApp(
ui = fluidPage(
actionButton("show", "Show data", width = "100%"),
rHandsontableOutput("data_table")
),
server = function(input, output) {
rv <- reactiveValues(
# Triggers
pressed_first_time = 0,
confirm_module = TRUE,
save_module = TRUE,
table_change = TRUE
)
observeEvent(input$show, ignoreInit = TRUE, {
if (rv$pressed_first_time == 0){
rv$pressed_first_time <- 1
rv$table_change <- isolate(!rv$table_change)
cat("pressed_first time")
} else {
rv$pressed_first_time <- 1
rv$confirm_module <- isolate(!rv$confirm_module)
}
})
observeEvent(rv$confirm_module, ignoreInit = TRUE,{
confirmSweetAlert(
session = session,
inputId = session$ns("show_confirmation"),
title = "Be careful, your changes might be lost",
text = "Do you want to save your changes?",
type = "question",
btn_labels = c("Cancel", "Save"),
btn_colors = NULL,
closeOnClickOutside = FALSE,
showCloseButton = FALSE,
html = FALSE
)
})
observeEvent(input$show_confirmation, ignoreInit = TRUE, {
if (isTRUE(input$show_confirmation)) {
sendSweetAlert(
session = session,
title = "Saved",
text = "Updated data has been successfully saved",
type = "success"
)
rv$table_change <- isolate(!rv$table_change)
cat("saving module")
} else {
return()
}
})
data_to_modify <- eventReactive(rv$table_change, ignoreInit = TRUE, {
rhandsontable(mtcars)
})
# handson_df <- eventReactive(rv$table_change, ignoreInit = TRUE, {
# cat("create handsons")
# req(data_to_modify())
# rhandsontable(data_to_modify())
# })
output$data_table <- renderRHandsontable({
cat("plot module")
req(data_to_modify())
data_to_modify()
# htmlwidgets::onRender(handson_df(),change_hook)
})
}
)

Creating a notiftication when function is initiated in a Shiny Application

I have the following R Shiny Application:
library(shiny)
shinyApp(
ui = dashboardPage(
dashboardHeader(
title = "Tweetminer",
titleWidth = 350
),
dashboardSidebar(
width = 350,
sidebarMenu(
menuItem("Menu Item")
)
),
dashboardBody(
fluidRow(
tabBox(
tabPanel("Select tweets",
numericInput("tweet_amount", "Amount of tweets:", 10, min = 1, max = 100),
div(style="display:inline-block",numericInput("tweet_long", "Longitude:", 10, min = 1, max = 100)),
div(style="display:inline-block",numericInput("tweet_lat", "Latitude:", 10, min = 1, max = 100)),
#selectInput("tweet_name", "Account name", choices = c("#realDonaldTrump","#GorgeNails"), width = NULL, placeholder = NULL),
selectInput("tweet_name", "Account name", choices = c("#realDonaldTrump","#Yankees"), selected = NULL, multiple = FALSE,
selectize = TRUE, width = NULL, size = NULL),
actionButton("get_tweets", "Fetch the tweets"),
hidden(
div(id='text_div',
verbatimTextOutput("text")
)
)
)
)
)
)
),
server = function(input, output) {
test_list <- c(1,2,3)
run_function <- reactive({
for(i in test_list){
Sys.sleep(2)
}
})
observeEvent(input$get_tweets, {
run_function()
toggle('text_div')
output$text <- renderText({"You're now connecting to the API"})
})
}
)
This application should allow users to input parameters after which tweets will be scraped. However - because it will take some time Im looking for a way to inform users this will take a while.
Using this function:
observeEvent(input$get_tweets, {
run_function()
toggle('text_div')
output$text <- renderText({"You're now connecting to the API"})
})
I inform the user that the function (run_function() in this example, a mockup but just for reproducing purposes) is ready. However Im looking for a notice after you initiate the function and when its done.
So after you press the first time it should say something like - function initiated and after the function is ready -> "You .. api".
Any thoughts on what I should change to get this working?
You can use withProgress function, check this example:
output$plot <- renderPlot({
withProgress(message = 'Calculation in progress', detail = 'This may take a while...', value = 0, {
for (i in 1:15) {
incProgress(1/15)
Sys.sleep(0.25)
}
})
plot(cars)
})
More info
EDIT: New example:
library(shiny)
ui <- fluidPage(
actionButton(inputId = 'DoSomething', label = 'Do something'),
verbatimTextOutput('t1')
)
server <- function(input, output, session) {
output$t1 <- renderPrint('Doing nothing...')
run_function <- reactive({
withProgress(message = "Starting...", max = 60, value = 0, {
for (i in 1:25) {
incProgress(1)
Sys.sleep(0.25)
}
setProgress(message = "Connected..")
for (i in 1:25) {
incProgress(1)
Sys.sleep(0.25)
}
setProgress(message = "Doing Stuffs...")
for (i in 1:10) {
incProgress(1)
Sys.sleep(0.25)
}
setProgress(message = "Disconecting...")
})
return('Finish')
})
observeEvent(input$DoSomething, {
output$t1 <- renderPrint(run_function())
})
}
shinyApp(ui, server)

Resources