Related
I have a Shiny app (please see end for a minimum working example) with a "parent" reactable table and a drilldown table that pops up when a user clicks on a row of the parent table. The information on which row is selected in the parent is obtained via reactable::getReactableState(). However, when the user switches to a different "parent" table, the function returns the row selection for the outdated table, not the updated one.
This occurs event though the output for the new parent table has completed it's calculations and is fully updated by the time the drilldown table starts it's calculations. After the whole systems finished and the app is idle, something (and I'm not sure what) triggers the input to reactable::getReactableState() to be invalidated, and the reactives fire again, but this time using the updated (or "correct" from my perspective) tables, and returns the expected result, which is that now row is selected.
Referring to the reactive graph below, what I want to do is have input$tables-table_parent__reactable__selected set not NULL every time input$tables-data_set changes.
I have tried to do this via the session$sendCustomMessage() and Shiny.addCustomMessageHandler approach found here: Change the input value in shiny from server, but I find that, although I can change input$tables-table_parent__reactable__selected value it doesn't seem to send send the info to the browser until after all the outputs are done caculating when input$tables-data_set is changed.
A minimum working example:
UI module:
drilldownUI <- function(id) {
ns <- NS(id)
tagList(
tags$script("
Shiny.addCustomMessageHandler('tables-table_parent__reactable__selected', function(value) {
Shiny.setInputValue('tables-table_parent__reactable__selected', value);
});
"),
shiny::selectizeInput(
inputId = ns("data_set"),
label = "Data set",
choices = c("iris", "cars"),
selected = "iris"
),
reactable::reactableOutput(outputId = ns("table_parent"),
width = "100%"),
reactable::reactableOutput(
outputId = NS(id, "drilldown_table"),
width = "100%"
)
)
}
Server module:
drilldownServer <- function(id, dat) {
moduleServer(id, function(input, output, session) {
dataset <- reactive({
data_list <-
list(iris = as.data.table(iris), cars = as.data.table(MASS::Cars93))
data_list[[input$data_set]]
})
data_grouped <- reactive({
dataset()[, .N, by = c(grouping_var())]
})
grouping_var <- reactive({
if (input$data_set == "iris") {
return("Species")
}
"Origin"
})
output$table_parent <- reactable::renderReactable({
req(input$data_set)
reactable::reactable(
data_grouped(),
selection = "single",
onClick = "select"
)
})
selected <- reactive({
out <- reactable::getReactableState("table_parent", "selected")
if(is.null(out)||out=="NULL") return(NULL)
out
})
output$drilldown_table <- reactable::renderReactable({
req(selected())
# This should only fire after a new parent table is generated and the row selection is
# reset to NULL, but it fires once the new table is generated and BEFORE the row selection
# is reset to NULL
selected_group <- data_grouped()[selected(), ][[grouping_var()]]
drilldown_data <- dataset()[get(grouping_var()) == selected_group]
reactable::reactable(drilldown_data)
})
observeEvent(input$data_set, {
session$sendCustomMessage("tables-table_parent__reactable__selected", 'NULL')
})
})
App:
library(shiny)
library(reactable)
library(data.table)
# Define UI for application that draws a histogram
ui <- fluidPage(
drilldownUI("tables")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
drilldownServer("tables")
}
# Run the application
shinyApp(ui = ui, server = server)
I found the solution thanks in part to this SO answer https://stackoverflow.com/a/39440482/9474704.
The key was to consider the row selection a state, rather than just reacting to input changes. Then, by using reactiveValues() instead of reactive(), I could update the state in multiple places using observeEvent().
An important additonal piece of information was that observe functions are eager, and you can set a priority, so when the user changes the input$data_set, I could reset the row selection to 0 before the drilldown reactable::renderReactable() section was evaluated.
The updates to the server module below for an example of the working solution:
drilldownServer <- function(id, dat) {
moduleServer(id, function(input, output, session) {
dataset <- reactive({
data_list <-
list(iris = as.data.table(iris), cars = as.data.table(MASS::Cars93))
data_list[[input$data_set]]
})
data_grouped <- reactive({
dataset()[, .N, by = c(grouping_var())]
})
grouping_var <- reactive({
if (input$data_set == "iris") {
return("Species")
}
"Origin"
})
# Create output for parent table
output$table_parent <- reactable::renderReactable({
req(input$data_set)
reactable::reactable(data_grouped(),
selection = "single",
onClick = "select")
})
# Create state variable
selected <- reactiveValues(n = 0)
currentSelected <- reactive({
reactable::getReactableState("table_parent", "selected")
})
observeEvent(currentSelected(), priority = 0, {
selected$n <- currentSelected()
})
# When data set input changes, set the selected number of rows to 0e
observeEvent(input$data_set,
label = "reset_selection",
priority = 9999, {
selected$n <- 0
})
# Create output for drilldown table
output$drilldown_table <- reactable::renderReactable({
req(selected$n > 0)
selected_group <-
data_grouped()[selected$n, ][[grouping_var()]]
drilldown_data <-
dataset()[get(grouping_var()) == selected_group]
reactable::reactable(drilldown_data)
})
})
}
I am working on building a shiny App. I have used some filters and rendered a data frame and the data frame changes dynamically as per the user input. But I cannot store a particular column value from a data frame into a vector. I need to store the reactive output every time into a vector so that I can use the values later again. Here the values are stored in text_vec and i need to pass that into the API but I cannot access the values from text_vec and i have to pass the updated values every time into the API
library(dplyr)
library(shiny)
shinyApp(ui = fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "cyl",
label = "Number cylinders:",
choices = c("all",sort(unique(mtcars$cyl))),
selected = "all"),
actionButton("capture",
"capture value")
), # closes sidebarPanel
mainPanel(
tableOutput("text"),
tableOutput("text2"),
tableOutput("text3"),
tableOutput("table")
) # closes mainPanel
) # closes sidebarLayout
), # closes fluidPage
server = function(input, output) {
# some example reactive data
cars_react <- reactive({
mtcars %>%
filter(cyl == input$cyl | input$cyl == "all")
})
# simply global assignment of a reactive vector
observeEvent(cars_react(), {
# here is a globally assigned vector taken from the reactive data
# reused in a render statement it will not react to change, since it is not reactive
test_vec3 <<- unique(cars_react()$hp)
})
# here a file is written to the working directory of your shiny app
# everytime cars_react() changes write (and overwrite) vector to a file
observeEvent(cars_react(), {
test_vec = unique(cars_react()$hp)
saveRDS(test_vec, file = "test_vec.Rdata")
})
# same as above but the file is gradually growing and not overwritten
# everytime cars_react() changes add vector to a (over several sessions growing) list
observeEvent(cars_react(), {
test_vec2 = unique(cars_react()$hp)
if (file.exists("test_list.Rdata")) {
temp = readRDS("test_list.Rdata")
test_list = c(temp, list(test_vec2))
} else {
test_list = list(test_vec2)
}
saveRDS(test_list, file = "test_list.Rdata")
})
# here we access the reactive data with isolate and make it non-reactive, but can update the values through a button click
text_vec <<- eventReactive(input$capture, {
isolate(unique(cars_react()$hp))
})
# output of our reactive data as table
output$table <- renderTable({
cars_react()
})
# text output of globally assigned non-reactive vector test_vec3 (not changing!)
output$text <- renderText({
test_vec3
})
# you can capture values of reactives with isolate, but then, they don't change anymore
# text output of isolated formely reactive vector unique(cars_react()$hp (not changing!)
output$text2 <- renderText({
isolate(unique(cars_react()$hp))
})
# text output of new reactive vector (changes when input$capture button is clicked)
output$text3 <- renderText({
text_vec()
})
for (i in text_vec)
{
url = "https://oscar.com/prweb/PRRestService/"
parameters<-'{
{
"Reference":"Account"
,"ReferenceValue":""
}'
b<-fromJSON(parameters)
b["ReferenceValue"]=i
r <- POST(url, body = parameters,encode = "json")
r_c<-toJSON(content(r))
print(r_c)
}
}
)
A simple way to get a data frame to persist across all environments used within your Shiny app, is to use the '<<-' assignment instead of the '<-" assignment. This is not a great programming technique, but it may be what you're hoping to find.
# To get a data frame to persist, use
a <<- b
# instead of
a <- b
** Updated answer **
Based on your updated answer, I would wrap you API call into an observeEvent which gets triggered once the action button is pressed. Since you do not provide a working example with some real code, I am not sure whether the example below is of help. I further assume that your for loop is correct and working (on my end, I cannot know without a real API and some real values).
library(dplyr)
library(shiny)
library(httr)
library(jsonlite)
shinyApp(ui = fluidPage(
selectInput(inputId = "cyl",
label = "Number cylinders:",
choices = c("all",sort(unique(mtcars$cyl))),
selected = "all"),
actionButton("capture",
"capture value")
), # closes fluidPage
server = function(input, output) {
# some example reactive data
cars_react <- reactive({
mtcars %>%
filter(cyl == input$cyl | input$cyl == "all")
})
# here we access the reactive data with isolate and make it non-reactive, but can update the values through a button click
observeEvent(input$capture, {
for (i in unique(cars_react()$hp))
{
url = "https://oscar.com/prweb/PRRestService/"
parameters<-'{
"Reference":"Account"
,"ReferenceValue":""
}'
b<-fromJSON(parameters)
b["ReferenceValue"]=i
r <- POST(url, body = parameters,encode = "json")
r_c<-toJSON(content(r))
print(r_c)
}
})
}
)
Old answer
It is not clear from your question how, where and how often you want to use the vector of your reactive data frame. But it is an important question, since the concept of reactivity and how to access it is very hard to grasp when you come from a pure non reactive R environment.
Below is a simple example app which shows how to access vectors in reactive data frames, and how they could be used.
I hope it helps to get a better understanding of reactivity in shiny.
library(dplyr)
library(shiny)
shinyApp(ui = fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "cyl",
label = "Number cylinders:",
choices = c("all",sort(unique(mtcars$cyl))),
selected = "all"),
actionButton("capture",
"capture value")
), # closes sidebarPanel
mainPanel(
tableOutput("text"),
tableOutput("text2"),
tableOutput("text3"),
tableOutput("table")
) # closes mainPanel
) # closes sidebarLayout
), # closes fluidPage
server = function(input, output) {
# some example reactive data
cars_react <- reactive({
mtcars %>%
filter(cyl == input$cyl | input$cyl == "all")
})
# simply global assignment of a reactive vector
observeEvent(cars_react(), {
# here is a globally assigned vector taken from the reactive data
# reused in a render statement it will not react to change, since it is not reactive
test_vec3 <<- unique(cars_react()$hp)
})
# here a file is written to the working directory of your shiny app
# everytime cars_react() changes write (and overwrite) vector to a file
observeEvent(cars_react(), {
test_vec = unique(cars_react()$hp)
saveRDS(test_vec, file = "test_vec.Rdata")
})
# same as above but the file is gradually growing and not overwritten
# everytime cars_react() changes add vector to a (over several sessions growing) list
observeEvent(cars_react(), {
test_vec2 = unique(cars_react()$hp)
if (file.exists("test_list.Rdata")) {
temp = readRDS("test_list.Rdata")
test_list = c(temp, list(test_vec2))
} else {
test_list = list(test_vec2)
}
saveRDS(test_list, file = "test_list.Rdata")
})
# here we access the reactive data with isolate and make it non-reactive, but can update the values through a button click
text_vec <- eventReactive(input$capture, {
isolate(unique(cars_react()$hp))
})
# output of our reactive data as table
output$table <- renderTable({
cars_react()
})
# text output of globally assigned non-reactive vector test_vec3 (not changing!)
output$text <- renderText({
test_vec3
})
# you can capture values of reactives with isolate, but then, they don't change anymore
# text output of isolated formely reactive vector unique(cars_react()$hp (not changing!)
output$text2 <- renderText({
isolate(unique(cars_react()$hp))
})
# text output of new reactive vector (changes when input$capture button is clicked)
output$text3 <- renderText({
text_vec()
})
}
)
Here is the context :
library(shiny)
liste_statut <- c("A","B","C")
ui <- shinyUI(fluidPage(uiOutput("testUI")))
server <- function(input, output, session) {
output$testUI <- renderUI({
navbarPage(
title = "Test",
tabPanel(icon = icon("users"), 'Test',
sidebarPanel(
# Statut
checkboxGroupInput("statut", "Statut", liste_statut, liste_statut),
checkboxInput('selectall_statut', 'Tout / Aucun', T))))
})
# observe({
# updateCheckboxGroupInput(
# session, 'statut', choices = liste_statut,
# selected = if (input$selectall_statut) liste_statut
# )
# })
}
shinyApp(ui = ui, server = server)
I would like to use my checkbox All/None (in comment lines) properly cause in this case i have a "Warning: Error in if: argument is of length zero". Where should i put it or maybe should i redefine properly something in the UI part?
I willingly use the renderUI/uiOutput option (contrary to the "standard mode" ui/server) because in future, i will add an authentification module, so be able to display several "panels" according to user.
Thanks and sorry for my terrible english :).
The following works for me:
library(shiny)
liste_statut <- c("A","B","C")
ui <- shinyUI(fluidPage(uiOutput("testUI")))
server <- function(input, output, session) {
output$testUI <- renderUI({
navbarPage(
title = "Test",
tabPanel(icon = icon("users"), 'Test',
sidebarPanel(
# Statut
checkboxGroupInput("statut", "Statut", liste_statut, liste_statut),
checkboxInput('selectall_statut', 'Tout / Aucun', T))))
})
observeEvent(input$selectall_statut,{
val <- liste_statut
if(!input$selectall_statut)
val <- character(0)
updateCheckboxGroupInput(
session, 'statut',
selected = val
)
})
}
I initially tried selected = ifelse(input$selectall_statut, liste_statut, character(0)) instead of the intermediate variable val. However, ifelse() only returned a single value, not a vector.
If you are going to do this many times over, then I would recommend a custom ifelse function. Perhaps something like the following:
ifelse2 <- function(test, yes, no){
if(test)
return(yes)
return(no)
}
I'm developping an R Shiny-based application.
I want to keep my input consistent with available data, thus I update the selected values in selectInput.
When I change selected value in input 1, then the value of input 2 is updated, then the data is updated (just once). OK
BUT if I change selected value in input 2, then the data is updated, then the value of input 1 is updated, then the data is updated AGAIN.
Check out the "check latest_value" that is printed twice.
Initially I used renderUI rather than updateSelectInput, but at initialisation, the data is computed twice.
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type)),
uiOutput('plant_ui'),
DTOutput('plot')
),
server = function(input, output) {
data=reactive({
# req(input$type)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
latest_plant_value=reactive({
if(is.null(input$plant))data()$Plant[1]
else input$plant
})
output$plant_ui=renderUI({
sub_data=data()
selectInput(inputId = 'plant',"filtre par plant",choices = unique(sub_data$Plant),
selected=latest_plant_value())
})
output$plot <- renderDT({
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
That's why I decided to use updateSelectInput based on this Alternate control of a sliderInput between a derived value and user selected value but the sequential structure of the code makes the data to be computed twice when I change input 2 value.
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du type',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
data=reactive({
# req(input$type)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
observeEvent(input$type,{
print("update type changed")
updateSelectInput(session, "plant",
selected = unique(my_data%>%filter(Type%in%input$type)%>%.$Plant)[1])
})
observeEvent(input$plant,{
print("update plant changed")
updateSelectInput(session, "type",
selected = unique(my_data%>%filter(Plant%in%input$plant)%>%.$Type)[1])
})
output$plot <- renderDT({
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
Fixes like this one don't work in that case because I'm not trying to achieve the same thing three interdependent selectInput in R/Shiny application
I want the default selected value of each input to be consistent so that the filter returns at least 1 value. This of any input I change.
One way to get around this is to create a reactiveVal that tells the app that an updating operation is in progress, and require data to wait until that flag returns to False before running.
I've added 5 lines to your second shiny app:
To server():
# Create update in progress flag
updating_type_inprogress <- reactiveVal(FALSE)
To observeEvent(input$type ...:
# When type is changed, set flag to TRUE
updating_type_inprogress(TRUE)
To observeEvent(input$plant ...:
# Once this function has run, the updating operation is done
updating_type_inprogress(FALSE)
To data():
# Stops updating data() if the in-progress flag is TRUE
req(!updating_type_inprogress())
To renderDT():
# Stops updating renderDT() if the in-progress flag is TRUE
# this is probably optional unless there's resource-intensive code
# that doesn't depend on changes in data()
req(!updating_type_inprogress())
Here's the whole code:
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du type',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
data=reactive({
req(!updating_type_inprogress())
print(input$type)
print(input$plant)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
observeEvent(input$type,{
updating_type_inprogress(TRUE)
updateSelectInput(session, "plant",
selected = unique(my_data%>%filter(Type%in%input$type)%>%.$Plant)[1])
})
observeEvent(input$plant,{
updating_type_inprogress(FALSE)
updateSelectInput(session, "type",
selected = unique(my_data%>%filter(Plant%in%input$plant)%>%.$Type)[1])
})
updating_type_inprogress <- reactiveVal(FALSE)
output$plot <- renderDT({
req(!updating_type_inprogress())
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
As you can see, when you change input$type, the data() and renderDT() functions only run once with the correctly updated values:
[1] "check latest_value"
[1] "Quebec"
[1] "Qn1"
[1] "check latest_value"
[1] "Mississippi"
[1] "Mn1"
[1] "check latest_value"
[1] "Quebec"
[1] "Qn1"
Interesting problem and not easy to solve! Interestingly, what you are asking for is not what you need. Observation:
If the user selects Qn2 while Input1 is "Mississippi", you first set Input1 on Quebec and then hard set Input2 on Qn1, changing the choise of the user. This is bad.
Datatable is always updated once any of the two inputs changes, hence the many re-calculations of the table.
The solution therefore is twofold:
Don't overwrite the user's choice of e.g. Qc2 to Qc1. I used an if condition for that.
Install a watchguard to only update
the datatable when its contents actually changed. I do this with a reactiveVal() that I only update when the choice of the two inputs was valid (i.e. when the result set is greater than 0).
See the result below. Watch the console output to observe the decisions.
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du plant',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
latest_data <- reactiveVal(my_data)
observe({
result <- my_data %>% filter(Type %in% input$type, Plant %in% input$plant)
if(nrow(result) > 0){
latest_data(result)
}else{
cat(format(Sys.time(), "%H:%M:%S"), "Didn't update the dataframe because the choice was not valid.\n")
}
})
observeEvent(input$type,{
if(! input$plant %in% my_data$Plant[my_data$Type == input$type]){
old <- input$plant
new <- my_data %>% filter(Type %in% input$type) %>% slice(1) %>% pull(Plant) %>% as.character()
updateSelectInput(session, "plant", selected = new)
cat(format(Sys.time(), "%H:%M:%S"), "Updated input$plant from", old, "to", new, "so that it represents a valid choice for", input$type, "\n")
}else{
cat(format(Sys.time(), "%H:%M:%S"), "Didn't update input$plant", input$plant, "because it is a valid choice for", input$type, "already\n")
}
})
observeEvent(input$plant,{
updateSelectInput(session, "type",
selected = my_data %>% filter(Plant %in% input$plant) %>% slice(1) %>% pull(Type))
})
output$plot <- renderDT({
cat(format(Sys.time(), "%H:%M:%S"), "updating datatable to only include", isolate(input$plant), "and", isolate(input$type), "\n\n")
latest_data()
datatable(latest_data())
})
}
)
I try to make a shiny module to present data from dataframes using the DT package. I would like to use a module to have a standard set up of DT-table options like language and others.
I want the user to be able to select different subsets of the data interactively and thereafter be able to see the data as a DT-table. The selection of the subset will be generated outside the module because I would like the subset to be available for other uses, for example to be exported to a csv-file.
This works as intended when I don't use a module for making the DT table. When I put the code inside a module, a table is produced when the app starts. But when the selection criteria are changed, the table don't update.
I have included an app illustrating the problem. Table 1 is generated without using shiny module and updates as expected when the selection changes. Table 2 is output using the module and don't update when the selection is changed.
I'm running R-studio 1.1.463, R version 3.5.2 and DT version 0.5.
require("DT")
require("shiny")
# module for presenting data using DT
showDTdataUI <- function(id) {
ns <- NS(id)
tagList(
DT::dataTableOutput(ns("table"))
)
}
showDTdata <- function(input, output, session, DTdata) {
output$table <- renderDataTable({
DT::datatable(DTdata)
})
}
# User interface
ui <-
fluidPage(
sidebarLayout(
sidebarPanel(id="DT",
width = 4,
helpText(h4("Select")),
selectInput("selectedSpecies", label = "Species",
choices = c("setosa","versicolor","virginica"),
selected = "versicolor")
),
mainPanel(
h3("Table 1. Presenting selected data from Iris" ),
DT::dataTableOutput("table"),
h5(br("")),
h3("Table 2. Presenting selected data from Iris using shiny module"),
showDTdataUI(id="testDTModule")
)
)
)
# Define server logic ----
server <- function(session, input, output) {
selectedIris <- reactive ( {
selected <- iris[which(iris$Species==input$selectedSpecies),]
selected
})
output$table <- renderDataTable({
DT::datatable(selectedIris())
})
callModule(showDTdata, id="testDTModule", DTdata=selectedIris())
}
# Run the app ----
shinyApp(ui = ui, server = server)
You have to pass the reactive conductor in showDTdata:
showDTdata <- function(input, output, session, DTdata) {
output$table <- renderDataTable({
DT::datatable(DTdata()) # not datatable(DTdata)
})
}
callModule(showDTdata, id="testDTModule", DTdata=selectedIris) # not DTdata=selectedIris()
Does this do what you want? I removed your functions and added the selection ='multiple' to table 1 (tableX) so that we can then listen to tableX_rows_selected
P.S.: I have noticed that if you first load DT and then shiny, that the whole app won't work anymore. This is a bit weird since we call all datatable functions with DT::... but, you do get a warning message that some parts of DT are masked by shiny or viceversa.
require("shiny")
require('DT')
# User interface
ui <-
fluidPage(
sidebarLayout(
sidebarPanel(id="DT",
width = 4,
helpText(h4("Select")),
selectInput("selectedSpecies", label = "Species",
choices = c("setosa","versicolor","virginica"),
selected = "versicolor")
),
mainPanel(
h3("Table 1. Presenting selected data from Iris" ),
DT::dataTableOutput("tablex"),
br(),
h3("Table 2. Presenting selected data from Iris using shiny module"),
DT::dataTableOutput("table2")
)
)
)
# Define server logic ----
server <- function(session, input, output) {
values <- reactiveValues(rowselect = numeric())
selectedIris <- reactive ( {
selected <- iris[which(iris$Species==input$selectedSpecies),]
selected
})
output$tablex <- renderDataTable({
DT::datatable(selectedIris(), selection = 'multiple')
})
IrisSelected <- reactive({
df <- iris[c(input$tablex_rows_selected), ]
df
})
output$table2 <- renderDataTable({
req(nrow(IrisSelected()) > 0)
DT::datatable( IrisSelected())
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Without knowing of the shiny module approach, I would have probably written it like a normal function. The app below works but I am curious now after seeing the answer by #Stephane what the advantages are of using callModule approach over regular function approach
require("DT")
require("shiny")
makeTable <- function(dataframe) { DT::datatable(dataframe) %>%
formatStyle(names(dataframe), background = '#fff')
}
# User interface
ui <-
fluidPage(
sidebarLayout(
sidebarPanel(id="DT",
width = 4,
helpText(h4("Select")),
selectInput("selectedSpecies", label = "Species",
choices = c("setosa","versicolor","virginica"),
selected = "versicolor")
),
mainPanel(
dataTableOutput('Table1')
)
)
)
# Define server logic ----
server <- function(session, input, output) {
selectedIris <- reactive ( {
selected <- iris[which(iris$Species==input$selectedSpecies),]
selected
})
output$Table1 <- renderDataTable(makeTable(selectedIris()))
}
# Run the app ----
shinyApp(ui = ui, server = server)