Javascript in Shiny app only loads once - shiny

Edit: Generalized question by finding reproducible example
The app below has two tabPanels, each with some Javascript within a sidebarPanel. I expect the Javascript in Tab 1 to run anytime Tab 1 is activated (i.e., when I initially launch the app and when I navigate back to Tab 1 from Tab 2). Also, I expect the Javascript in Tab 2 to run anytime I navigate to that tab.
Instead, the Javascript for both tabs runs immediately upon launching the app and then never again.
As context, I constructed this example because I ran into this problem while trying to use Javascript to place an Amazon Associates ad in my Shiny app.
Reproducible example
library(shiny)
ui = navbarPage( "",
tabPanel( "Tab 1",
mainPanel(
wellPanel("Blah blah blah"),
width = 6
),
sidebarPanel(
# only runs once, like the ads
HTML('<script type="text/javascript"> alert("Tab 1 is talking"); </script>')
# # runs every time
#HTML('<b> test </b>')
, width=6 )
),
tabPanel( "Tab 2",
mainPanel(
wellPanel("Blah blah blah"),
width = 6
), # ends mainPanel
sidebarPanel(
# only runs once, like the ads
HTML('<script type="text/javascript"> alert("Tab 2 is talking"); </script>')
# # runs every time
#HTML('<b> test </b>')
, width=6 )
)
)
server <- function(input, output) {
}
app = shinyApp( ui, server )

Tabs only toggle visibility of content already on the page. So when the app launches, Tab 2 is actually loaded but hidden. If you want tabs to dynamically add and remove scripts, you could use renderUI to do this based on the active tab. Or sendCustomMessage and addCustomMessageHandler.
Here's an example using renderUI:
library(shiny)
ui = navbarPage("", id = "navbar",
tabPanel( "Tab 1",
mainPanel(
wellPanel("Blah blah blah"),
width = 6
),
sidebarPanel(
uiOutput("tab1"),
width = 6
)
),
tabPanel( "Tab 2",
mainPanel(
wellPanel("Blah blah blah"),
width = 6
),
sidebarPanel(
uiOutput("tab2"),
width=6
)
)
)
server <- function(input, output) {
output$tab1 <- renderUI({
req(input$navbar == "Tab 1")
HTML('<script type="text/javascript"> alert("Tab 1 is talking"); </script>')
})
output$tab2 <- renderUI({
req(input$navbar == "Tab 2")
HTML('<script type="text/javascript"> alert("Tab 2 is talking"); </script>')
})
}
shinyApp( ui, server )

Related

Reload a page and switch to tab

