I have a problem that has been discussed in different ways here but apparently not to face my quite simple need.
I have a simple app that makes a call to an SQL database. I use a button to launch the query.
I would simply need a text showing "click on button" to download at the very beginning.
Once a user clicks on the button, I would need this output text to show "Downloading the data, please wait".
Once the query is completed and the data has been fully received, I would need the output text to show "Data downloaded successfully."
I've seen some solutions based off the progress bar but I cannot use it since I'm not going through a data.frame. I query the database and I don't know how long this could take.
I've seen other solutions based off reactive values but the text output in this case should react based on the size of the dataframe (0 rows and button clicked -> still downloading the data; >0 rows and button clicked "data downloaded successfully").
Hence, I'm stuck here.
This is my simple code but that ideally does what I would need.
ui <- fluidPage(
fluidRow(actionButton("download_btn", "Download Data")),
fluidRow(textOutput(outputId = "load_data_status")),
fluidRow(dataTableOutput("output_table"))
)
server <- function(input, output) {
cat("\n output$output_table = \n", output$output_table)
data <- eventReactive(input$download_btn,{
output$load_data_status <- renderText({ "Downloading data from Server. Please wait..." })
# here I actually download the data from a database and this could take several seconds
df <- data.frame(mtcars)
output$load_data_status <- renderText({ "Data downloaded succesfully." })
df
})
output$output_table <- renderDataTable({
data()
})
}
shinyApp(ui, server)
Option A:
Here is a pretty good solution by Dean Attali: https://github.com/daattali/advanced-shiny/tree/master/busy-indicator
Option B:
You can listen to JavaScript events to:
Change text when the button is clicked
Change the text again when the output is rendered
I also added Sys.sleep() to simulate some loading time.
Code:
library(shiny)
ui <- fluidPage(
tags$head(tags$script(HTML('
// 1. Change text to "Downloading..." when button is clicked
$(document).on("shiny:inputchanged", function(event) {
if (event.name === "download_btn") {
$("#download_btn").html("Downloading data from Server. Please wait...");
}
});
//. 2. Change text to "Success" when output table is changed
$(document).on("shiny:value", function(event) {
if (event.name === "output_table") {
$("#download_btn").html("Data downloaded succesfully.");
}
});
'))),
fluidRow(actionButton("download_btn", "Download Data")),
fluidRow(DT::dataTableOutput("output_table"))
)
server <- function(input, output) {
data <- eventReactive(input$download_btn,{
df <- data.frame(mtcars)
Sys.sleep(3) # Simulate some loading time
df
})
output$output_table <- renderDataTable({
data()
})
}
shinyApp(ui, server)
Output:
Related
I have a question when using renderText() function when this text box is triggered by a button. The output Error only triggered by the button for the first time, but for the second or third time, I don't have to click the button, the error text already showed. I think it is really confusing. Here is my code:
library(DT)
shinyServer(function(input, output) {
observeEvent(input$searchbutton, {
if (input$id %in% df$ID) {
data1 <-
datatable({
memberFilter <-
subset(df, df$ID == input$id)
}, rownames = FALSE, options = list(dom = 't'))
output$decision <- DT::renderDataTable({
data1
})
}
else{
output$Error<- renderText(if(input$id %in% df$ID || input$id==""){}
else{
paste("This ID :",input$id,"does not exist")
})
})
}
})
})
so the problem is in this renderText function, if I click the button more than once, the text box will updated automatically when I change the input even i did not click the button.
I guess this issue is because the text box has been triggered, it always 'rendering' the text box, so it did not need to trigger again, if there any solution can make this renderText box always been triggered by button?
It would be better if you could prepare minimal, reproducible example, so we could talk about your real or almost-real app, but I have prepared MRE for our discussion:
library(shiny)
ids <- 1:5
ui <- fluidPage(
numericInput("id_to_check", "Id to check", value = 0),
actionButton("check_id_btn", "Check"),
textOutput("check_result")
)
server <- function(input, output, session) {
observeEvent(input$check_id_btn, {
if (input$id_to_check %in% ids) {
} else {
output$check_result <- renderText({
paste0("This ID: ", input$id_to_check, " does not exist.")
})
}
})
}
shinyApp(ui, server)
So we have ids (from 1 to 5) and the Shiny app, in the server in observeEvent we check if the chosen id (from numericInput) exists in ids and if not, we display the user information that id doesn't exist.
Is this app shows the problem you see in your app? When you push "Check" button (and leave 0 as a id), text is displayed and then, when you change id, text - at least visually - changes and gives wrong results (e.g. if we change id to 1, then we should see any text output according to this part of code:
if (input$id_to_check %in% ids) {
} else {
)
So the first thing is that:
you should never nest objects which belongs to reactive context (observe, reactive, render*),it just won't work in expected way most of the time.
If you want to trigger render* only if the event occurs (like the button is pushed), then you can use bindEvent() function (from shiny):
library(shiny)
ids <- 1:5
ui <- fluidPage(
numericInput("id_to_check", "Id to check", value = 0),
actionButton("check_id_btn", "Check"),
textOutput("check_result")
)
server <- function(input, output, session) {
output$check_result <- renderText({
if (!input$id_to_check %in% ids) {
paste0("This ID: ", input$id_to_check, " does not exist.")
}
}) |>
bindEvent(input$check_id_btn)
}
shinyApp(ui, server)
I have removed observeEvent(), because I don't see the reason to leave it if we want just to display something, but I don't know your app, so maybe you need this, but do not nest anything which is reactive.
The scenario I'm emulating with the below minimal example is allowing a user to engage with a Shiny App (click the numericInput control and see server-side events occur) while a long-running download is occurring (simulated with Sys.sleep(10) within downloadHandler).
In a synchronous setting, when the "Download" button is clicked, the user can still interact with UI elements, but other Shiny calculations (in this case, renderText), get put in a queue. I'd like the asynchronous setting, where the download occurs in the background, and users can still interact with the UI elements and get desired output (e.g. renderText).
I'm using callr::r_bg() to achieve asynchronicity within Shiny, but the issue is that my current code of the downloadHandler is incorrect (mtcars should be getting downloaded, but the code is unable to complete the download, 404 error message), I believe it's due to the specific way in which downloadHandler expects the content() function to be written, and the way I've written callr::r_bg() is not playing nicely with that. Any insights would be appreciated!
Reference:
https://www.r-bloggers.com/2020/04/asynchronous-background-execution-in-shiny-using-callr/
Minimal Example:
library(shiny)
ui <- fluidPage(
downloadButton("download", "Download"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text")
)
server <- function(input, output, session) {
long_download <- function(file) {
Sys.sleep(10)
write.csv(mtcars, file)
}
output$download <- downloadHandler(
filename = "data.csv",
content = function(file) {
x <- callr::r_bg(
func = long_download,
args = list(file)
)
return(x)
}
)
observeEvent(input$count, {
output$text <- renderText({
paste(input$count)
})
})
}
shinyApp(ui, server)
I figured out a solution, and learned the following things:
Because downloadHandler doesn't have a traditional input$X, it can be difficult to include reactivity in the traditional way. The workaround was to present the UI as a hidden downlodButton masked by an actionButton which the user would see. Reactivity was facilitated in the following process: user clicks actionButton -> reactive updates -> when the reactive finishes (reactive()$is_alive() == FALSE), use shinyjs::click to initiate the downloadHandler
Instead of placing the callr function within the downloadHandler, I kept the file within the content arg. There seems to be some difficulties with scoping because the file needs to be available within the content function environment
I'm using a reactive function to track when the background job (the long-running computation) is finished to initiate the download using the syntax: reactive()$is_alive()
The invalidateLater() and toggling of a global variable (download_once) is important to prevent the reactive from constantly activating. Without it, what will happen is your browser will continually download files ad infinitum -- this behavior is scary and will appear virus-like to your Shiny app users!
Note that setting global variables is not a best practice for Shiny apps (will think of a better implementation)
Code Solution:
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
download_once <- TRUE
#output that tracks progress of background task
check <- reactive({
invalidateLater(millis = 1000, session = session)
if (long_run()$is_alive()) {
x <- "Job running in background"
} else {
x <- "Async job in background completed"
if(isTRUE(download_once)) {
shinyjs::click("download")
download_once <<- FALSE
}
invalidateLater(millis = 1, session = session)
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
Thanks #latlio for your great answer. I think it cloud be easily improved.
invalidateLater should be used very carefully and only WHEN needed. I use invalidateLater only once and moved it to the logical part where we are waiting for the result. Thus we are NOT invalidating the reactivity infinitely.
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
#output that tracks progress of background task
check <- reactive({
if (long_run()$is_alive()) {
x <- "Job running in background"
invalidateLater(millis = 1000, session = session)
} else {
x <- "Async job in background completed"
shinyjs::click("download")
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
I'm having trouble creating a sequence of events in a Shiny app. I know there are other ways of handling parts of this issue (with JS), and also different Shiny functions I could use to a similar end (e.g. withProgress), but I'd like to understand how to make this work with reactivity.
The flow I hope to achieve is as follows:
1) user clicks action button, which causes A) a time-consuming calculation to begin and B) a simple statement to print to the UI letting the user know the calculation has begun
2) once calculation returns a value, trigger another update to the previous text output alerting the user the calculation is complete
I've experimented with using the action button to update the text value, and setting an observer on that value to begin the calculation (so that 1B runs before 1A), to ensure that the message isn't only displayed in the UI once the calculation is complete, but haven't gotten anything to work. Here is my latest attempt:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("run", "Pull Data")
mainPanel(
textOutput("status")
)
)
)
server <- function(input, output, session) {
# slow function for demonstration purposes...
test.function <- function() {
for(i in seq(5)) {
print(i)
Sys.sleep(i)
}
data.frame(a=c(1,2,3))
}
report <- reactiveValues(
status = NULL,
data = NULL
)
observeEvent(input$run, {
report$status <- "Pulling data..."
})
observeEvent(report$status == "Pulling data...", {
report$data <- test.function()
})
observeEvent(is.data.frame(report$data), {
report$status <- "Data pull complete"
}
)
observe({
output$status <- renderText({report$status})
})
}
Eventually, I hope to build this into a longer cycle of calculation + user input, so I'm hoping to find a good pattern of observers + reactive elements to handle this kind of ongoing interaction. Any help is appreciated!
I have a small shiny app for annotating text files.
The UI provides fileInput to select .txt files. One of the files is the default when the app is launched.
Next, Previous buttons allow user to display the contents of the file, one sentence at a time.
User may select any text within a sentence and click the Add Markup button to annotate the sentence. The Action Button triggers javascript function addMarkup().
The sentence is displayed after being marked up.
I am only posting the shiny app code here. Complete code of the app is available on github repository
library(shiny)
ui <- fluidPage(
tags$head(tags$script(src="textselection.js")),
titlePanel("Corpus Annotation Utility"),
sidebarLayout(
sidebarPanel(
fileInput('fileInput', 'Select Corpus', accept = c('text', 'text','.txt')),
actionButton("Previous", "Previous"),
actionButton("Next", "Next"),
actionButton("mark", "Add Markup")
),
mainPanel(
tags$h1("Sentence: "),
htmlOutput("sentence"),
tags$h1("Sentence marked up: "),
htmlOutput("sentenceMarkedUp")
)
)
)
server <- function(input, output) {
sourceData <- reactive({
corpusFile <- input$fileInput
if(is.null(corpusFile)){
return(readCorpus('data/news.txt'))
}
readCorpus(corpusFile$datapath)
})
corpus <- reactive({sourceData()})
values <- reactiveValues(current = 1)
observeEvent(input$Next,{
if(values$current >=1 & values$current < length(corpus())){
values$current <- values$current + 1
}
})
observeEvent(input$Previous,{
if(values$current > 1 & values$current <= length(corpus())){
values$current <- values$current - 1
}
})
output$sentence <- renderText(corpus()[values$current])
}
shinyApp(ui = ui, server = server)
readCorpus() function looks like this:
readCorpus <- function(pathToFile){
con <- file(pathToFile)
sentences <- readLines(con, encoding = "UTF-8")
close(con)
return(sentences)
}
My question is how can I persist the sentences to a file after they have been annotated?
Update:
I have gone through Persistent data storage in Shiny apps, and hope that I will be able to follow along the documentation regarding persistent storage. However I am still unsure how to capture the sentence after it has been marked up.
You have two issues here - persisting the changes, and then saving the output. I solved the problem using a bit of JS and a bit of R code. I'll do a pull request on Github to submit the broader code. However, here's the core of it.
In your Javascript that you use to select things, you can use Shiny.onInputChange() to update an element of the input vector. Doing this, you can create a reactiveValues item for the corpus, and then update it with inputs from your interface.
Below, you'll notice that I switched from using a textnode to using just the inner HTML. Using a node, and firstChild, as you had it before, you end up truncating the sentence after the first annotation (since it only picks the stuff before <mark>. Doing it this way seems to work better.
window.onload = function(){
document.getElementById('mark').addEventListener('click', addMarkup);
}
function addMarkup(){
var sentence = document.getElementById("sentence").innerHTML,
selection="";
if(window.getSelection){
selection = window.getSelection().toString();
}
else if(document.selection && document.selection.type != "Control"){
selection = document.selection.createRange().text;
}
if(selection.length === 0){
return;
}
marked = "<mark>".concat(selection).concat("</mark>");
result = sentence.replace(selection, marked);
document.getElementById("sentence").innerHTML = result;
Shiny.onInputChange("textresult",result);
}
Next, I've tried to simplify your server.R code. You were using a reactive context to pull from another reactive context (sourceData into corpus), which seemed unnecessary. So, I tried to refactor it a bit.
library(shiny)
source("MyUtils.R")
ui <- fluidPage(
tags$head(tags$script(src="textselection.js")),
titlePanel("Corpus Annotation Utility"),
sidebarLayout(
sidebarPanel(
fileInput('fileInput', 'Select Corpus', accept = c('text', 'text','.txt')),
actionButton("Previous", "Previous"),
actionButton("Next", "Next"),
actionButton("mark", "Add Markup"),
downloadButton(outputId = "save",label = "Download")),
mainPanel(
tags$h1("Sentence: "),
htmlOutput("sentence"))
)
)
server <- function(input, output) {
corpus <- reactive({
corpusFile <- input$fileInput
if(is.null(corpusFile)) {
return(readCorpus('data/news.txt'))
} else {
return(readCorpus(corpusFile$datapath))
}
})
values <- reactiveValues(current = 1)
observe({
values$corpus <- corpus()
})
output$sentence <- renderText(values$corpus[values$current])
observeEvent(input$Next,{
if(values$current >=1 & values$current < length(corpus())) {
values$current <- values$current + 1
}
})
observeEvent(input$Previous,{
if(values$current > 1 & values$current <= length(corpus())) {
values$current <- values$current - 1
}
})
observeEvent(input$mark,{
values$corpus[values$current] <- input$textresult
})
output$save <- downloadHandler(filename = "marked_corpus.txt",
content = function(file) {
writeLines(text = values$corpus,
con = file,
sep = "\n")
})
}
Now, the code has a few changes. The loading from file is basically the same. I was right about my skepticism on isolate - replacing it with an observe accomplishes what I wanted to do, whereas isolate would only give you the initial load. Anyway, we use observe to load the corpus values into the reactiveValues object you created - this is to give us a place to propagate changes to the data.
We keep the remaining logic for moving forward and backward. However, we change the way the output is rendered so that it looks at the reactiveValues object. Then, we create an observer that updates the reactiveValues object with the input from our updated Javascript. When this happens, the data gets stored permanently, and you can also mark more than one sequence in the string (though I have not done anything with nested marking or with removing marks). Finally, a save function is added - the resulting strings are saved out with <mark> used to show the marked areas.
If you load a previously marked file, the marks will show up again.
I am trying to create a UI on which I can upload a file and also there is a text input where I can write the product name which I want to search in the uploaded file. I am doing that using the Levenshtein Distance function (adist() function). Now, once i get the results for which the edit distance is 0, I want to display those rows in the Table on the Main Panel. Whatever input is given in the Text input on the UI is searched against the items column in the file uploaded. A sample image of the CSV file which is uploaded is this-
Sample image of the CSV file which is input by the user
Once I run the code and find the edit distance for all the words, I store them in a vector and then use this to print the rows from the file which have edit distance equal to 0. The problem is that when I click on submit, the result is not displayed on the UI but it is displayed on the R-studio console. How do I fix this?
Please help me with the code.
library(shiny)
ui = shinyUI(fluidPage(
titlePanel("LEVENSHTEIN DISTANCE function trial"),
sidebarLayout(
sidebarPanel(
numericInput("rows","Enter the number of rows",value=NULL),
textInput("product", "input product name"),
br(),
br(),
fileInput("file", "Input the file"),
submitButton("Find!")),
mainPanel(
tableOutput("result")
)
)
))
server = shinyServer(function(input,output) {
output$result <- renderPrint({ if (is.null(input$file)) return( );
trial = read.csv(input$file$datapath)
ls = vector('list', length = input$rows)
for(i in 1:input$rows) {
edit = adist("earbuds", trial$items[i])
new_edit = as.numeric(edit)
ls[i] = edit
if(ls[i]==0) print(trial[i, ])
}
})
})
shinyApp(ui=ui,server=server)
Thank You!
It is very hard to provide working code without sample input date. But, here is my attempt at giving you what I think should work.
server = shinyServer(function(input,output) {
output$result <- renderTable({
if (!is.null(input$file)) {
trial = read.csv(input$file)
trial <- trial[adist('earbuds', trial$items) == 0), ]
}
})
})
If you provide input data and expected output table, I can edit the answer to be more precise.