R shiny observe for row deselection dataTable - r

I have a shiny app that has a DT::renderDataTable, the user can select a row in the datatable.
The following bit of code will only print FALSE (when a row is selected):
observeEvent(input$segment_library_datatable_rows_selected, {
print(is.null(input$segment_library_datatable_rows_selected))
})
How do I get it to print when a row is also deselected? (The print value would be TRUE)

observeEvent(input$selected,ignoreNULL = FALSE,{...})
ignoreNULL is defaulted to TRUE. Set to FALSE to have the event observed upon deselection.

As I understand a working minimal example would be the following (sel() reactive is TRUE if a row in datatable is selected):
library(shiny)
library(DT)
ui <- fluidPage(
DT::dataTableOutput("datatable"),
textOutput("any_rows_selected")
)
server <- function(input, output) {
# Output iris dataset
output$datatable <- DT::renderDataTable(iris, selection = "single")
# Reactive function to determine if a row is selected
sel <- reactive({!is.null(input$datatable_rows_selected)})
# Output result of reactive function sel
output$any_rows_selected <- renderText({
paste("Any rows selected: ", sel())
})
}
shinyApp(ui, server)

Alternatively you can use observe() which will respond to any hits to the input$datatable_rows_selected, including NULL.
To repurpose Kristoffer W. B.'s code:
library(shiny)
library(DT)
ui <- fluidPage(
DT::dataTableOutput("testtable")
)
server <- function(input, output) {
# Output iris dataset
output$testtable<- DT::renderDataTable(iris, selection = "single")
# Observe function that will run on NULL values
an_observe_func = observe(suspended=T, {
input$testtable_rows_selected
isolate({
#do stuff here
print(input$testtable_rows_selected)
})
})
#start the observer, without "suspended=T" the observer
# will start on init instead of when needed
an_observe_func$resume()
shinyApp(ui, server)
A few things to note:
1) I found it best to start the observer in suspended mode, that way it doesn't start when the program initializes. You can turn it on whenever you want it to... observe... (such as after you render the datatable, or before you'd like to start tracking selections).
2) Use isolate to stop the observer from tracking multiple elements. in this case the observer should only react to input$testtable_rows_selected, instead of everything else occurring. The symptom of this problem is that your observer fires multiple times on a single change.

Related

How can i register a click and at the same time update the click counter table in shiny?

I'm trying to create a project voting app and I need users to vote on the most popular proposals. So I need a click counter on each of the proposals. A example with the iris dataset where Sepal.Length is the record clicked.
library(shiny)
library(reactable)
iris2 = iris #to start
saveRDS(iris2, paste0(getwd(),'/iris.Rds'))
iris2 = readRDS(paste0(getwd(),'/iris2.Rds'))
ui <- fluidPage(
titlePanel("row selection example"),
reactableOutput("table"),
verbatimTextOutput("selected")
)
server <- function(input, output, session) {
selected <- reactive(getReactableState("table", "selected"))
output$table <- renderReactable({
reactable(x(), selection = "single", onClick = "select")
})
observe({
iris2[selected(), "Sepal.Length"] = iris2[selected(), "Sepal.Length"] +1
print(iris2[selected(), ])
saveRDS(iris2,paste0(getwd(),'/iris2.Rds'))
})
x <- reactivePoll(2000, session,
checkFunc = function() {
if (file.exists(paste0(getwd(),'/iris2.Rds')))
file.info(paste0(getwd(),'/iris2.Rds'))$mtime[1]
else
""
},
valueFunc = function() {
iris2=readRDS(paste0(getwd(),'/iris2.Rds'))
}
)
}
shinyApp(ui, server)
My problem is that the app records the vote, but then goes back to the original record and doesn't count the vote. how can i stop shiny from going back to the original record and at the same time update the voting table?
I think you have 2 main problems:
The version you save is not the latest version plus the vote, but you always start from the original iris2, because iris2 is modified only inside the observe bloc. If you want to modify the general variable, you will need to use <<- (or in a more cleaner way maybe a reactive variable)
The observe run once it's updated because the modification reset the selection. You need to stop the observe if the selected is empty (actually it seems cleaner anyway)
You observe becomes like that:
observe({
selection <- selected()
if(!is.null(selection)) {
iris2[selection, "Sepal.Length"] <<- iris2[selection, "Sepal.Length"] + 1
print(iris2[selection, ])
saveRDS(iris2, paste0(getwd(),'/iris2.Rds'))
}
})

Error in .getReactiveEnvironment()$currentContext() while using reactive output in reactiveValues function