I use the package shinyjs to allow the user to reload the page by clicking on "Reload the page" button in Tab 2 and I would like to stay on Tab 2 after reloading the page. But after realoading, the page is taking to Tab1 instead of Tab2
How can we fix it ? This is my code below :
library(shiny)
library(shinyjs)
jscode <- "shinyjs.refresh = function() { history.go(0); }"
ui <- fluidPage(
useShinyjs(),
extendShinyjs(text = jscode, functions = "refresh"),
tabsetPanel(
tabPanel("Tab 1"),
tabPanel("Tab 2", actionButton("mybutton", "Reload the page",
onclick ="javascript:window.location.reload(true)")))
)
server <- function(input, output, session) {
}
shinyApp(ui = ui, server = server)
Some help would be appreciated
You can resort to plain JavaScript. The idea is that you provide an id to tabsetPanel such that you can use updateTabsetPanel in the server.
All you have to do then is to let the reloaded page know that you want to start on the second tab. As a refresh typically resets all of the inputs and reactives (you are aware of that, right?), you cannot use reactives and you have to rely on another way of communication. The query string would be one possibility.
With these ingredients you can:
Write a simple JavaScript function that reloads the page and adds a parameter to the url.
Parse the query string in the server and if the parameter is found, react on it accordingly.
library(shiny)
js <- HTML("
function doReload(tab_index) {
let loc = window.location;
let params = new URLSearchParams(loc.search);
params.set('tab_index', tab_index);
loc.replace(loc.origin + loc.pathname + '?' + params.toString());
}")
ui <- fluidPage(
tags$head(tags$script(js, type ="text/javascript")),
tabsetPanel(
tabPanel("Tab 1", value = "tab1"),
tabPanel("Tab 2", value = "tab2",
actionButton("mybutton", "Reload the page",
onclick = "doReload('tab2')")),
id = "tabsets"
)
)
server <- function(input, output, session) {
observe({
params <- parseQueryString(session$clientData$url_search)
if ("tab_index" %in% names(params)) {
updateTabsetPanel(session, "tabsets", selected = params$tab_index)
}
})
}
shinyApp(ui = ui, server = server)

How to minimize a sidebarLayout in a Shiny App?

I am building a shiny app and I am using two sidebarLayouts. I’m looking for a way to minimize them. I have try put each sidebarLayout into a box.
Example code:
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
headerPanel("Here goes the heder"),
box(sidebarLayout(
sidebarPanel(textOutput("someinputs")),
mainPanel(textOutput("someoutputs"))),
width = 12,
title = "BB",
collapsible = T,
collapsed = F
)
)
server <- function(input, output) {
output$someinputs <- renderText({
"Here will go the inputs"
})
output$someoutputs <- renderText({
"Here will go the outputs"
})
}
shinyApp(ui, server)
Output:
When I press the collapsible button the Layout does not collapse. Why is this happening? What should I do? Is there other way to do this?
Because you didn't use shinydashboard. The box comes from shinydashboard package. You need to use shinydashboard::dashboardPage instead of fluidPage.
dashboardPage Loads required javascripts and CSS files to toggle the button.
library(shiny)
ui <- shinydashboard::dashboardPage(
shinydashboard::dashboardHeader(),
shinydashboard::dashboardSidebar(),
shinydashboard::dashboardBody(
headerPanel("Here goes the heder"),
shinydashboard::box(
width = 12,
title = "BB",
collapsible = TRUE,
collapsed = FALSE,
sidebarLayout(
sidebarPanel(textOutput("someinputs")),
mainPanel(textOutput("someoutputs")))
)
)
)
If you don't want to use dashboardPage, you can write your own scripts to control the button:
library(magrittr)
library(shiny)
ui <- fluidPage(
headerPanel("Here goes the heder"),
shinydashboard::box(
width = 12,
title = "BB",
collapsible = TRUE,
collapsed = FALSE,
sidebarLayout(
sidebarPanel(textOutput("someinputs")),
mainPanel(textOutput("someoutputs")))
)%>% {.$attribs[['id']] <- 'example-box'; .},
tags$head(tags$script(
"$(document).ready(function(){
$('#example-box button').attr({
'data-toggle':'collapse',
'data-target':'#example-box .box-body',
'aria-expanded':false
})
})"
))
)
I used a hack to assign an ID to the box %>% {.$attribs[['id']] <- 'example-box'; .}, and use some jquery to control the button. Be sure the ID in the script matches the ID you assign in UI, example-box in this case. In javascript, you add # for ID searching, so #example-box.
I wouldn't recommend you to use the second way. You can see in your UI, it's not really a box. It has no border and the button is not at the right place. If you use dashboardPage, you can see the difference.

Display hyperlink into shiny::info

I want to insert a fixed hyperlink in a shiny::info pop up
library(shiny)
library(shinyjs)
library(shinydashboard)
ui <- (
fluidPage(
useShinyjs(),
div(
id = "main_page",
fluidRow( # -------------------------------------------------------
infoBox(title=NULL, icon=shiny::icon(""), subtitle = HTML("<a id=\"infobutton\"
href=\"#\" class=\"action-button\"><i class=\"fa fa-info-circle\"></i></a>"))
)
)
)
)
server <- (
function(input, output, session) {
observeEvent(input$infobutton, {
shinyjs::info("It's me Mario")
})
}
)
shinyApp(ui = ui, server = server)
I've tried with a TagList but the pop up just display what's inside the tagList
shinyjs::info(tagList("It' me Mario:", a("Mario", href="https://en.wikipedia.org/wiki/Mario")))
Thanks !
You cannot (or should not) be able to insert HTML into there. It only supports plain text.
shinyjs::info() is running the javascript alert() function - here's the official documentation for it.
Notice the message parameter is:
A string you want to display in the alert dialog, or, alternatively, an object that is converted into a string and displayed.
It's not meant to accept HTML. I'm honestly very surprised that it's able to parse HTML within RStudio, browsers are supposed to only show plain text. If you want to show a pop up message with HTML you need to use something more advanced like shinyalert package or shiny modals.
You can directly generate the needed HTML with HTML:
library(shiny)
library(shinyjs)
library(shinydashboard)
ui <- (
fluidPage(
useShinyjs(),
div(
id = "main_page",
fluidRow( # -------------------------------------------------------
infoBox(title=NULL, icon=shiny::icon(""), subtitle = HTML("<a id=\"infobutton\"
href=\"#\" class=\"action-button\"><i class=\"fa fa-info-circle\"></i></a>"))
)
)
)
)
server <- (
function(input, output, session) {
observeEvent(input$infobutton, {
shinyjs::info(HTML("<p>It's me Mario:</p> <a href='https://en.wikipedia.org/wiki/Mario'>Mario</a>"))
})
}
)
shinyApp(ui = ui, server = server)

How to catch dynamic tabPanel() id in Shiny app

