R, vector of strings to vector of inputs - r

TL;DR:
I have this vector/list of characters, it could either look like this:
list_of_inputs <- c('input$thing_1', 'input$thing_2', ...)
or like this:
list_of_inputs <- c('thing_1', 'thing_2', ...)
What I need is to convert it so it is interpreted like this:
c(input$thing_1, input$thing_2, ...)
Is this possible?
The reason is I want to programatically make shiny inputs, and programatically check when one of them has been hit. If I manually declare inputs in ui.R/ui-function, this works:
observeEvent(c(input$manually_made_1, input$manually_made_2), {
print('a button has been hit!')
}
And this also works:
observeEvent(c(input[['manually_made_1']], input[['manually_made_2']]), {
print('a button has been hit!')
}
I can't just give observeEvent the list_of_inputs as a vector/list of strings/characters, as it doesn't understand that. But if I try to make a list using a loop:
input_list <- c()
for(i in 1:length(list_of_inputs){
input_list <- append(input_list, input[[paste0('thing_',i)]])
}
input_list ends up containing nothing but NULL (or several)...
I tried using as.symbol() but that didn't seem to work. Any suggestions?

Here is an alternate solution for your consideration. The Shiny app below takes a list of lists of button information (id, label, and callback) and adds an actionButton and observer for each item. Each button press will trigger the corresponding callback function from the list.
The call to local() is key. If the call to local is removed only the final expression of the for loop is registered as an observer. Credit for the local workaround goes to this thread https://gist.github.com/wch/5436415/ and Winston Chang.
If multiple buttons need to share a callback you can define the function outside of the list and reference it multiple times. If there is no need for multiple callbacks you could forgo including a callback element in the lists and build out the event expression of observeEvent instead.
library(shiny)
myButtons <- list(
list(
id = "first",
label = "First Button",
callback = function() {
print("The first button was pushed??")
}
),
list(
id = "second",
label = "Second Button",
callback = function() {
print("And now the second button was pushed")
}
)
)
shinyApp(
ui = fluidPage(
lapply(
myButtons,
function(btn) actionButton(btn$id, btn$label)
)
),
server = function(input, output, session) {
for (btn in myButtons) {
local({
btnLocal <- btn
observeEvent(input[[btnLocal$id]], {
btnLocal$callback()
})
})
}
}
)

Related

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

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.

Test whether any input in a set of numbered input objects in R Shiny is empty

Let's say I have created 10 selectInput dropdowns for a multi plot export and these selectInputs are called "xaxis_1", "xaxis_2", ..... , "xaxis_10"
for a single 1 I can write:
if(!is.null(input$xaxis_1)) { .... do stuff } to stop it running export when the user hasn't entered any name, and presses submit, to avoid crashes.
A bit more general you can check this:
if(!is.null(input[[paste('xaxis', i, sep = '_')]])) { ...}
how can you write it elegantly so that 1 line of code checks whether ANY of the 1:10 input[[...]] is empty, i.e. NULL?
The nr of inputs depends on how many plots the user wants to export per file, so all is build with lapply(1:input$nrofplots, function(i) { .... } renderUI structure, and my if statement needs to have the same flexibility of 1:n
In a situation like below in the image, pressing Initiate export should give a sweetalert (got that covered) saying there is at least 1 value missing
Here a snippet I used in the UI side to validate the user's inputs.
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(), # Set up shinyjs
numericInput('axis1','Val 1',1),
numericInput('axis2','Val 2',1),
numericInput('axis3','Val 3',1),
actionButton('Go','Plot')
)
server <- function(input, output, session) {
#Try 1, space, AAA and check what shiny will return
observe(print(input$axis1))
observe({
All_Inputs <- vapply(paste0('axis',1:3),
function(x){isTruthy(input[[x]])},
logical(1))
All_InputsCP <- all(All_Inputs)
shinyjs::toggleState(id="Go", condition = All_InputsCP) #This is to make the button Go able or disable according to condition All_InputsCP #
})
}
shinyApp(ui, server)
I hope it helps.

Pattern for triggering a series of Shiny actions

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!

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.

Get Selected Row From DataTable in Shiny App

I want to modify this application:
https://demo.shinyapps.io/029-row-selection/
so that only one row can be selected at a time, and so that I can acquire the item in the first column of the selected row to plot data with. Does anyone know how to do this?
UPDATE: you can now access the selected rows using input$tableId_rows_selected in server.R. See here for more details.
To select a unique row, you can change the callback function of your example to this:
callback = "function(table) {
table.on('click.dt', 'tr', function() {
table.$('tr.selected').removeClass('selected');
$(this).toggleClass('selected');
Shiny.onInputChange('rows',
table.rows('.selected').data()[0][0]);
});
}"
When you click on a row,it basically removes any selected rows (they have the .selected class) and selects the row you clicked on.
I also changed the code in the Shiny.onInputChange function so that it returns the number in the first column.
The R method which renders a DataTable has a parameter which defines the selection mode. For example:
output$table1 <-
DT::renderDataTable(dataSet,
selection = 'single')
Possible values are ('multiple' is the default):
none
single
multiple
For further reference you can see: http://rstudio.github.io/DT/shiny.html
EDIT 04/14/2016
In the setup I use working with single selection mode has issues.
Here is what version I use:
> DT:::DataTablesVersion
[1] "1.10.7"
> packageVersion("DT")
[1] ‘0.1’
The problem I faced is that visually you have a single row selection but when you do:
observeEvent(input$table1_rows_selected, {
str(input$table1_rows_selected)
})
You will get a list with all rows which were selected but were not explicitly deselected. In other words selecting a new row does not automatically deselect the previous row in the internal Datatables logic. This might be also due to the DT wrapper, not sure.
This is why currently as a workaround we use JS for this:
$(document).on('click', '#table1 table tr', function() {
var selectedRowIds = $('#table1 .dataTables_scrollBody table.dataTable').DataTable().rows('.selected')[0];
var selectedId = "";
if (selectedRowIds.length === 1) {
selectedId = $(this).children('td:eq(0)').text();
} else {
$('#table1 tbody tr').removeClass('selected');
}
Shiny.onInputChange("table1_selected_id", selectedId);
});
Once you have this in place you will be able to do:
observeEvent(input$table1_selected_id, {
str(input$table1_selected_id)
})
This now at least sends correct data to your server.R code. Unfortunately you will still have an issue with the table because internally it keeps track on which rows were selected and if you switch pages it will restore a wrong selection. But at least this is purely a visual defect and your code will have the chance to function properly. So this solution actually needs more work.
You can use this:
output$data_table <- renderDataTable(data(),options = list(pageLength = 9))
Then to get the selected row:
selected <- input$data_table_rows_selected
Then to get cells from that row, you can use (assuming that time is the name of the col you are trying to get in this case):
time_x = data()[selected, "time"]
Selected is the index for the selected rows, so you need to use that index along with the col name.
The below code display a dataframe in DT table format. Users will be able to select single row. The selected row is retrieved and displayed. you can write your plot function in the plot block in the server.
I hope this helps !!
# Server.R
shinyServer(function(input, output,session) {
output$sampletable <- DT::renderDataTable({
sampletable
}, server = TRUE,selection = 'single')
output$selectedrow <- DT::renderDataTable({
selectedrowindex <<- input$sampletable_rows_selected[length(input$sampletable_rows_selected)]
selectedrowindex <<- as.numeric(selectedrowindex)
selectedrow <- (sampletable[selectedrowindex,])
selectedrow
})
output$plots <- renderPlot({
variable <- sampletable[selectedrowindex,1]
#write your plot function
})
})
#ui.R
shinyUI(navbarPage( "Single Row Selection",
tabPanel("Row selection example",
sidebarLayout(sidebarPanel("Parameters"),
mainPanel(
DT::dataTableOutput("selectedrow"),
DT::dataTableOutput("sampletable")
))
)
))
# global.R
library(DT)
library(shiny)
selectedrowindex = 0

Resources