I have a shiny app in which a datatable is displayed and upon a click on a row, a modalDialog opens in which I embedded a glide from shinyglide. This worked fine until I introduced a next_condition to the second screen. Now whenever the first box is selected (or after deselecting everything and selecting again), the glide jumps back to the first screen. If you now change the option on the first screen, then the behaviour gets very strange altogether. I have no idea what causes this or where to start to fix it. Below is a (not so small) mockup example of my app which includes the observed behaviour (if you uncomment the next_condition, everything works fine). The important part to the problem is the server part in the end, the rest is just setup to make the app fully functional.
UPDATE:
I have tried to shorten the example by getting rid of the datatable and the modalDialog, but I could not replicate the behaviour this way. So it seems to me the interaction between modalDialog and glide is at fault. I was however able to shorten it a tiny bit by getting rid of the reactive variables without changing the result.
UPDATE 2:
Also posted it here, but the answer has not (yet) worked for me.
Code:
Library Calls:
library(shiny)
library(shinydashboard)
library(shinyBS)
library(shinyglide)
library(shinyWidgets)
library(shinyjs)
library(DT)
UI:
ui <- dashboardPage(skin = 'purple',
dashboardHeader(title = "Shinyglide Example"),
dashboardSidebar(disable = TRUE),
dashboardBody(
useShinyjs(),
tags$head(tags$style("#modal1 .modal-body {min-height:750px; padding: 10px}
#modal1 .modal-dialog { width: 1280px; height: 1280px;}"
)),
fixedRow(
column(width = 12,
box(title = "I am the table!",width = NULL,status = 'info',solidHeader = TRUE,
DT::dataTableOutput("table")))
)
)
)
Setup Functions:
render_my_table <- function(){
col_a <- c("A","B","C","D","E")
col_b <- c("Human","Cat","Human","Dog","Dog")
col_c <- c(35,7,42,5,11)
col_d <- c("Earth","Earth","Earth","Earth","Mars")
my_data <- data.frame(letter = col_a,species = col_b,age = col_c,planet = col_d)
my_data <- datatable(my_data,colnames = c("ID","Species","Age","Home Planet"),rownames = FALSE,filter = 'top',selection = 'single',
callback = JS("table.on('click.dt','tr',function() {
Shiny.onInputChange('rows',table.rows(this).data().toArray(),{priority:'event'});});"))
return(my_data)
}
pickerinput_choices <- function(my_species){
if(my_species == "Human"){
return(c("Job","Family","Mortgage"))
}else{
return(c("Breed","Owner","Family"))
}
}
advanced_inputs <- function(my_species,my_choiceA){
if(is.null(my_choiceA)){return(0)}
if(my_choiceA == "Job"){
return(checkboxGroupInput("my_checkbox",label = "Type of Jobs",choices = c("Employed","Self-Employed","Apprenticeship")))
}else if(my_choiceA == "Mortgage"){
return(checkboxGroupInput("my_checkbox",label = "Type of Housing",choices = c("Apartment","House")))
}else if(my_choiceA == "Breed"){
return(checkboxGroupInput("my_checkbox",label = "Details",choices = c("Height","Fur","Weight")))
}else if(my_choiceA == "Owner"){
return(checkboxGroupInput("my_checkbox",label = "Details",choices = c("Age","Employed","Children")))
}else{
if(my_species == "Human"){
return(checkboxGroupInput("my_checkbox",label = "Details",choices = c("Partner","Parents","Children","Siblings")))
}else{
return(checkboxGroupInput("my_checkbox",label = "Details",choices = c("Owner","Children","Owners of Children")))
}
}
}
Server:
server <- function(input, output,session) {
glide_modal <- modalDialog(
renderUI({title = tags$span(paste("You have chosen Row",input$rows[1]),style = "font-size: 20px; font-weight: bold")}),
footer = NULL,
easyClose = TRUE,
glide(
id = "my_glide",
controls_position = 'bottom',
height = "800px",
screen(
renderUI({
pickerInput(inputId = "my_pickerinput",h3("Make Choice A"),choices = pickerinput_choices(input$rows[2]),
options = pickerOptions(container = 'body'))
})
),
screen(
renderUI({
tagList(
h3("Make Choice B"),
advanced_inputs(input$rows[2],input$my_pickerinput)
)
}),
next_condition = "(typeof input['my_checkbox'] !== 'undefined' && input['my_checkbox'].length > 0)"
),
screen(
renderText({
paste("You have selected row",input$rows[1],"which is a",input$rows[2],"and have requested information about",
input$my_pickerinput,", especially about",paste(input$my_checkbox,collapse = " and "))
})
)
)
)
output$table <- DT::renderDataTable({
render_my_table()
})
observeEvent(input$rows,{
showModal(tags$div(id="modal1",glide_modal))
})
}
and function call:
shinyApp(ui = ui, server = server)
Fixed by the newest development version thanks to the package author. See the github thread, the code works now as posted in the question.
Related
I have to swtiches. I want each switch to force the other one to FALSE should they become TRUE. (e.g. if you 'toggle' switch1 (= TRUE), and switch2 = TRUE, then switch2 should be changed to FALSE).
This is a simplified version of what I'm trying to achieve:
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
ui <- fluidPage(
materialSwitch(inputId = "switch1", label = "Switch 1", status = "danger"),
materialSwitch(inputId = "switch2", label = "Switch 2", status = "danger")
)
server <- function(input, output, session) {
observeEvent(input$switch1, {
#if (is.null(input$switch1)) return(NULL)
if (input$switch1 == TRUE && input$switch2 == TRUE) {
updateMaterialSwitch(session = session, "switch2", status = "danger", value = FALSE)
}
},
ignoreInit = TRUE)
}
shinyApp(ui, server)
I tried passing the input to a reactive event, but everytime I trigger switch1 after switch2 has been triggered, the app enters an endless loop. Any suggestions?
The end game would be for condition to work both ways, but for now since it doens't even work in one direction I would appreciate some help there.
Please check the following:
library(shiny)
library(shinyWidgets)
ui <- fluidPage(
materialSwitch(inputId = "switch1", label = "Switch 1", status = "danger"),
materialSwitch(inputId = "switch2", label = "Switch 2", status = "danger")
)
server <- function(input, output, session) {
observeEvent(input$switch1, {
if (input$switch1 == TRUE && input$switch2 == TRUE) {
updateMaterialSwitch(session = session, "switch2", value = FALSE)
}
})
observeEvent(input$switch2, {
if (input$switch1 == TRUE && input$switch2 == TRUE) {
updateMaterialSwitch(session = session, "switch1", value = FALSE)
}
})
}
shinyApp(ui, server)
I am trying to design a search feature where you can search via a text input and through check boxes (I am using shinyWidgets), except for some reason, when you hit enter inside the text input it is activating my "ALL/NONE" button.
The goal is that when the ALL/NONE button is hit that it alternates between selecting all of the check boxes and selecting none of them. The issue is that hitting enter in the text box also seems to activate the observe, even when it should only be activating by the button.
library(shiny)
library(shinyWidgets)
Habitat <- c("grass", "water", "stone")
ID <- c(1, 2, 3)
data <- data.frame(ID, Habitat)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
width = 2,
textInput("keyword_search", label = "Search by Keyword"),
uiOutput("h_button"),
uiOutput("habitat_filter")
),
mainPanel(width = 10
))
)
server <- function(input, output, session) {
output$habitat_filter <- renderUI({
habitat_choices <- checkboxGroupInput(inputId = "habitat", label = "",
choices = unique(data$Habitat[!is.na(data$Habitat)]),
selected = unique(data$Habitat[!is.na(data$Habitat)]))
})
output$h_button <- renderUI({
habitat_button <- checkboxGroupButtons(
inputId = "habitat_switch",
choices = "ALL / NONE",
size = "sm",
selected = "ALL / NONE")
})
observe({ #all/none button for habitats
x <- input$habitat_switch
if (!is.null(x)) {
x <- unique(data$Habitat[!is.na(data$Habitat)])
}
else {
x <- character(0)
}
updateCheckboxGroupInput(
session,
"habitat",
label = NULL,
choices = unique(data$Habitat[!is.na(data$Habitat)]),
selected = x
)
})
}
shinyApp(ui = ui, server = server)
Weirdly, this problem seems to go away if it is coded outside of the sidebarLayout. i.e. if the ui side looks like this:
ui <- fluidPage(
textInput("keyword_search", label = "Search by Keyword", width = '100%', placeholder = "Type here to search the archive..."),
uiOutput("h_button"),
uiOutput("habitat_filter")
)
Unfortunately, I need the sidebar so removing it isn't an option for fixing the problem. Does anyone have a solution to prevent these features from being connected? Or an explanation for why this happening?
Replacing my observe for the button with this seems to avoid the problem as suggested here: Select/Deselect All Button for shiny variable selection
observe({ #all/none button for habitats
x <- unique(data$Habitat[!is.na(data$Habitat)])
if (!is.null(input$habitat_switch) && input$habitat_switch >= 0) {
if (input$habitat_switch %% 2 == 0) {
x <- unique(data$Habitat[!is.na(data$Habitat)])
}
else {
x <- character(0)
}
}
updateCheckboxGroupInput(
session,
"habitat",
label = NULL,
choices = sort(unique(data$Habitat[!is.na(data$Habitat)])),
selected = x
)
})
Still no idea what caused this issue initially, but this work around seems good enough
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 need a reactive variable (declared server-side) available after start-up. Using what I learned here How to create a conditional renderUI in Shiny dashboard I tried wrapping in reactive() before defining the UI but no luck. Moving topValuesSelector to the UI inside a conditionalPanel would work except conditional panels apparently do not like the %in% operator (a separate issue that I also tried to resolve w/o success).
if (interactive()) {
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
ui <-
dashboardPage(header = dashboardHeaderPlus(left_menu = tagList(
dropdownBlock(
id = "prefDropdown",
title = "Preferences",
icon = NULL,
badgeStatus = NULL,
checkboxGroupInput(
inputId = "prefDropdown",
label = NULL,
choices = c("Pareto",
"Legend on chart",
"Cases/1K uniques",
"Top 10 only"),
selected = c("Pareto", "Cases/1K uniques", "Top 10 only")
),
uiOutput("topValues")
)
)),
dashboardSidebar(),
dashboardBody(fluidRow(box(
title = "Top",
textOutput("topN")
))))
server <- function(input, output) {
topValuesSelector <- reactive({
if ("Top 10 only" %in% input$prefDropdown) {
numericInput(
inputId = "topValues",
label = NULL,
width = "25%",
value = 10,
min = 1,
max = 30,
step = 1
)
}
})
output$topValues <- renderUI({
topValuesSelector()
})
observe({
if ("Top 10 only" %in% input$prefDropdown) {
output$topN <- renderText(input$topValues)
} else{
output$topN <- renderText(100)
}
})
}
shinyApp(ui, server)
}
The intent is for the initial value of "topValues" to be 10 with this value immediately available. However, no value is available which causes an error. Using req() avoids the error by pausing execution but that is not a viable approach because "topValues" is needed for a plot. So no plot until selecting "prefDropdown".
It looks like the problem is that input$topValues does not exist until you click on the Preferences button. Since the UI element isn't needed it hasn't been created yet.
In order to work around that you can create a variable that detects whether or not the input is available and if not use a default value.
if (interactive()) {
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
ui <-
dashboardPage(header = dashboardHeaderPlus(left_menu = tagList(
dropdownBlock(
id = "prefDropdown",
title = "Preferences",
icon = NULL,
badgeStatus = NULL,
checkboxGroupInput(
inputId = "prefDropdown",
label = NULL,
choices = c("Pareto",
"Legend on chart",
"Cases/1K uniques",
"Top 10 only"),
selected = c("Pareto", "Cases/1K uniques", "Top 10 only")
),
uiOutput("topValues")
)
)),
dashboardSidebar(),
dashboardBody(fluidRow(box(
title = "Top",
textOutput("topN")
))))
server <- function(input, output) {
## We want to use the same default value in two places so create a var
default_value <- 10
topValuesSelector <- reactive({
if ("Top 10 only" %in% input$prefDropdown) {
numericInput(
inputId = "topValues",
label = NULL,
width = "25%",
value = default_value, ## Change to use the default value
min = 1,
max = 30,
step = 1
)
}
})
output$topValues <- renderUI({
topValuesSelector()
})
## Create a variable that is the default value unless the input is available
myTopN <- reactive({
if(length(input$topValues)>0){
return(input$topValues)
}
return(default_value)
})
observe({
if ("Top 10 only" %in% input$prefDropdown) {
# output$topN <- renderText(input$topValues)
output$topN <- renderText(myTopN()) ## Use our new variable instead of the input directly
} else{
output$topN <- renderText(100)
}
})
}
shinyApp(ui, server)
}
There are a couple of other things going on with your code. Notice that "Top 10 only" %in% input$prefDropdown will not do what you think it is doing. You have to check to see if "Top 10 only" is TRUE... I'll leave you there to start another question if you get stuck again.
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")
})
}
)