It is possible to use a tabPanel id into a link ? I speak of id like #tab-6584-1 or #tab-6985-1 that shows when your cursor is over a tab in a Shiny app running in a browser :
Image of dynamic tabPanel() id example with Firefox
I would like to use this to put a top left link in a Shiny app to redirect on app "home" page.
From your question, I'm not sure, but it seems like you want to mimic navigation to "subpages" in your app. If that's the case, there's a way of doing that in Shiny by reading and writing the value of the hash string at the end of the app's URL (link to gist):
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
tags$a("Go to Panel 1", href = "#panel1"), br(),
tags$a("Go to Panel 2", href = "#panel2"), br(),
tags$a("Go to Panel 3", href = "#panel3")
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Panel 1", h1("Panel 1"), value = "#panel1"),
tabPanel("Panel 2", h1("Panel 2"), value = "#panel2"),
tabPanel("Panel 3", h1("Panel 3"), value = "#panel3")
)
)
)
)
server <- function(input, output, session) {
# When we change from one `tabPanel` to another, update the URL hash
observeEvent(input$tabs, {
# No work to be done if input$tabs and the hash are already the same
if (getUrlHash() == input$tabs) return()
# The 'push' argument is necessary so that the hash change event occurs and
# so that the other observer is triggered.
updateQueryString(
paste0(getQueryString(), input$tabs),
"push"
)
# Don't run the first time so as to not generate a circular dependency
# between the two observers
}, ignoreInit = TRUE)
# When the hash changes (due to clicking on the link in the sidebar or switching
# between the `tabPanel`s), switch tabs and update an input. Note that clicking
# another `tabPanel` already switches tabs.
observeEvent(getUrlHash(), {
hash <- getUrlHash()
# No work to be done if input$tabs and the hash are already the same
if (hash == input$tabs) return()
valid <- c("#panel1", "#panel2", "#panel3")
if (hash %in% valid) {
updateTabsetPanel(session, "tabs", hash)
}
})
}
shinyApp(ui, server)

'shinydashboard' is not showing item properly in 'sidebar'

I have created a Shiny App using Package 'shinydashboard' as below :
library(shinydashboard)
library(shiny)
sidebar <- dashboardSidebar(
sidebarMenu(id="tabs",
menuItem("ABC", tabName="ABC", icon=icon("line-chart"), selected=TRUE),
menuItem("ABC1", tabName="ABC1", icon=icon("line-chart"), selected=FALSE)
),
conditionalPanel("input.tabs == 'ABC'",
fluidRow(
column(11, offset = 1, h5((' Note')))
)
),
conditionalPanel("input.tabs == 'ABC1'",
fluidRow(
column(11, offset = 1, style = "height:20px; color:rgb(30,144,255);", h1((' Update')))
)
))
body <- dashboardBody(
tabItems(
tabItem(tabName = "ABC",br())
),
tabItems(
tabItem(tabName = "ABC1",br())
)
)
ui = dashboardPage(
dashboardHeader(title = "ABC"),
sidebar,
body
)
server = function(input, output){}
shinyApp(ui = ui, server = server)
However I have noticed a strange behaviour that, when I run the App, initially the comment 'Note' in "input.tabs == 'ABC'" is not visible. However when I click on "input.tabs == 'ABC1'" and then 'ABC', the 'Note' comment becomes visible.
Can somebody points me where I went wrong in above code?
Any help will be highly appreciated.
Thanks,
It seems that we introduced this bug in the newest version of shinydashboard. Sorry! I'll try to fix it soon. You can keep track of the progress here: https://github.com/rstudio/shinydashboard/issues/214.
Update (9 June 2017): This is now fixed on the development version of shinydashboard. Your original code should run just fine if you install shinydashboard from github:
devtools::install_github("rstudio/shinydashboard")
In the meantime, a couple of things:
You don't need to use selected = FALSE.
If the menuItem() that you want to start selected is the first one (like in the example you posted above), also remove selected = TRUE and your problem goes away. This is just a workaround to get your app working now. When this bug is solved, there will be no difference in having selected = TRUE or nothing in the first menuItem().
A more general workaround for now (works no matter which menuItem() you want to start selected) is to use a dynamic sidebar menu:
library(shinydashboard)
library(shiny)
sidebar <- dashboardSidebar(
sidebarMenuOutput("menu"),
conditionalPanel("input.tabs == 'ABC'",
fluidRow(
column(11, offset = 1, h5((' Note')))
)
),
conditionalPanel("input.tabs == 'ABC1'",
fluidRow(
column(11, offset = 1, style = "height:20px; color:rgb(30,144,255);", h1((' Update')))
)
))
body <- dashboardBody(
tabItems(
tabItem(tabName = "ABC",br())
),
tabItems(
tabItem(tabName = "ABC1",br())
)
)
ui = dashboardPage(
dashboardHeader(title = "ABC"),
sidebar,
body
)
server = function(input, output){
output$menu <- renderMenu({
sidebarMenu(id="tabs",
menuItem("ABC", tabName="ABC", icon=icon("line-chart"), selected=TRUE),
menuItem("ABC1", tabName="ABC1", icon=icon("line-chart"))
)
})
}
shinyApp(ui = ui, server = server)