I have a small app like this:
require(shiny)
require(shinyjs)
require(rhandsontable)
shinyApp(ui = fluidPage(useShinyjs(),
div(id = 'div1',
titlePanel("RHOT - Form"),
fluidRow(column(width = 3,selectizeInput("Trialid","What Iteration is this?",choices = c('1','2-3','4-7','8-15'))),
column(width = 3,textInput("Techie_Name","Your Name",value='EE')),
column(width = 3,textInput("lab_id","LAB ID",value='NA')),
column(width = 3,textInput("email","Your Email ID",value='eeshanchatterjee#gmail.com'))
),
h4('Observations:'),
rHandsontableOutput("handsontable_obs"),
actionButton("SaveObs", "Save Observations")
),
shinyjs::hidden(div(id = 'SubmitMsg',
h3("Thanks for submitting the Observations!"),
actionLink('addNextObs',"Add Another Observation"))
)
),
server = function(input, output,session){
output$handsontable_obs = renderRHandsontable({
rhandsontable(data.frame(Obs_itr = c(1:5),
Val1 = rep(0,5),
Val2 = rep(0,5)))
})
observeEvent(input$SaveObs,{
shinyjs::reset("div1")
shinyjs::hide("div1")
shinyjs::show("SubmitMsg")
})
observeEvent(input$addNextObs,{
shinyjs::show("div1")
shinyjs::hide("SubmitMsg")
})
}
)
When I run it, I can edit the input fields as well as the tables. Upon hitting the save button, this div resets (using shinyjs::reset), hides, and a hidden thank you div shows up.
Clicking another action link on the 2nd div brings the original one back on.
Now, ass the input fields are reset to their default values, except the handsontable.
Question is, how do I ensure the handsontable resets to default values along with the other input fields?
Adding a reactiveValue and a bit more detail on the rhandsontable gets the job done, but this may not be very efficient:
shinyApp(ui = fluidPage(useShinyjs(),
div(id = 'div1',
titlePanel("RHOT - Form"),
fluidRow(column(width = 3,selectizeInput("Trialid","What Iteration is this?",choices = c('1','2-3','4-7','8-15'))),
column(width = 3,textInput("Techie_Name","Your Name",value='EE')),
column(width = 3,textInput("lab_id","LAB ID",value='NA')),
column(width = 3,textInput("email","Your Email ID",value='eeshanchatterjee#gmail.com'))
),
h4('Observations:'),
rHandsontableOutput("handsontable_obs"),
actionButton("SaveObs", "Save Observations")
),
shinyjs::hidden(div(id = 'SubmitMsg',
h3("Thanks for submitting the Observations!"),
actionLink('addNextObs',"Add Another Observation"))
)
),
server = function(input, output,session){
vals <- reactiveValues(reset=TRUE)
output$handsontable_obs = renderRHandsontable({
input$addNextObs
if(isolate(vals$reset) | is.null(input$handsontable_obs)) {
isolate(vals$reset <- FALSE)
df <- data.frame(Obs_itr = c(1:5),
Val1 = rep(0,5),
Val2 = rep(0,5))
} else df <- hot_to_r(input$handsontable_obs)
rhandsontable(df)
})
observeEvent(input$SaveObs,{
shinyjs::reset("div1")
shinyjs::hide("div1")
shinyjs::show("SubmitMsg")
vals$reset <- TRUE
})
observeEvent(input$addNextObs,{
shinyjs::show("div1")
shinyjs::hide("SubmitMsg")
})
}
)
Related
I am new to shiny and am currently trying to develop my first shinyapp.
This apps contains multiple actionButtons and nested observeEvents statements, which I think are the cause of my problem.
The app should allow the user to add observations of species by clicking on a add button, that updates the UI. Within each observation, more details can be asked, but I only showed the species name in the REPREX below (textinput).
Each observation can be deleted individually via a delete button.
Until here, it works! However, I also want a modal dialog to confirm the deletion when the delete button is clicked. To do this, I used a nested observeEvent and it doesn't seem to work (or maybe only for the first time). What am I doing wrong ?
Thanks in advance to anyone who tries to help me.
library(shiny)
library(random)
ui <- fluidPage(
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete")
)
)
)
)
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input[[removeSpeciesId]], {
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
# Delete observation if user confirms
observeEvent(input$confirm, {
id_to_remove <- substring(removeSpeciesId,1, nchar(removeSpeciesId)-8)
rv$GridId_list <- rv$GridId_list[rv$GridId_list!=id_to_remove]
removeUI(selector = paste("#", id_to_remove, sep = ""))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
})
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
Referencing dynamic input id's is a pain. I find it best to add a last clicked input identifier to reference. You can add a class to those inputs to just listen to them and not others in your app:
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});")))
That little piece of code will allow you to get an input$last_btn id, that you can use for your event listeners. In this case you don't need to nest your event listeners; it is better to think about the events in sequence and program those reactions. So, with some tweakings in your code, your app now looks like this:
library(shiny)
library(random)
ui <- fluidPage(
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});"))),
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete", class="needed")
)
)
)
)
})
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input$last_btn, {
observeEvent(input[[input$last_btn]] > 0,{#We want the modal to show when any "remover" id is clicked
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
})
}, ignoreNULL = TRUE, ignoreInit = TRUE)
# Delete observation if user confirms
observeEvent(input$confirm, {
#The following selector is for the parent id of the parent id of the last_btn id
removeUI(selector = paste0("div:has(>div:has(>#", input$last_btn, "))"))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
I'm currently building a Shiny dashboard in R and I'm currently building a mechanism that would allow some user to comment some datatable inside it, and I got this mechanism done and sorted (as you can see on the code below). My problem is when I add the comments and refresh the page, the comments go away (as they should). Is there any way to save the datatable and keep the changes saved for any user to see, even after I refresh the dashboard page?
Thanks if you read this far :)
Please find a reproducible example below:
library(shiny)
library(DT)
dt <- data.table(
ID = c('Order 1','Order 2', 'Order 3', 'Order 4'),
Name = c('John','Peter','Anna','Richard')
)
ui <- fluidPage(
fluidRow(
column(2, pickerInput(inputId = 'selectID',
label = 'Select order ID to comment on:',
choices = c('Order 1','Order 2', 'Order 3', 'Order 4'),
selected='',
multiple=FALSE)),
column(2, textInput(inputId = 'comment',
label ='Please add comment in the text box:',
value = "", width = NULL,
placeholder = NULL))
,
column(1, actionButton(inputId = "button",
label = "Add Comment",
size = "extra-small",
style = "margin-top:25px"
)
)
),
fluidRow(
column(12,
dataTableOutput('data')
)
)
)
server <- function(input, output, session){
dt_comments <- reactiveVal({
data.table(
ID = character(0),
Comment = character(0),
stringsAsFactors = FALSE
)
})
dt_current <- reactive({
dt <- dt
## merge with current comments
if(nrow(dt_comments()) > 0)
dt <- merge(dt, dt_comments(), by = "ID", all.x = TRUE)
return(dt)
})
observeEvent(input$button, {
req(input$selectID)
## update df_comments by adding comments
dt_comments_new <- rbind(dt_comments(),
data.table(ID = input$selectID, Comment = input$comment)
)
## if duplicated id's keep only most recent rows
dt_comments_new <- dt_comments_new[!duplicated(dt_comments_new$ID, fromLast = TRUE), , drop = FALSE]
dt_comments(dt_comments_new)
})
output$data <- DT::renderDataTable({
req(dt_current())
dt2 <- dt_current()
## show comments if non-empty
showComments <- is.null(dt2$Comment) || !all(is.na(dt2$Comment))
DT::datatable(dt2,
editable = TRUE,
options = list(
columnDefs = list(
list(targets = ncol(dt2), visible = showComments)
)
)
)
})
}
shinyApp(ui = ui, server = server)
I am trying to reset selectizeInput selections upon actionButton input.
Please see the following code, in which I cannot get the eventReactive to function:
library(shiny)
ui <- fluidPage(
column(width = 4,algin = "center", uiOutput("choose_Number")) ,
br(),
column(width = 4, algin = "center",div(
align = "center", actionButton('delete','Delete Number(s)',style="color: #fff; background-color: #53C1BE")))
)
server <- function(input, output, session) {
output$choose_Number <- renderUI({
selectizeInput("choose_Number", "Select Number", as.list(c(1,2,3,4)),selected = c(''), options=list(create=TRUE,'plugins' = list('remove_button'),
persist = FALSE), multiple = TRUE)
})
##### I am trying to reset the selectizeInput upon input from the Delete button
eventReactive(input$delete, {updateSelectizeInput("choose_Number", "Select Number", as.list(c(1,2,3,4)), selected = c(''),options=list(create=TRUE,'plugins' = list('remove_button'),
persist = FALSE), multiple = TRUE)} )
}
shinyApp(ui, server)
Thank you.
The biggest issue was that the session was missing. You can omit the session argument if you want to but then you need to name all other arguments because session is the first in line. Second issue was that you can set multiple in selectizeInput but not later when using updateSelectizeInput() to change it.
Minor improvements: the manual recommends using character() to deselect the current choice. As mentioned by #YBS you do not provide a reactive value so that observeEvent is the better choice here.
PS: are you sure you want to centre align the columns? It looks strange.
library(shiny)
ui <- fluidPage(
column(width = 4, align = "center", uiOutput("choose_Number")),
br(),
column(width = 4, align = "center", div(
align = "center",
actionButton('delete', 'Delete Number(s)', style="color: #fff; background-color: #53C1BE"))
)
)
server <- function(input, output, session) {
output$choose_Number <- renderUI({
selectizeInput("choose_Number", "Select Number", as.list(c(1,2,3,4)), selected = character(),
options = list(create=TRUE,'plugins' = list('remove_button'), persist = FALSE),
multiple = TRUE)
})
##### I am trying to reset the selectizeInput upon input from the Delete button
observeEvent(input$delete, {
updateSelectizeInput(session, "choose_Number", choices = as.list(1:4),
selected = character(0),
options = list(create=TRUE, 'plugins' = list('remove_button'), persist = FALSE))
})
}
shinyApp(ui, server)
An instance of collapsibleTreeSummary() becomes distorted as it's rendered more than once in the same modelDialog() within a Shiny app. The tree is compressed after the modal and tree are rendered more than once, making it difficult to comprehend.
For example, this code displays a horizontal tree, representing an artificial company's headcount by division.
Modified
library(shiny)
library(collapsibleTree)
choice1_data = data.frame(
V1 = c(rep("Corporate",3),"Sales"),
V2 = c("Finance","Marketing","HR","Sales"),
V3 = c(110,43,12,243)
)
choice2_data = data.frame(
V1 = c(rep("Corporate",3),"Sales","IT"),
V2 = c("Finance","Marketing","HR","Sales","IT"),
V3 = c(110,43,12,243,22)
)
choice3_data = data.frame(
V1 = c(rep("Corporate",3),"Sales","IT","Management"),
V2 = c("Finance","Marketing","HR","Sales","IT","Management"),
V3 = c(110,43,12,243,34,22)
)
ui <- fluidPage(
mainPanel(
br(),
actionButton("mainButton","Click me"),
br(),
uiOutput("jobcodeRadio")
)
)
server <- function(input,output,session){
output$jobcodeRadio = renderUI({
div(
style="display:flex;align-items:top;",
div(
class="jobcodeRadioStyle",
div(
radioButtons(
"JOBCODE",
"Explore a Role of Interest:",
choiceNames=c("choice 1","choice 2","choice 3"),
choiceValues=c("choice_1","choice_2","choice_3")
)
)
)
)
})
output$tree = renderCollapsibleTree({
selected_choice=input$JOBCODE
if(selected_choice=="choice_1")
{
df=choice1_data
}
else if(selected_choice=="choice_2"){
df=choice2_data
}
else{
df=choice3_data
}
collapsibleTreeSummary(
df,
root="Fake Corporation",
hierarchy=c("V1","V2"),
zoomable=T,
attribute="V3",
nodeSize="V3",
tooltip=T,
linkLength=250,
fontSize=12
)
})
plotModal <- function(failed = FALSE) {
modalDialog(
title = NULL,
size="l",
easyClose=T,
# Display the tree
fluidPage(collapsibleTreeOutput("tree"))
)
}
observeEvent(input$mainButton,{
showModal(plotModal())
})
}
shinyApp(ui,server)
1st time being rendered
Subsequent times being rendered
I appreciate if anyone can help me.
I'm not sure why this happens, but the proper way of displaying UI (apart from text) inside a modalDialog() would be to create a function to trigger the modalUI as mentioned here.
So I just moved your collapsibleTreeOutput and renderCollapsibleTree outside the button's observe event and included the plotModal() inside the button's observe event.
This function will generate the proper UI.
Here's the modified code
library(shiny)
library(collapsibleTree)
df = data.frame(
V1 = c(rep("Corporate",3),"Sales"),
V2 = c("Finance","Marketing","HR","Sales"),
V3 = c(110,43,12,243)
)
ui <- fluidPage(
mainPanel(
br(),
actionButton("mainButton","Click me")
)
)
server <- function(input,output,session){
output$tree = renderCollapsibleTree({
collapsibleTreeSummary(
df,
root="Fake Corporation",
hierarchy=c("V1","V2"),
zoomable=T,
attribute="V3",
nodeSize="V3",
tooltip=T,
linkLength=250,
fontSize=12
)
})
plotModal <- function(failed = FALSE) {
modalDialog(
title = NULL,
size="l",
easyClose=T,
# Display the tree
fluidPage(collapsibleTreeOutput("tree"))
)
}
observeEvent(input$mainButton,{
showModal(plotModal())
})
}
shinyApp(ui,server)
Hope this helpes!
P.S I'm still not sure why the UI is rendered from the top when clicked >1 times
Issue has been resolved when I render data tree in shiny renderUI({}) function instead of in fluidPage() :
plotModal <- function(failed = FALSE) {
modalDialog(
title = NULL,
size="l",
easyClose=T,
# Display the tree
renderUI(collapsibleTreeOutput("tree"))
)
}
I'm having trouble iteratively loading and filtering a datatable in Shiny. The ideal workflow would be as follows:
User pushes button to confirm loading data
Data is retrieved from MySql query. Note this should only occur once
(optional) filter buttons/sliders become visible/available
User interacts with buttons/sliders to filter datatable
1 and 2 work fine, but I am having particular issue with 4 (also any input for 3 would be appreciated as well).
The initial code that is not working is as follows:
get_data=function(){ # note that this is for sample purpose, real function is MySQL query
df=data.frame(x=1:10,Age=1:100)
print("loading data...")
return(df)
}
ui = bootstrapPage(
fluidPage(
fluidRow(
actionButton(
inputId = "confirm_button",
label = "Confirm"
)
)
,
fluidRow(
column(4,
sliderInput("slider_age", label = h4("Age"), min = 0,
max = 100, value = c(0, 100))
)
),
hr(),
fluidRow(
DT::dataTableOutput("all_background_table")
)
)
)
server = function(input, output){
observeEvent(input$confirm_button, {
req(input$confirm_button)
output$all_background_table <- DT::renderDataTable({
all_background=get_data() # <- MySQL function to laod data
# if all_background filter function put here:
#--> data is re-loaded by MySQL query
# if all_background filter function is put here surrounded by observeEvent(input$slider_age, {...:
#--> there is no change when input$slider_age is changed
datatable(all_background,
rownames = FALSE,
style = "bootstrap")
})
})
observeEvent(input$slider_age, {
## this will throw an error requiring all_background
#--> Error in observeEventHandler: object 'all_background' not found
req(input$confirmation_load_pts)
all_background=all_background[(all_background$Age > as.numeric(input$slider_age[1]) & all_background$Age < as.numeric(input$slider_age[2])),]
})
}
shinyApp(ui, server)
I am not sure about get_data(), but I will be using df to make it easier. With eventReactive you can create a new data frame after using the slider and only after clicking on the confirm button. Your observeEventwould not be necessary for this scenario.
library(shiny)
library(DT)
get_data=function(){ # note that this is for sample purpose, real function is MySQL query
df=data.frame(x=1:10,Age=1:100)
print("loading data...")
return(df)
}
ui = bootstrapPage(
fluidPage(
fluidRow(
actionButton(
inputId = "confirm_button",
label = "Confirm"
)
)
,
fluidRow(
column(4,
sliderInput("slider_age", label = h4("Age"), min = 0,
max = 100, value = c(0, 100))
)
),
hr(),
fluidRow(
DT::dataTableOutput("all_background_table")
)
)
)
server = function(input, output){
test <- eventReactive(input$confirm_button, {
df=get_data()
})
observeEvent(input$confirm_button, {
output$all_background_table <- DT::renderDataTable({
df=test()
all_background2=df[(df$Age > as.numeric(input$slider_age[1]) & df$Age < as.numeric(input$slider_age[2])),]
datatable(all_background2,
rownames = FALSE,
style = "bootstrap")
})
})
}
shinyApp(ui, server)