I'm trying to get a reactiveValue that is depending on a reactive. In the real code (this is a very simplified version), I load a dataset interactively. It changes when pushing the buttons (prevBtn/nextBtn). I need to know the number of rows in the dataset, using this to plot the datapoints with different colors.
The question: Why can't I use the reactive ro() in the reactiveValues function?
For understanding: Why is the error saying "You tried to do something that can only be done from inside a reactive expression or observer.", although ro() is used inside a reactive context.
The error is definitely due to vals(), I already checked the rest.
The code :
library(shiny)
datasets <- list(mtcars, iris, PlantGrowth)
ui <- fluidPage(
mainPanel(
titlePanel("Simplified example"),
tableOutput("cars"),
actionButton("prevBtn", icon = icon("arrow-left"), ""),
actionButton("nextBtn", icon = icon("arrow-right"), ""),
verbatimTextOutput("rows")
)
)
server <- function(input, output) {
output$cars <- renderTable({
head(dat())
})
dat <- reactive({
if (is.null(rv$nr)) {
d <- mtcars
}
else{
d <- datasets[[rv$nr]]
}
})
rv <- reactiveValues(nr = 1)
set_nr <- function(direction) {
rv$nr <- rv$nr + direction
}
observeEvent(input$nextBtn, {
set_nr(1)
})
observeEvent(input$prevBtn, {
set_nr(-1)
})
ro <- reactive({
nrow(dat())
})
output$rows <- renderPrint({
print(paste(as.character(ro()), "rows"))
})
vals <- reactiveValues(needThisForLater = 30 * ro())
}
shinyApp(ui = ui, server = server)
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
I think you want
vals <- reactiveValues(needThisForLater = reactive(30 * ro()))
Not everything in a reactiveValues list is assumed to be reactive. It's also a good place to store constant values. So since it's trying to evaluate the parameter you are passing at run time and you are not calling that line in a reactive environment, you get that error. So by just wrapping it in a call to reactive(), you provide a reactive environment for ro() to be called in.

Shiny renderDataTable table_cell_clicked

I am trying to create a table using Shiny, where the user can click on a row in order to see further information about that row. I thought I understood how to do this (see code attached).
However, right now as soon as the user clicks the "getQueue" action button, the observeEvent(input$fileList_cell_clicked, {}) seems to get called. Why would this be called before the user even has the chance to click on a row? Is it also called when the table is generated? Is there any way around this?
I need to replace "output$devel <- renderText("cell_clicked_called")" with code that will have all sorts of errors if there isn't an actual cell to refer to.
Thank you for any advice!
ui <- fluidPage(
actionButton("getQueue", "Get list of queued files"),
verbatimTextOutput("devel"),
DT::dataTableOutput("fileList")
)
shinyServer <- function(input, output) {
observeEvent(input$getQueue, {
#get list of excel files
toTable <<- data.frame("queueFiles" = list.files("queue/", pattern = "*.xlsx")) #need to catch if there are no files in queue
output$fileList <- DT::renderDataTable({
toTable
}, selection = 'single') #, selection = list(mode = 'single', selected = as.character(1))
})
observeEvent(input$fileList_cell_clicked, {
output$devel <- renderText("cell_clicked_called")
})}
shinyApp(ui = ui, server = shinyServer)
minimal error code
DT initializes input$tableId_cell_clicked as an empty list, which causes observeEvent to trigger since observeEvent only ignores NULL values by default. You can stop the reactive expression when this list is empty by inserting something like req(length(input$tableId_cell_clicked) > 0).
Here's a slightly modified version of your example that demonstrates this.
library(shiny)
ui <- fluidPage(
actionButton("getQueue", "Get list of queued files"),
verbatimTextOutput("devel"),
DT::dataTableOutput("fileList")
)
shinyServer <- function(input, output) {
tbl <- eventReactive(input$getQueue, {
mtcars
})
output$fileList <- DT::renderDataTable({
tbl()
}, selection = 'single')
output$devel <- renderPrint({
req(length(input$fileList_cell_clicked) > 0)
input$fileList_cell_clicked
})
}
shinyApp(ui = ui, server = shinyServer)

R shiny isolate reactive data.frame

