how to make renderText() only trigger by a button Rshiny - r

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.

Related

How to break a for loop basing on captured shinyalert input value

I am using a for loop to create multiple popping up messages in my shiny app (using shinyalert package).
I would like messages to stop popping if user had clicked Cancel as an answer to a previous message.
Below a sample of code illustrating my example
library(shiny)
library(shinyalert)
ui <- fluidPage(
actionButton("run", "Run")
)
server <- function(input, output, session) {
observeEvent(input$run, {
vector.elements <- c("A", "B", "C", "D", "E")
decision <<- TRUE
for (element in vector) {
shinyalert(
title = "Do you accept following element?",
text = element,
showCancelButton = TRUE,
callbackR = mycallback)
mycallback <- function(value) {
decision <<- value
}
if (decision == FALSE) {
break
}
}
})
}
shinyApp(ui = ui, server = server)
If user clicks on Cancel button as an answer to Do you accept following element? A, I would like next messages not to pop up.
Any hint would be much appreciated!
Shiny alert does not seem to be run synchronous to the loop. Put print(decision) in front of the for-loop. It'll show in the console that the loop runs independently from the user clicking on the alert messages. That means: it won't work with a for loop or any other loop. It can only be done using the event mechanisms provided by Shiny.
The solution below creates and manipulates a reactive value RequiredAnswers. Any change to it will trigger the shiny alert to open and ask the user to confirm the first element of the RequiredAnswers vector. In other words, it removes the element that has just been answered with "No".
Each answer to the alert will be caught by observeEvent(input$AnswerAlert, {}). If the response was "cancel" it dismisses the first element of RequiredAnswers thus triggering the next alert. This way we get a loop. If the response was "Ok" it will clear RequiredAnswers and no more alerts will be triggered (because observeEvent(RequiredAnswers(), {}) does not respond to RequiredAnswers == NULL.
Drawback: if the user clicks 'Cancel' quite fast, Shiny does not recognize the event observeEvent(input$AnswerAlert, {}) does not get called. I cannot say for sure what the source of this is. My guess is a bug in Shiny Alert.
Another way would be to do it recursively (see the section "Chaining modals" in the documentation). This way, the lost events may be avoided.
library(shiny)
library(shinyalert)
ui <- fluidPage(
actionButton("run", "Run"),
verbatimTextOutput("answer", placeholder = TRUE)
)
server <- function(input, output, session) {
RecentDecision <- reactiveVal()
RequiredAnswers <- reactiveVal()
# Responds to the alert being confirmed or dismissed
observeEvent(input$AnswerAlert, {
if (input$AnswerAlert) {
Answer <- RequiredAnswers()[1]
RecentDecision(Answer)
print(RecentDecision())
RequiredAnswers(NULL)
} else {
# Remove the first item of RequiredAnswers
# Clear it completely when the end has been reached
if (length(RequiredAnswers()) == 1) {
RequiredAnswers(NULL)
RecentDecision(NULL)
}
else
RequiredAnswers(RequiredAnswers()[-1])
}
})
# Responds to changes, ignores NULL
observeEvent(RequiredAnswers(), {
shinyalert(
title = "Do you accept following element?",
text = RequiredAnswers()[1],
showCancelButton = TRUE,
inputId = "AnswerAlert" # use individual id
)
})
# Respond to the Run button
observeEvent(input$run, {
# Set up the vector of desired answers
RequiredAnswers(c("A", "B", "C", "D", "E"))
})
output$answer <- renderText({
if (!is.null(RecentDecision()))
RecentDecision()
else
"No answer, yet"
})
}
shinyApp(ui = ui, server = server)

Dynamically add button UI and associated observeEvents

In my Shiny application, I'd like the click of a button to dynamically populate a list of new buttons. Each of these buttons should act specifically and uniquely.
So far I have been able to get the "main" button to create the list of buttons. I have not been able to figure out how to get each of the individual buttons to act accordingly. For simplicity, let's say that I'd like to print the label of the button. In reality, each button triggers should trigger a a variety of things and set a number of reactive values, but that's outside the scope for now.
Here is a reproducible example. In particular, only the last button receives an event listener. And when it fires, it for some reason fires N times; in this example N = 5. In reality too, the buttons may disappear on another event, but then should show up again if that "main" button was clicked a second time.
library(shiny)
ui <- pageWithSidebar(headerPanel("test"),
sidebarPanel(actionButton("build", "Build")),
mainPanel(uiOutput("buttons")))
server <- function(input, output) {
observeEvent(input$build, {
output$buttons <- renderUI({
btns <- list()
for (ix in 1:5) {
name <- paste("btn_", ix)
btns <- c(btns,
list(actionButton(
name, paste("Button", ix)
)))
observeEvent(input[[name]], {
print(name)
})
}
btns
})
})
}
shinyApp(ui, server)
What do I need to do in order to have an event listener associated with each button?
I have tried a variety of things, including wrapping the observeEvent() in local({}), creating a new button name with the namespace like ns <- NS(name) and ns(name) after, and moving renderUI outside of the parent observeEvent().
I've taken a look at Shiny - Can dynamically generated buttons act as trigger for an event, How to create dynamic number of observeEvent in shiny?, and Dynamic number of actionButtons tied to unique observeEvent, among others, but either I didn't understand them or they weren't quite right.
Bonus points if you can explain what's happening behind the scenes so I can learn more about Shiny reactivity!
Created on 2021-12-01 by the reprex package (v2.0.1)
Here is a sample app using purrr from the tidyverse that work very good to create multiple inputs. I also added textOutput's to see that the buttons are being clicked. This app could be further modified to select the amount of buttons to create.
App::
library(shiny)
number_of_buttons <- 5
outputs <- map(paste0('btn_', seq(1:number_of_buttons)), ~textOutput(outputId = .))
ui <- pageWithSidebar(headerPanel("test"),
sidebarPanel(actionButton("build", "Build")),
mainPanel(uiOutput("buttons"),
tagList(outputs)))
# server ------------------------------------------------------------------
server <- function(input, output) {
observeEvent(input$build, {
#this creates a list with five buttons to pass into `renderUI`
buttns_inputs <-
paste0('btn_', seq(1:number_of_buttons)) %>%
map(~ actionButton(., .))
output$buttons <- renderUI({
tagList(buttns_inputs)
})
})
#for debugging purposes
observe({
req(input$btn_1)
names(input) %>%
str_subset('^btn') %>%
walk(~ {output[[.]] <<- renderPrint({input[[.]] %>% print}) })
})
}
shinyApp(ui, server)
Simplified version:
library(shiny)
number_of_buttons <- 5
ui <- pageWithSidebar(
headerPanel("test"),
sidebarPanel(actionButton("build", "Build")),
mainPanel(uiOutput("buttons"))
)
# server ------------------------------------------------------------------
server <- function(input, output) {
observeEvent(input$build, {
# this creates a list with five buttons to pass into `renderUI`
buttns_inputs <-
paste0("btn_", seq(1:number_of_buttons)) %>%
map(~ actionButton(., .))
output$buttons <- renderUI({
tagList(buttns_inputs)
})
})
}
shinyApp(ui, server)

shiny reactive output, evaluate input only after first click on actionbutton

I have a simple shiny app where the user should input comma-separated values into a text input, chose the output and click on a button to convert to an output.
I have followed the advice in Update content on server only after I click action button in Shiny to change the output only when clicking, and it works.
However, only when starting/ opening the app the first time, the field is empty, yet the output seems to try to evaluate the input field.
It is more of a cosmetic problem, because once the user filled something in, this does not recur, but I wonder how I could avoid this...
My app:
library(shiny)
ui <- fluidPage(
textInput("from", "csv", value = NULL),
actionButton("run", "Run"),
textOutput("to")
)
server <- function(input, output, session) {
list1 <- reactive({
input$run
x <- isolate(paste(read.table(text = input$from, sep = ",")))
x
})
output$to <- renderText({
list1()
})
}
shinyApp(ui = ui, server = server)
The not-desired output - I would like to get rid of the errors.
you can use req(input$from), see Check for required values

Shiny text output to update at the end of SQL query

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:

How to persist changes in a text file using shiny?

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.

Resources