I want to include some javascript in my shiny module, but regardles of what I tried the javascript is not appear in the final app. What am I doing wrong?
App.r:
library(shiny)
source("examplemod.R")
ui <- fluidPage(
tags$head(
tags$script(src="shinyjavascript.js")
),
sidebarLayout(
sidebarPanel(
examplemodUI("example")
),
mainPanel(
)
)
)
server <- function(input, output) {
callModule(examplemod, "example")
}
shinyApp(ui, server)
Module:
examplemodUI<-function(id){
ns <- NS(id)
tagList(
textOutput(ns("prb"))
)
}
examplemod<- function(input, output, session) {
output$prb<-renderPrint({
input$one
})
}
shinyjavascript.js
$( document ).on("shiny:sessioninitialized", function(event) {
Shiny.onInputChange("one", "noone");
});
First, check that your shinyjavascript.js can be found by the browser. Run your add and open it in a browser. Chrome has a better inspector module than RStudio's built in app viewer (the one that pops up when you run an app). In the browser's Inspector, find the reference to shinyjavascript.js both in the "Elements" pane and that the contents can be loaded in the "Sources" pane.
If it's there, but cannot be loaded, ensure that the file is placed in a subdirectory www from where your app's R source files are located.
If the file is found and loaded, update it to the following for some good ol' fashioned debugging:
console.log('JavaScript file loaded')
$( document ).on("shiny:sessioninitialized", function(event) {
console.log('session initialized')
Shiny.onInputChange("one", "noone");
});
Reload the page and look for these two lines in the console. This will give us a hint to how far your code runs.
Update:
Once satisfied your javascript is loaded and running, we move on to the next. Does your app receive the value and why doesn't it update?
output$prb<-renderPrint is a reactive that only executes if and only if Shiny as prb binded to an output control. This could be your case, or it's that input$one doesn't work. So, we separate the two.
Try to update the following:
server <- function(input, output) {
callModule(examplemod, "example")
observe({cat(input$one, '\n')})
}
This should print 'noone' to the R console.
If that works, move the new observe line to your module:
examplemod<- function(input, output, session) {
output$prb<-renderPrint({
input$one
})
observe({cat(input$one, '\n')})
}
and see if you get a response there.
Update 2 with solution:
So, I've detailed how I would debug these problems. But have found a solution. It requires 3 small changes:
examplemod<- function(input, output, session, one) { # 1
output$prb<-renderPrint({
one() # 2
})
callModule(examplemod, "example", reactive(input$one)) # 3
But I cannot offer any explanation as to why you cannot use input directly...
Related
I've encapsulated several small Shiny apps into modules. When calling these modules individually from the command line they display as expected, filling the entire page. I want to create an app where a user can select different modules from a menu. I've been trying to do this with navbarPage and tabPanel, but the modules always display at half the height of my screen.
I've tried wrapping various elements in divs/boxes and changing their height, but it only changes the height of the container around my modules, not the modules themselves. I'm pretty new to Shiny, and I think I must be misunderstanding something about the relationship between modules and the apps that call them.
Reproducible example:
WGStableUI <- function(id){ fluidPage(
dataTableOutput(NS(id,"dynamic"))
)
}
WGStableServer <- function(id){
moduleServer(id, function(input, output, session) {
#WGS_tbl <- tbl(connection,"WGS") %>% as_tibble()
#output$dynamic <- renderDataTable(WGS_tbl, options = list(pageLength = 5))
output$dynamic <- renderDataTable(mtcars, options = list(pageLength = 100))
})}
WGStableApp <- function() {
ui <- fluidPage(
WGStableUI("displayWGStable")
)
server <- function(input, output, session) {
WGStableServer("displayWGStable")
}
shinyApp(ui, server)
}
library(shiny)
source("./WGS_table_module.R")
ui <- navbarPage("title",
tabPanel("page1"),
tabPanel("page2",WGStableApp())
)
server <- function(input, output){}
shinyApp(ui,server)
Edit: still trying to figure this one out, I have realized that just calling a module from inside another app is causing an issue, the tabPanels have no effect. There are scroll bars, but I can't change the size of the display window. Same thing happens just doing:
library(shiny)
ui <- WGStableApp()
server <- function(input, output, session) {
}
shinyApp(ui, server)
I never figured out why this was happening, but I did find a solution. Instead of calling the UI and server modules together as I did above, I created another module, which called each UI and server element separately, and then I called that new module from an app.
Example:
menu_ui <- function(id) {
navbarPage("Menu",
tabPanel("WGS Epi Table",WGStableUI(NS(id, "infotable")))
)
}
menu_server <- function(id) {
moduleServer(id, function(input, output, session) {
WGStableServer("infotable")
})
}
demo_menu <- function() {
ui <- fluidPage(menu_ui("demomenu"))
server <- function(input, output, session) {
menu_server("demomenu")
}
shinyApp(ui, server)
}
There are numerous posts regarding changing titles of other pieces of Shiny apps, e.g.:
Change the title by pressing a shiny button Shiny R
Shiny page title and image
Shiny App: How to dynamically change box title in server.R?
My question is related, but not answered by any of these. I would like to make the <head><title>...</title></head> tag reactive, or at least controllable from within an observeEvent in server.R.
The following does not work, since ui can't find theTitle, but is the kind of approach I'd hope is possible:
library(shiny)
ui <- fluidPage(
title = theTitle(),
textInput("pageTitle", "Enter text:")
)
server <- function(input, output, session) {
theTitle <- reactiveVal()
observeEvent( input$pageTitle, {
if(is.null(input$pageTitle)) {
theTitle("No title yet.")
} else {
theTitle(input$pageTitle)
}
})
}
I've tried making output$theTitle <- renderText({...}) with the if..else logic in that observeEvent, and then setting title = textOutput("theTitle") in ui's fluidPage, but that generates <div ...> as the title text, or <span ...> if we pass inline=True to renderText.
In case this clarifies what I'm looking for, the answer would make something equivalent to the literal (replacing string variables with that string) ui generated by
ui <- fluidPage(
title = "No title yet.",
....
)
before the user has entered any text in the box; if they have entered "Shiny is great!" into input$pageTitle's box, then we would get the literal
ui <- fluidPage(
title = "Shiny is great!",
....
)
One way would be to write some javascript to take care of that. For example
ui <- fluidPage(
title = "No title yet.",
textInput("pageTitle", "Enter text:"),
tags$script(HTML('Shiny.addCustomMessageHandler("changetitle", function(x) {document.title=x});'))
)
server <- function(input, output, session) {
observeEvent( input$pageTitle, {
title <- if(!is.null(input$pageTitle) && nchar(input$pageTitle)>0) {
input$pageTitle
} else {
"No title yet."
}
session$sendCustomMessage("changetitle", title)
})
}
shinyApp(ui, server)
This was created following the How to send messages from the browser to the server and back using Shiny guide
As of June 2021, there is an R package called shinytitle that can update the window title from within Shiny's reactive context: https://cran.r-project.org/package=shinytitle
I'm using modules within my shiny app to display the content of different tabPanels. I would like to be able to travel to the second tab by clicking one button. I have the following code:
library(shiny)
library(shinydashboard)
moduleUI <- function(id){
ns <- NS(id)
sidebarPanel(
actionButton(ns("action1"), label = "Go to tab 2")
)
}
module <- function(input, output, session, openTab){
observeEvent(input$action1, {
openTab("two")
})
return(openTab)
}
ui <- fluidPage(
navlistPanel(id = "tabsPanel",
tabPanel("one", moduleUI("first")),
tabPanel("two", moduleUI("second"))
))
server <- function(input, output, session){
openTab <- reactiveVal()
openTab("one")
openTab <- callModule(module,"first", openTab)
openTab <- callModule(module,"second", openTab)
observeEvent(openTab(), {
updateTabItems(session, "tabsPanel", openTab())
})
}
shinyApp(ui = ui, server = server)
However this only works once. The problem I think, is that the module does not know when a tab is changed in the app. Therefore I'm looking for a way to make sure the module knows which tab is opened so that the actionButton works more that once.
I have considered using input$tabsPanel but I don't know how to implement it.
Help would be greatly appreciated.
The problem is that once the user manually switches back to tab 1, the openTab() does not get updated. So therefore, when you click the actionButton a second time, openTab changes from "two" to "two" (i.e. it stays the same), and therefore your observeEvent is not triggered.
You could add:
observeEvent(input$tabsPanel,{
openTab(input$tabsPanel)
})
So the openTab reactiveVal is updated also when a user manually changes back to tab1 (or any other tab).
You don't need modules to do what you want by the way, but I assume you have a specific reason to use them. For anyone else who wants to achieve the same but does not need modules:
library(shiny)
library(shinydashboard)
ui <- fluidPage(
sidebarPanel(
actionButton(ns("action1"), label = "Go to tab 2")),
navlistPanel(id = "tabsPanel",
tabPanel("one"),
tabPanel("two")
))
server <- function(input, output, session){
observeEvent(input$action1, {
updateTabItems(session, "tabsPanel", "two")
})
}
shinyApp(ui = ui, server = server)
I'd like to have a variable number of identical actionButton()s on a page all handled by one observeEvent() function.
For example, in a variable-length table of tables, I'd like each interior table to have a button that links to more information on that table.
In standard HTML, you do this with a simple form, where you use a hidden input to designate the interior table number, like this:
<form ...>
<input id="table_number" type="hidden" value="1"/>
<input type="submit" value="Examine"/>
</form>
When a button is pressed, you can examine the hidden input to see which one it was.
Is there a way to do this in Shiny? The only solution I've come up with is to give each actionButton() it's own inputId. This requires a separate observeEvent() for each button. Those have to be created ahead of time, imposing a maximum number of buttons.
It only took me a couple of years, but I now have a much better answer to this question. You can use a JavaScript/jQuery function to put an on-click event handler on every button in a document, then use the Shiny.onInputChange() function to pass the ID of a button (<button id="xxx"...) that has been clicked to a single observer in your Shiny code.
There's a full description with code examples at One observer for all buttons in Shiny using JavaScript/jQuery
You could use shiny modules for this: you can have variable number of actionButton that are identical. These are defined in the ab_moduleUI part. They are handled by their own observeEvent but it has to be defined only once in the ab_module part.
With lapply any number of actionButton can be created.
Edit: You don't have to specify the number of buttons beforehand: use renderUI to generate UI elements at server side.
For demonstration purposes I added a numericInput to increase/decrease the number of modules to render.
# UI part of the module
ab_moduleUI <- function(id){
ns <- NS(id)
tagList(
fluidRow(
actionButton(ns("btn"), paste("ActionButton", id, sep="-")),
textOutput(ns("txt"))
)
)
}
# Server part of the module
ab_module <- function(input, output, session){
observeEvent(input$btn,{
output$txt <- renderText("More information shown")
})
}
# UI
ui <- fluidPage(
# lapply(paste0("mod", 1:no_btn), ab_moduleUI)
numericInput("num", "Number of buttons to show" ,value = 5, min = 3, max = 10),
uiOutput("ui")
)
# Server side
server <- function(input, output, session){
observeEvent(input$num, {
output$ui <- renderUI({
lapply(paste0("mod", 1:input$num), ab_moduleUI)
})
lapply(paste0("mod", 1:input$num), function(x) callModule(ab_module, x))
})
}
shinyApp(ui, server)
Read more about shiny modules here
Regarding the use of Shiny modules to answer my original question...
What I'd like to have is a way to have multiple buttons on a page that can be handled by a single observeEvent(), which is easy to do with traditional HTML forms, as shown in the original question.
GyD's demonstration code using Shiny modules almost solves the problem, but it doesn't actually return which button was pressed to the main server. It took me a long time, but I finally figured out how to write a module that does let the main server know which button was pressed:
actionInput <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("text"), label=NULL, value=id),
actionButton(ns("button"), "OK")
)
}
action <- function(input, output, session) {
eventReactive(input$button, {
return(input$text)
})
}
ui <- fluidPage(fluidRow(column(4, actionInput("b1")),
column(4, actionInput("b2")),
column(4, uiOutput("result"))))
server <-function(input, output, session) {
b1 <- callModule(action, "b1")
observeEvent(b1(), {
output$result = renderText(b1())
})
b2 <- callModule(action, "b2")
observeEvent(b2(), {
output$result = renderText(b2())
})
}
shinyApp(ui = ui, server = server)
(In a real application, I would make the textInputs invisible, as they're only there to provide an id for which button was pressed.)
This solution still requires an observeEvent() in the main server for each button. It may be possible to use modules in some other way to solve the problem, but I haven't been able to figure it out.
My original alternative, using a separate observeEvent() in the main server for each button, is actually quite a bit simpler than an expansion of this demo code would be for a hundred or more buttons.
I'm still new to r and shiny, and i'm stumped with what should otherwise be simple logic. I am trying to display pdf files in imageOutput widgets but with no luck. Could someone steer me in the right direction?
sample ui.R
shinyUI(pageWithSidebar(
mainPanel(
selectInput("sel_ed",
label = "View outputs for Ecodistrict:",
choices = c(244,245,247,249),
selected = NULL,
multiple = FALSE),
imageOutput("imp_pdf",width="500px",height="500px")
))
sample server.R
shinyServer(function(input, output, session) {
importance <- function(inputSpecies){
img_dir <- pdf(paste(inputSpecies,"\\models\\MATL\\MATRF_Importance",sep=""))
}
output$imp_pdf <- renderImage({importance(input$sel_ed)})
})
Most of the errors i get have to do with expected character vector arguments, or atomic vectors. I know that shiny is more or less designed to render AND display images or plots but there has to be a way to display pdf's that are already on a local drive..
To embed a PDF viewer (the default PDF viewer of your web browser, pdf.js on mozilla for example) in your Shiny ui, you can use an iframe which the src will be the path to your PDF.
Here is 2 differents ways to include an iframe in your interface :
in the Ui you can directly add an iframe tag with an absolute src attribute as bellow :
tags$iframe(style="height:600px; width:100%", src="http://localhost/ressources/pdf/R-Intro.pdf"))
Or get an URL from the ui in the server , write the iframe tag with the input URL and return the HTML code in a htmlOutput in the ui :
Ui :
textInput("pdfurl", "PDF URL")
htmlOutput('pdfviewer')
Server :
output$pdfviewer <- renderText({
return(paste('<iframe style="height:600px; width:100%" src="', input$pdfurl, '"></iframe>', sep = ""))
})
Note that when pages are loaded with a HTTP(S) protocol (the case of the Shiny app) for security reasons you can't framed locals files with their "file:" URLs. If you want to display locals pdf you should access to them with a http(s): URL, so you have to save them in your www directory (a local web server) and access to files with their http(s): URLs (the URL will be something like http://localhost/.../mypdf.pdf) as in the second iframe of my example. (Then you can't use a fileInput directly, you have to format it)
Ui.R :
library(shiny)
row <- function(...) {
tags$div(class="row", ...)
}
col <- function(width, ...) {
tags$div(class=paste0("span", width), ...)
}
shinyUI(bootstrapPage(
headerPanel("PDF VIEWER"),
mainPanel(
tags$div(
class = "container",
row(
col(3, textInput("pdfurl", "PDF URL"))
),
row(
col(6, htmlOutput('pdfviewer')),
col(6, tags$iframe(style="height:600px; width:100%", src="http://localhost/ressources/pdf/R-Intro.pdf"))
)
)
)
))
Server.R :
shinyServer(function(input, output, session) {
output$pdfviewer <- renderText({
return(paste('<iframe style="height:600px; width:100%" src="', input$pdfurl, '"></iframe>', sep = ""))
})
})
The web pages with the PDF viewers :
Hope this help.
Create a folder called www in the original directory that contains your server.R and ui.R scripts. Place the PDF in the www/ folder, then use something like the code below:
In server.R:
shinyServer(function(input, output) {
observeEvent(input$generate, {
output$pdfview <- renderUI({
tags$iframe(style="height:600px; width:100%", src="foo.pdf")
})
})
})
In ui.R:
shinyUI(fluidPage(
titlePanel("Display a PDF"),
sidebarLayout(
sidebarPanel(
actionButton("generate", "Generate PDF")
),
mainPanel(
uiOutput("pdfview")
)
)
))
Additional to Fabian N.'s answer.
There are two important things:
Make sure you create a R Shiny Web Application from Rstudio. Make sure you run as "Run App". Otherwise, files in "www" folder can not be accessed!
Make sure you create a "www" folder in Web Application directory.