I am struggling to understand how isolate() and reactive() should be used in R Shiny.
I want to achieve the following:
Whenever the "Refresh" action button is clicked:
Perform a subset on a data.frame and,
Feed this into my function to recalculate values.
The subset depends on a group of checkboxes that the user has ticked, of which there are approximately 40. I cannot have these checkboxes "fully reactive" because the function takes about 1.5 sec to execute. Instead, I want to give the user a chance to select multiple boxes and only afterwards click a button to (a) subset and (b) call the function again.
To do so, I load the data.frame in the server.R function:
df1 <- readRDS("D:/././df1.RData")
Then I have my main shinyServer function:
shinyServer(function(input, output) {
data_output <- reactive({
df1 <- df1[,df1$Students %in% input$students_selected]
#Here I want to isolate the "students_selected" so that this is only
#executed once the button is clicked
})
output$SAT <- renderTable({
myFunction(df1)
})
}
How about something like
data_output <- eventReactive(input$button, {
df1[,df1$Students %in% input$students_selected]
})
Here is my minimal example.
library(shiny)
ui <- list(sliderInput("num", "rowUpto", min= 1, max = 10, value = 5),
actionButton("btn", "update"),
tableOutput("tbl"))
server <- function(input, output) {
data_output <- eventReactive(input$btn, {
data.frame(id = 1:10, x = 11:20)[seq(input$num), ]
})
output$tbl <- renderTable({
data_output()})
}
runApp(list(ui = ui, server = server))
Edit
Another implementation, a bit more concise.
renderTable by default inspects the changes in all reactive elements within the function (in this case, input$num and input$button).
But, you want it to react only to the button. Hence you need to put the elements to be ignored within the isolate function.
If you omit the isolate function, then the table is updated as soon as the slider is moved.
library(shiny)
ui <- list(sliderInput("num", "rowUpto", min= 1, max = 10, value = 5),
actionButton("btn", "update"),
tableOutput("tbl"))
server <- function(input, output) {
output$tbl <- renderTable({
input$btn
data.frame(id = 1:10, x = 11:20)[seq(isolate(input$num)), ]
})
}
runApp(list(ui = ui, server = server))
Use eventReactive instead:
data_output <- eventReactive(input$updateButton, {
df1 <- df1[,df1$Students %in% input$students_selected] #I think your comments are messed up here, but I'll leave the filtering formatting to you
})
output$SAT <- renderTable({
data_output()
})
And in your UI you should have something like:
actionButton('updateButton',label = "Filter")
Looking at ?shiny::eventReactive:
Use eventReactive to create a calculated value that only updates in
response to an event. This is just like a normal reactive expression
except it ignores all the usual invalidations that come from its
reactive dependencies; it only invalidates in response to the given
event.

Update handsontable by editing table and/or eventReactive

I am using the rhandsontable package in a Shiny app which should have the following functionality:
the data used in the calculation can be randomly generated, invoked by an actionButton (and when the app starts)
the data can be manually edited by the user via the handsontable object
after manual editing it should be possible to re-generate random data, invoking a new calculation
The following app does exactly that what I want, but I could not figure it out how to get rid of the global variable did_recalc. It is a minimal example, where the data consists of two numeric values which are summed up.
library(shiny)
library(rhandsontable)
did_recalc <- FALSE
ui <- fluidPage(
rHandsontableOutput('table'),
textOutput('result'),
actionButton("recalc", "generate new random vals and calculate")
)
server <- function(input,output,session)({
dataset_generator <- eventReactive(input$recalc, {
df <- as.data.frame(runif(2))
output$table <- renderRHandsontable({rhandsontable(df)})
did_recalc <<- TRUE
df
}, ignoreNULL = FALSE)
output$result <- renderText({
df <- dataset_generator()
if (!is.null(input$table) && !did_recalc)
df <- hot_to_r(input$table)
did_recalc <<- FALSE
sum(df)
})
})
shinyApp(ui = ui, server = server)
If I remove the !did_recalc condition within output$result <- ... then editing the table still invokes a (correct) calculation. But if "recalc" is pressed (after some manual editing was done), then the "recalc" button just generates new random values, but without recalculating the sum.
It seems to me, that input$table can just be changed by manual edits of the table object and does not care about new values given via renderRHandsontable. Hence I need this hack with the global variable, which allows me to track if the user just re-generated the data (causing that input$table is "outdated")
Has anybody an idea how to get the functionality of this example without the global variable?
You could store the data in a reactiveValues and have two observers updating it; one if the button is clicked, one if the table is edited by hand.
In your output$table and output$result, you then just need to use the data that is in the reactiveValues. Here's an example (same ui.R as you posted):
server <- function(input,output,session)({
values <- reactiveValues(data=as.data.frame(runif(2)))
observe({
input$recalc
values$data <- as.data.frame(runif(2))
})
observe({
if(!is.null(input$table))
values$data <- hot_to_r(input$table)
})
output$table <- renderRHandsontable({
rhandsontable(values$data)
})
output$result <- renderText({
sum(values$data)
})
})

Resources