Giter VIP home page Giter VIP logo

shiny.router's Introduction

shiny.router shiny.router logo

A minimalistic router for your Shiny apps.

R-CMD-check codecov cranlogs total

Now it's possible to recreate a state of your app, by providing a specific URL, like:

router_ui(
  route("<your_app_url>/main",  mainPageShinyUI),
  route("<your_app_url>/other", otherPageShinyUI)
)

How to install?

It's possible to install this library through CRAN

install.packages("shiny.router")

The most recent version you can get from this repo using remotes.

remotes::install_github("Appsilon/shiny.router")

How to use it?

Basic usage:

library(shiny)
library(shiny.router)

root_page <- div(h2("Root page"))
other_page <- div(h3("Other page"))

ui <- fluidPage(
  title = "Router demo",
  router_ui(
    route("/", root_page),
    route("other", other_page)
  )
)

server <- function(input, output, session) {
  router_server()
}

shinyApp(ui, server)

Check the tutorial for more details on how to start using shiny.router.

Examples

An application that showcases the shiny.router features can be found here:

It was built using two other Appsilon Open Source packages:

  • rhino - an R package designed to help building high quality, enterprise-grade Shiny applications at speed.
  • shiny.fluent - Microsoft's Fluent UI for Shiny apps.

You can also visit examples directory for some complete samples.

How to contribute?

If you want to contribute to this project please submit a regular PR, once you're done with new feature or bug fix.

Reporting a bug is also helpful - please use GitHub issues and describe your problem as detailed as possible.

Appsilon

Appsilon is a Posit (formerly RStudio) Full Service Certified Partner.
Learn more at appsilon.com.

Get in touch [email protected]

Explore the Rhinoverse - a family of R packages built around Rhino!

We are hiring!

shiny.router's People

Contributors

agwells avatar anirbanshaw24 avatar cosi1 avatar cpsievert avatar damianrodziewicz avatar dokato avatar galachad avatar jakubnowicki avatar krystian8207 avatar krzysztofwrobel avatar pedrocoutinhosilva avatar przytu1 avatar rstammer avatar sankhadeepdutta avatar scizmeli avatar vanhry avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shiny.router's Issues

route_link() keep root url but lose path

I deployed the sample application @https://appsilon.com/shiny-router-package/ on an nginx server with the following path:
https://www.rooturl.com/shinyproxy, when I click the "home" or "side", the root url (www.rooturl.com) is maintained but the path "shinyproxy" is disappeared. This only happens on the route_link. The "change_page" is still working as expected.

library(shiny)
library(shiny.router)

# This generates menu in user interface with links.
menu <- (
  tags$ul(
    tags$li(a(class = "item", href = route_link("home"), "Home page")),
    tags$li(a(class = "item", href = route_link("side"), "Side page"))
  )
)

# This creates UI for each page.
page <- function(title, content) {
  div(
    menu,
    titlePanel(title),
    p(content),
    actionButton("switch_page", "Click to switch page!")
  )
}

# Both sample pages.
home_page <- page("Home page", uiOutput("current_page"))
side_page <- page("Side page", uiOutput("current_page"))

# Creates router. We provide routing path, a UI as
# well as a server-side callback for each page.
router <- make_router(
  route("home", home_page, NA),
  route("side", side_page, NA)
)

# Create output for our router in main UI of Shiny app.
ui <- shinyUI(fluidPage(
  router_ui()
))

# Plug router into Shiny server.
server <- shinyServer(function(input, output, session) {
  router(input, output, session)
  
  output$current_page <- renderText({
    page <- get_page(session)
    sprintf("Welcome on %s page!", page)
  })
  
  observeEvent(input$switch_page, {
    if (is_page("home")) {
      change_page("side")
    } else if (is_page("side")) {
      change_page("home")
    }
  })
})

# Run server in a standard way.
shinyApp(ui, server)

Using shiny.router to receive POST requests on specific routes

Is it possible to set up a webhook so we can receive POST requests to specific routes?

So let's say that I have a shiny app deployed on https://itsme.shinyapps.io/myapp

I want to set up a webhook so the app 'wakes up' and the server does 'something' if a user sends a POST request with a json file to - let's say - https://itsme.shinyapps.io/myapp/myfeature

How would be the recommended way of addressing this with shiny.router? Is it even possible?

Thank you! (I'm aware the question is a bit vague, let me know if you need more clarification)

Allow passing GET parameters on the main page

There is no possibility to send parameters via url on the main page.
Passing: app_url/#!/?val=1 redirects me to the main page app_url/#!/ without passing parameters to the app.
Could you validate this issue? Thanks a lot.

Disable routing when in an iframe

Is it possible to disable routing when the shiny app is launched in an iframe? In this case, the window.location variables are wrong (I guess).

The fix in #57 does not fix my use case, which is running shiny.router in combination with shinyproxy.

The reason for continuing to use shiny.router (even though the differentiated http links cannot be seen from an embedded iframe) is because shiny.router is excellent for managing apps with a complicated navigation setup.

Rendering slider_input from server with renderUI forces whole UI to completely redraw when routing page

Looks like the problem is present with shiny.semantic slider_input as well as standard shiny sliderInput.

server <- shinyServer(function(input, output) {
router(input, output) slider_ui <- sliderInput("slider", "slider_label", 0 , 10 , 3, 1, 3)
output$slider <- renderUI(slider_ui)
}
Above makes whole ui refresh on routing ( even elements outside router_ui() )

I've checked some additional components (numericInput, semantic date_input, actionButton) and it is working fine with them.

Shiny.router 0.1.1 cannot work with both R 3.4.1 and R 3.5.1

  • Shiny.router (0.1.0) can work well with both R 3.4.1 and R 3.5.1 (devtools::install_github("Appsilon/shiny.router", ref = "0.1.0")), please notice that there is no DOT in library(shinyrouter)
  • Shiny.router (0.1.1, the latest default version installed by devtools::install_github("Appsilon/shiny.router")) can NOT work both R 3.4.1 and R 3.5.1, another weird thing is the usage of the new shiny.router has a DOT between “shiny” and “router” library(shiny.router)

Callback behavior changed with 0.2.2

Hi, I just recently updated to shiny.router 0.2.2 and noticed that for some dashboards where data fetching takes 10-15 seconds behavior has changed. Previously I would see the root page while the data loads, but now I get the 404 page while data loads.

I modified the basic app.R example code in order to create a reproducible example, with the following two changes:

# Callbacks on the server side for
# the sample pages
root_callback <- function(input, output, session) {
  
  ds <- reactive({
    # Adding a 5 second wait
    Sys.sleep(5)
    data.frame(x = c(1, 2), y = c(3, 4))
  })
  
  output$table_one <- renderDataTable({
    ds()
  })
}
# Creates router. We provide routing path, a UI as
# well as a server-side callback for each page.
router <- make_router(
  route("/", root_page, root_callback),
  route("other", other_page, other_callback),
  route("third", third_page, NA),
  page_404 = page404("You opened non existing bookmark!")
)

Now when the app starts off, the 404 page displays for 5 seconds, then the root page appears. In the previous version the root page would show while the root_callback finishes.

Screenshot 2021-07-06 141615

Is this expected, or is there a way to get the previous behavior back?

Session info attached:

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19042)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                           LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] shiny.router_0.2.2 shiny_1.5.0       

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.4.6    digest_0.6.25   withr_2.2.0     later_1.0.0     mime_0.9        R6_2.4.1        xtable_1.8-4    jsonlite_1.6.1 
 [9] magrittr_1.5    rlang_0.4.11    promises_1.1.0  tools_3.6.3     httpuv_1.5.2    fastmap_1.0.1   compiler_3.6.3  htmltools_0.5.0

Returning to main page via url and passing wrong path

I suppose there is no possibility to get back to the main page just removing some part of url.
When I move to: app_url/#!/other and want to return to main page by passing url: app_url/#!/ it still returns to app_url/#!/other.
Moreover passing app_url/#!/wrong_path still returns to app_url/#!/other (not to the default page). Is this target behaviour?
Could you verify above issues? Thank you.

semantic tabset not working with router

Only with the current master version of shiny.router.

A bit puzzling error to me:

library(shiny)
library(shiny.router)
library(shiny.semantic)

tabs <- tabset(id = "tab1", list(list(menu = div("First link"),
                                content = div("First content")),
                           list(menu = div("Second link"),
                                content = div("Second content")),
                           list(menu = div("Third link"),
                                ontent = div("Third content"))))
router <- make_router(
  route("index", tabs),
  route("second", h1('second'))
)

ui <- semanticPage(
  horizontal_menu(
    list(
      list(name = "One", link = route_link("index"), icon = "dog"),
      list(name = "Two", link = route_link("second"), icon = "cat")
    ),
  ),
  router_ui()
)

server <- function(input, output, session) {
  router(input, output, session)
}
shinyApp(ui, server)

It looks like the last change broke that, as version of a router from CRAN (shiny.router_0.1.1) works fine ( cc @krystian8207 )

Using shiny.router with separate ui and server files

Is it possible to use shiny.router with separate ui and server files? I could not figure out how you would get the scoping right on that. Defining the router object in UI would make it not accessible in server and global.R did not seem to help that either.

Router "True" Issue

Anytime I source a .R page that I have created and then Run the app, the App will load
but only with have the screen visible and the word "TRUE" at the bottom left side. How
do I fix this annoyance. I have uploaded some minimal R code.

library(shiny)
#devtools::install_github("Appsilon/shiny.router")
library(shiny.router)

Both sample pages.

page2 <- source("/SamplingProject/7a_Create_Strata_Assing_Default_Single_Stratum.R")

Creates router. We provide routing path and UI for this page.

router <- make_router(
route("/page2", page2)
)

Creat output for our router in main UI of Shiny app.

ui <- shinyUI(fluidPage(
router_ui()
))

Plug router into Shiny server.

server <- shinyServer(function(input, output) {
router(input, output)
})

Run server in a standard way.

shinyApp(ui, server)

Building a markdown-based survey platform in {shiny}

Hi! I'm not really sure where to post this as it's not quite an issue. Rather, it's an initial conversation to see if you might be interested in / have ideas for how one might build an open-source, markdown-based survey platform using {shiny} (and possibly RMarkdown / Quarto). I've had this idea in mind for years (see this blog post), but I've struggled to find an architecture that achieves the end goal. So I'm posting this in case anyone in the Appsilon team might be interested in the idea as I think a lot of your packages could be key components of making this work, and I figured you all probably would have the best ideas of how to set something like this up.

For example, I never knew about {shiny.router}, and having just discovered it I feel like this could be a key component for controlling navigation through a survey, with questions set on different pages. And packages like {rhino} are probably also super useful for something like this. Apologies that this isn't quite an issue - I just didn't know how else to reach out to the Appsilon team.

My end goal is to build a package such that users can define their survey content using mostly just markdown and code chunks. That is, if the goal is to design a survey, then all we really need is to define the UI. The server code could be generated with some logic based on what's in the UI as we're not using {shiny} in a conventional dashboard way but rather simply for data collection.

For example, in this discussion I describe an example of how I would want a simple survey to be able to be defined, something like this:

---
name: "surveydown demo"
author: "John Paul Helveston"
format: html
self-contained: true
execute:
  echo: false
---

```{r}
#| include: false

library(surveydown)
```

# Welcome!

This is a demo survey! Click the button below to go to the next page.

---

This is a screener.

You must choose "Red" in the question below, otherwise you will be screened out (meaning you will be sent to the end of the survey)

```{r}
question(
  name     = 'color',
  type     = 'mc',
  required = TRUE,
  label    = "Do you want to take the red pill or the blue pill?",
  option   = c('Red', 'Blue')
)
```

---

Here are some more questions:

```{r}
question(
  name     = 'animal',
  type     = 'text',
  required = FALSE,
  label    = "What's your favorite animal?"
)

question(
  name     = 'education',
  type     = 'select',
  required = TRUE,
  label    = "What is the highest level of education you have attained?",
  option   = c(
    "Did not attend high school" = "hs_no",
    "Some high school"           = "hs_some",
    "High school graduate"       = "hs_grad",
    "Some college"               = "college_some",
    "College"                    = "college_grad",
    "Graduate Work"              = "grad",
    "Prefer not to say"          = "no_response"
  )
)
```

---

# Fine

Thank you for taking our survey!

There is no {surveydown} package (yet), but that's the idea. Define a survey in markdown, then render it to a shiny app. The data would be saved to any database - I'm thinking of just saving to a Google sheet for now. But if you defined all of the UI content in a .Rmd or .qmd file, we should be able to define some functions that generate the appropriate UI and server code for a shiny app that simply writes user responses to a database.

Anyway, if this sounds interesting I would appreciate any thoughts / suggestions you have.

Tables double loading when using browser back button

Issue:

Currently, when I click a link in the app and then go back to the previous page, the tables on it appear to be cached from when I was on that page previously. After a moment, the table will flash and reload. Initially I thought this might be due to some reactive dependencies, but even when I removed all dependencies and just loaded the table by itself, the behavior persisted. I would like to devise a solution that either clears that cached version of the table so that there is no double loading, or prevent the reloading if the cached table hasn't changed. Is there a known solution to this problem?

Environment:

Shiny: 1.4.0.2
shiny.router: 0.1.1
R: 3.6.1

How to reproduce issue:

I've included code to reproduce this problem. To see the double load issue, run the app and wait for the table to load. Then click the link to the second page, and click the back button in the browser. You should see the table from before, then after 1 second, there should be a flash as the table is reloaded. I tested this on both Chrome and Firefox and was able to get the same results in both browsers.

library(shiny)
library(shiny.router)

# This generates menu in user interface with links.
menu <- (
  tags$ul(
    tags$li(a(class = "item", href = route_link("/"), "First page")),
    tags$li(a(class = "item", href = route_link("second"), "Second page"))
  )
)

# This creates UI for each page.
page <- function(title, content = "") {
  div(
    menu,
    titlePanel(title),
    p(content)
  )
}

# Both sample pages.
root_page <- page("Home page", tagList(DT::dataTableOutput("table")))
other_page <- page("Second page")

# callbacks
root_callback <- function(input, output, session) {
  output$table = DT::renderDataTable({
    Sys.sleep(1)
    DT::datatable(EuStockMarkets)
  })
}

# Creates router. We provide routing path, a UI as
# well as a server-side callback for each page.
router <- make_router(
  route("/", root_page, root_callback),
  route("second", other_page)
)

# Creat output for our router in main UI of Shiny app.
ui <- shinyUI(fluidPage(
  router_ui()
))

# Plug router into Shiny server.
server <- shinyServer(function(input, output, session) {
  router(input, output, session)
})

# Run server in a standard way.
shinyApp(ui, server)

[Bug]: Error: 'router_ui' is not an exported object from 'namespace:shiny.router'

Guidelines

  • I agree to follow this project's Contributing Guidelines.

Project Version

0.2.2

Platform and OS Version

MacOS Monterey 12.3.1 (Apple Chip, M1)

Existing Issues

seems no

What happened?

I just successfully installed shiny.router with R 4.2.0 on a MacOS Monterey 12.3.1 (Apple Chip, M1):

> install.packages("shiny.router")
> packageVersion("shiny.router")
[1] ‘0.2.2’

I can access the man page of the function router_ui() with and without double colon operators...

> ?router_ui
> ?shiny.router::router_ui

... as seen here in this screenshot from RStudio:

But, when I try to run, for example, this example from your tutorial/blog page...

library(shiny)
library(shiny.router)

# This creates UI for each page.
page <- function(title, content) {
  div(
    titlePanel(title),
    p(content),
    uiOutput("power_of_input")
  )
}

# Part of both sample pages.
home_page <- page("Home page", "This is the home page!")
side_page <- page("Side page", "This is the side page!")

# Callbacks on the server side for the sample pages
home_server <- function(input, output, session) {
  output$power_of_input <- renderUI({
    HTML(paste(
      "I display <strong>square</strong> of input and pass result to <code>output$power_of_input</code>: ", 
      as.numeric(input$int) ^ 2))
  })
}

side_server <- function(input, output, session) {
  output$power_of_input <- renderUI({
    HTML(paste(
      "I display <strong>cube</strong> of input and <strong>also</strong> pass result to <code>output$power_of_input</code>: ", 
      as.numeric(input$int) ^ 3))
  })
}

# Create routing. We provide routing path, a UI as well as a server-side callback for each page.
router <- make_router(
  route("home", home_page, home_server),
  route("side", side_page, side_server)
)

# Create output for our router in main UI of Shiny app.
ui <- shinyUI(fluidPage(
  shiny::sliderInput("int", "Choose integer:", -10, 10, 1, 1),
  router_ui()
))

# Plug router into Shiny server.
server <- shinyServer(function(input, output, session) {
  router(input, output, session)
})

# Run server in a standard way.
shinyApp(ui, server)

... R throws the error Error in router_ui() : could not find function "router_ui".

I tried to specify the package with double colons...

shiny.router::router_ui()

... what yields the error Error: 'router_ui' is not an exported object from 'namespace:shiny.router'.

I verified that the package is loaded via adding packageVersion() and getAnywhere() right after reading the libraries (here, I outcommented router_ui() to make it "run")...

> library(shiny)
> library(shiny.router)

> print(packageVersion("shiny.router"))
> print(getAnywhere(router_ui))
> ...
[1] ‘0.2.2A single object matchingrouter_uiwas found
It was found in the following places
  namespace:shiny.router
with value

function (router) 
{
    shiny::addResourcePath("shiny.router", system.file("www", 
        package = "shiny.router"))
    js_file <- file.path("shiny.router", "shiny.router.js")
    css_file <- file.path("shiny.router", "shiny.router.css")
    list(shiny::singleton(shiny::withTags(shiny::tags$head(shiny::tags$script(type = "text/javascript", 
        src = js_file), shiny::tags$link(rel = "stylesheet", 
        href = css_file)))), shiny::tags$div(id = "router-page-wrapper", 
        lapply(router$routes, function(route) route$ui)))
}
<bytecode: 0x105b37c10>
<environment: namespace:shiny.router>

... what also showed that shiny.router is properly installed and router_ui() should be accessible.

Do you have any clue what's wrong here? Sorry, in case I might have overlooked something...
Thank you!

Steps to reproduce

  1. Install shiny.router version 0.2.2 from CRAN (on a MacOS Monterey 12.3.1 (Apple Chip, M1?)
  2. Run this example from your blog page

Expected behavior

I would expect R to find the function router_ui() and open the app.

Attachments

No response

Screenshots or Videos

No response

Additional Information

No response

Internal linking?

Hello!

Lovely package! I am wondering if anyone else had encountered issues with internal links: https://way2tutorial.com/html/html_internal_links.php.

For example, while I am on page One, found at route /#!/page_one I would like to click on a link which takes me to a different section of the same page.

<a href="/#!/page_one#anchorid">Go to section B</a>

<a id="anchorid">Section B</a>

This isn't working for me, however. Any thoughts on how to approach linking to different sections on the same page?

Thank you so much!
Jessica

observeEvent triggered as many times as page_change

Hello, thanks for your useful package!

I have run into an issue when it comes to changing pages. Here is a minimal example:

library(shiny)
library(shiny.router)

# This generates menu in user interface with links.
menu <- (
  tags$ul(
    tags$li(a(class = "item", href = route_link("home"), "Home page")),
    tags$li(a(class = "item", href = route_link("side"), "Side page"))
  )
)

# This creates UI for each page.
page <- function(title, content, button_name) {
  div(
    menu,
    titlePanel(title),
    p(content),
    actionButton("switch_page", "Click to switch page!"),
    actionButton(button_name, "Go!")
  )
}

# Both sample pages.
home_page <- page("Home page", uiOutput("current_page"), "test1")
side_page <- page("Side page", uiOutput("current_page"), "test2")

home_server <- function(input, output, session) {
  observeEvent(input$test1, {
    print("Foo")
  })
}

side_server <- function(input, output, session) {
  observeEvent(input$test2, {
    print("Bar")
  })
}

# Creates router. We provide routing path, a UI as
# well as a server-side callback for each page.
router <- make_router(
  route("home", ui = home_page, server = home_server),
  route("side", ui = side_page, server = side_server)
)

# Create output for our router in main UI of Shiny app.
ui <- shinyUI(fluidPage(
  router_ui()
))

# Plug router into Shiny server.
server <- shinyServer(function(input, output, session) {
  router(input, output, session)
  
  output$current_page <- renderText({
    page <- get_page(session)
    sprintf("Welcome on %s page!", page)
  })
  
  observeEvent(input$switch_page, {
    if (is_page("home")) {
      change_page("side")
    } else if (is_page("side")) {
      change_page("home")
    }
  })
})

# Run server in a standard way.
shinyApp(ui, server)

Expected behaviour:

  • Clicking the "Click to switch page!" button switches from Home page to Side page and vice versa.
  • Clicking the "Go!" button in the Home page prints "Foo", while clicking it in the Side pages prints "Bar" once.

Actual behaviour:

  • "Click to switch page!" works as intended.
  • The number of times either "Foo" or "Bar" is printed when clicking "Go!" depends on the number of times the "Click to switch page!" button has been clicked. Specifically:
    • On initial load, "Foo" is printed twice.
    • On initial page switch, "Bar" is printed once.
    • For every additional page switch, "Foo" or "Bar" is printed an addition time.

This seems to be related to issue #67. Any feedback is appreciated!

[Feature]: Only load the html for the route

Guidelines

  • I agree to follow this project's Contributing Guidelines.

Description

We have a workaround using UIOutput and renderUI combined with ssession$clientData we can call the module and only transfer the content that is required across to the client based on the url (this is in standard shiny using tabs).

Below I have implemented the same approach / workaround using shiny.router:
`
library(shiny)
library(shiny.router)

This generates menu in user interface with links.

menu <- tags$ul(
tags$li(a(class = "item", href = route_link("/"), "Page")),
tags$li(a(class = "item", href = route_link("second"), "Second page")),
tags$li(a(class = "item", href = route_link("third"), "A third page"))
)

This creates UI for each page.

page <- function(title, content, id) {
ns <- NS(id)
div(
titlePanel(title),
p(content),
textOutput(ns("click_me"))
)
}

Both sample pages.

root_page <- page("Home page", "Converted number of clicks", "root")
second_page <- page("Second page", "Converted number of clicks", "second")
third_page <- page("Third page", "Converted number of clicks", "third")

server_module <- function(id, clicks, power = 1) {
moduleServer(id, function(input, output, session) {
output$click_me <- renderText({
as.numeric(clicks())^power
})
})
}

Create output for our router in main UI of Shiny app.

ui <- fluidPage(
menu,
actionButton("clicks", "Click me!"),
router_ui(
route("/", uiOutput("root_out")),
route("second", uiOutput("second_out")),
route("third", uiOutput("third_out"))
)
)

Plug router into Shiny server.

server <- function(input, output, session) {

router_server()

clicks <- reactive({
input$clicks
})

observe({
if (session$clientData$url_hash == "#!/") {
output$root_out <- renderUI(root_page)
server_module("second", clicks = clicks, power = 2)
}
if (session$clientData$url_hash == "#!/second") {
output$ui <- renderUI(second_page)
server_module("second", clicks = clicks, power = 2)
}
if (session$clientData$url_hash == "#!/third") {
output$ui <- renderUI(third_page)
server_module("second", clicks = clicks, power = 2)
}
})

server_module("root", clicks = clicks)
server_module("third", clicks = clicks, power = 3)
}

Run server in a standard way.

shinyApp(ui, server)
`

From the screenshot you can see that only the root_page content was transferred to the client.

image

This is really useful when you are dealing with large shiny apps that cater for different users and scale well because sessions only deal with the content that they require and not the full app. In certain instances the user rights restrict them to not use certain parts of the app but with the current way shiny.router works, they will still sacrifice load time based on the app and not the session.

Open to discussions about this if you are interested.

Problem

NO

Proposed Solution

Only transfer html for the current route.

Alternatives Considered

Brochure is working in the desired way but only works with Golem. We would like to migrate our app to Rhino but this is preventing us from doing that.

Feature request: support dynamic population of `make_router()`

I was very excited to discover shiny.router and incorporate it into my Shiny application. Managed to get it to work only to realise a caveat in my specific situation: I've built a Shiny app that uses a browser's cookie to determine the permissions a user has (allowing it to show certain tabs but not others). This functionality requires the tabs to be populated in server.R, since the cookie must be passed using Shiny's input$. However, make_router() must be ran in Global.R to be available to ui.R.

Initially I fixed this by only displaying the buttons/links for the tabs the user was allowed to see, but then noticed that the way router works is that it populates the entire ui, then toggles hide/show depending on the URL we're on (clever!). However, in my example this means the user can see tabs they shouldn't be able to see by simply going into their browser's console.

Another solution would be to dynamically populate make_router(), but that's not possible since the permissions aren't known yet to Global.R, since it's ran before server.R.

It'd be great if 'make_router()' could somehow support a dynamic inclusion/exclusion (ran from Server.R). Might be quite a stretch, but thought I'd highlight it just in case!

Thanks for all the great work

Can you demonstrate using this with htmlTemplate?

An important use case arises when you start with a set of html pages (e.g. a commercial theme for a cms, blog, shop, etc). As I see it, it would be preferable to use these html templates directly without rewriting the whole thing using tags$ wrappers. Any idea if the htmlTemplate approach is fundamentally incompatible with shiny.router, or is it possible for this to work?

multiple_radio position inline not working

Hi,

It seems that the argument 'position' of the multiple_radio function does not have any effect when set to 'inline'.

Using the example provided in the package manual:

if (interactive()) {
  # Checkbox
  library(shiny)
  library(shiny.semantic)

  ui <- function() {
      shinyUI(
        semanticPage(
          title = "Checkbox example",
          h1("Radioboxes"),
          multiple_radio("radioboxes", "Select Letter", LETTERS[1:6], value = "A", position = "inline"),
          p("Selected letter:"),
          textOutput("selected_letter")
       )
     )
  }

  server <- shinyServer(function(input, output) {
     output$selected_letter <- renderText(input$radioboxes)
  })

  shinyApp(ui = ui(), server = server)
}

[Feature]: Examples need for ui.R and server.R app design

Guidelines

  • I agree to follow this project's Contributing Guidelines.

Description

All of the examples use app.R and it's not clear how one could leverage shiny.router in an app design with ui.R and server.R especially with moduleServer components.

Problem

Not enough example material

Proposed Solution

More examples

Alternatives Considered

DIY

Allow passing url parameters as lists

There is need to pass GET parameters as lists.
For example shiny::parseQueryString allows parsing links as:
?foo[1]=3&foo[2]=2&val=bar returning list with two objects: foo and val.

Shiny Router Issue - Linking .R pages with Custom Header

I am trying to use R/Shiny router to link .R pages that I have already coded but don't know how to get the router to work with my HTML links. I have a custom header Home.html, that I want to link to screen1.R using shiny router. Can someone help with this?

Home.html

<!DOCTYPE HTML>
<html>
	
    <head>
	
		<title>PPI Sampling System</title>
		<style type="text/css">
			body{background-color:white;}
					
				#PPI-title{
					font: 2em times, sans-serif;
					color: white;
					text-shadow: 1px 1px black;
					}
				
				.header{
					padding: 0px 0px 0px 15px;
					background: linear-gradient(#b4c9dd, white);
					border: 1px solid black;
				}	
					
				#nav-one{
					background-color:white;
					color: black;
					font: 14 px arial, sans-serif;
					border: 1px solid black;
					width: 100%
					}
					
				#nav-two{
					background-color: white;
					font: 14px verdana; 
					font-weight: bold;
					border: 1px solid black;
					top-border: 0px;
					color: blue;
					width: 100%
					}
					
				#nav-three{
					background-color: #b4c9dd;
					border: 1px solid black;
					font: 14px arial, sans-serif;
					font-weight: regular;
					width: 100%
					}

				.dropbtn1 {
					background-color: transparent;
					color: black;
					font: 14px arial, sans-serif;
					font-weight: bold;
					border: none;
					cursor: pointer;
					display: inline-block;
					z-index:1000;
				}

					.dropbtn2 {
					background-color: #ffffff;
					color: black;
					font: 14px arial, sans-serif;
					font-weight: bold;
					border: none;
					cursor: pointer;
					display: inline-block;
					
				}
				
				.dropdown {
					position: relative;
					display: inline-block;
				}

				.dropdown-content {
					display: none;
					position: absolute;
					background-color: #f9f9f9;
					border-style: solid;
					border-width: 1px;
					min-width: 160px;
					
				}

				.dropdown-content a {
					color: black;
					font: 12px arial, sans-serif;
					text-decoration: none;
					display: block;
					font-weight:regular;
					padding: 2px;
					z-index:1;
				}

				.dropdown a:hover {background-color: #a4a4a4}

				.dropdown:hover .dropdown-content {display: block;}
				
				.show {display:block;}

				#first-box{
					border-style: solid;
					border-width: 1px;
					}		
			
			
			.panes-container {
				display: flex;
				width: 100%;
				overflow: hidden;
			}

			.left-pane {
				width: 45%;
				border-style: solid;
				border-width: 1px;
			}

			.panes-separator {
				width: 1px;
				background: black;
				position: relative;
				cursor: col-resize;
			}

			.right-pane {
				flex: auto;
				border-style: solid;
				border-width: 1px;
			}

			.panes-container,
			.panes-separator,
			.left-pane,
			.right-pane {
				margin: 5px;
				padding: 5px;
			}
			
			.blue { color: blue; }
			
			.tiny-button{
				background-color: transparent;
				color: black;
				font: 12px arial, sans-serif;
				font-weight: bold;
				border: none;
				cursor: pointer;
				display: inline-block;
				width:10px;
				height:15px;
			}
				
			.white-button{
				background-color: #ffffff;
				color: black;
				font: 12px arial, sans-serif;
				border: none;
				cursor: pointer;
				display: inline-block;
			}

			.button-style{
				background-color: #ffffff;
				color: black;
				border-style:solid;
				font: 12px arial, sans-serif;
				cursor: pointer;
				display: inline-block;
				padding:4px;
				box-shadow: 0 1 2 0 black;
			}
			
			body {
               font-size:12px;
             
            }
			
			.tit {
               font-size:3em;
            }
			
           .sub {
               font-size:2em;
            }
			
           .cont {
               font-size:1em;
            }
			
            .button {
               position:relative;
               top:0;
               font-size:15px;
			   cursor: pointer;
            }
			
			
			
		</style>
	</head>
	

<!--Same as Previous Cycle Mockup 1, attempting to add split screen-->
	<body onload="script();">
	
		<!-- header of site -->
		
        <div id="colorchange" class="header">
		
        <div id="PPI-title">
			<img src="http://www.conferenceharvester.com/uploads/harvester/photos/-ExhibitorLogo-45716.png" width="75" style="display:inline-block;">
			<!-- image source: http://www.conferenceharvester.com/uploads/harvester/photos/-ExhibitorLogo-45716.png -->
			<h2 id="header" style="display:inline-block; padding:0 0 0 10px;">PPI: Sampling System</h2>
		</div>
        
		<!-- Table for username, database, role-->
			<table style="position:absolute; right:250px; top:20px;font:12px arial, sans-serif;" cellspacing="5px">
			
				<tr>
					<td><b>User:</b> Doe_j</td>
					<td><b>DB:</b> ips-udb-samp</td>
				</tr>
				<tr>
					<td><b>Role:</b> IA</td>
				</tr>
			</table >
            
		<!-- Table for changing font size/header color -->
	
			<table style="position:absolute; right:20px; top:20px; font:12px arial, sans-serif;">
			<tr> 
				<td><b> Font: 
				<div class="button" style="display:inline-block; font:12px arial;"> <a class="inc" > + &nbsp; </a> </div>
				<div class="button" style="display:inline-block; font:12px arial;"> <a class="dec" > - &nbsp; </a> </div>
				<div class="button" style="display:inline-block; font:12px arial;"> <a class="reset"> Reset </a> </b> </div>
				</td>
			</tr>
            <tr>
				<td><b>Color Scheme:</b>
					<button class="tiny-button" style="background:linear-gradient(to right,white,#b4c9dd,black)" onclick="document.getElementById('colorchange').style.background = 'linear-gradient(#b4c9dd,white)'"></button>
					<button class="tiny-button" style="background:linear-gradient(to right,white,#b4ddb5,black)" onclick="document.getElementById('colorchange').style.background = 'linear-gradient(#b4ddb5,white)'"></button>
					<button class="tiny-button" style="background:linear-gradient(to right,white,#f5ce6c,black)" onclick="document.getElementById('colorchange').style.background = 'linear-gradient(#f5ce6c,white)'"></button>
				</td>
				<td>
					<button class="tiny-button" style="background:linear-gradient(to right,white,#b4c9dd,black)" onclick="document.getElementById('nav-three').style.background = '#b4c9dd'"></button>
					<button class="tiny-button" style="background:linear-gradient(to right,white,#b4ddb5,black)" onclick="document.getElementById('nav-three').style.background = '#b4ddb5'"></button>
					<button class="tiny-button" style="background:linear-gradient(to right,white,#f5ce6c,black)" onclick="document.getElementById('nav-three').style.background = '#f5ce6c'"></button>
				</td>
			</tr>
			</table>
		</div>
	
		<!-- first navigation bar -->
		<div>
			<table id="nav-one" cellspacing="10">
			<tr>
				
		    <th> Home </th>
					<th>
						<div class="dropdown">
							<button class="dropbtn2">Sampling Process</button>
							<div class="dropdown-content">
								<a href="#">Browse Frame</a>
								<a href="#">Frame Refinement</a>
								<a href="#">Approve Sample Parameters</a>
								<a href="#">Sample Refinement Round I</a>
								<a href="#">Sample Refinement Round II</a>
								<a href="#">Browse Sample</a>
							</div>
						</div>
					</th>
					<th> Tools</th>
					<th> Help </th>
					<th width="50%"></th>
					<th><a style="font:12px arial, sans-serif; float:right;">Go To: <input type="text"></a></th>
					<th><button class="white-button">Logout</button><th>

			</tr>
			</table>
			
		</div>
		<!--second naviagtion bar-->
		<div>
			<table id="nav-two" cellspacing="10">
				<tr>
					<th> Frame Refinement </th>
					<th> Sample - 99 </th>
					<th> Industry - 311311 </th>
					<th> Cycle - F </th>
					<th> Status Code - 2 </th>
					<th width="30%"></th>
				</tr>
			</table>
		</div>
		<!--third navigation bar-->
		<div class="ex1">
			<table id="nav-three" cellspacing="10">
				<tr>
					<th> <a class="item1" href="#" target="main" >Search Frame </th>
					<th> <a class="item2" href="#" target="main" > View Detail </th>
					<!-- link not working -->
					<th> <a class="item3" href="source(file://filer1/ppi/SamplingProject/2SampleSelection/screen1.R)" target="main" > Clustering </a> </th>
					<th> <a class="item4" href="/page2" target="main"> Reports </a> </th>
					<th> <a class="item5" href="" target="main" >  View Changes </th>
					<th> <a class="item6" href="#" target="main" > Search Universe </th>
					<th>
						<div class="dropdown">
							<button class="dropbtn1"> Search Previous</button>
							<div class="dropdown-content">
							<a href="#" target="main"> Selected Industry</a>
							<a href="#" target="main"> Any Industry</a>
						    </div>
						</div>
					</th>
					<th> Industry Status </th>
					<th width="25%"></th>
				</tr>
			</table>
		</div>
		
		
		<iframe name="main" src="http://www.yahoo.com" width="1570" height="560" scrolling="auto" >  <p>Your browser does not support iframes.</p></iframe>
		
		
<script src="https://cdn.rawgit.com/lingtalfi/simpledrag/master/simpledrag.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>

$(document).ready(function(){

    var fontSize = parseInt($('body').css('font-size'),10);
	
    $('.inc').on('click',function(){
        fontSize++;
        $('body').css('font-size',fontSize+'px');
    })
    $('.dec').on('click',function(){
        fontSize--;
        $('body').css('font-size',fontSize+'px');
    })
	
	 $('.reset').on('click',function(){
        $('body').css('font-size',"12px");
    })
})

</script>


	
</body>
</html>

Router.R

library(shiny)
#devtools::install_github("Appsilon/shiny.router")
library(shiny.router)


Home <- includeHTML('//filer1/ppi/SamplingProject/Home.html')

router <- make_router(
  route("/page2", page2))

ui <- shinyUI(fluidPage(
  Home,
  router_ui()
))

# Plug router into Shiny server.
server <- shinyServer( function(input, output) {
  router(input,output)
  
})

# Run server in a standard way.
shinyApp(ui=ui, server=server)

screen1.R

library(shiny)
library(data.table)
library(DT)
library(dplyr)
library(shinyjs)
library(shinycssloaders)

# List of BLS 2017 Index items
codes <- read.csv("//filer1/ppi/SamplingProject/2017_index_items.csv", header = TRUE, sep = ",", fill=TRUE)

ui = fluidPage(
  
  tags$div(class="flex-container",
  # App title
  titlePanel("Select NAICS code"),
  
  sidebarLayout(
    
    sidebarPanel(
      
      # Drop down menu of NAICS codes. 337920 is a good example.
      selectizeInput("industry", "Search", choices = codes, options = list(placeholder = "Search NAICS",  onInitialize = I('function() { this.setValue(""); }')), width = "10em"),
      h6("Click button below to Cluster by EIN"),
      actionButton('action', 'Cluster by EIN')
      
    ),
      
    # Display the datatable
    mainPanel(DT::dataTableOutput('tbl1'), HTML('&nbsp; &nbsp;') ,DT::dataTableOutput('tbl2'), width = 9  )
    
  )
)
)
#Start Server 

server = function(input, output, session) {
  
  
  # Universe file
  universe <- fread("//filer1/ppi/SamplingProject/universe.csv", header = TRUE, sep = ",", fill = TRUE)
  
  #Frame Universe
  withProgress(message = "Reading file", {
  frame_codes <- Reduce(function(x, y) full_join(x, y, by='NAICS'), list(codes,universe))
  })
  
  withProgress(message = "Reading file", {
  Tail <- fread("//filer1/ppi/SamplingProject/Tail.csv", header = TRUE, sep = ",")
  })
  
  # Make a subset of the universe using the industry code that was inputted
  datatableInput1 <- reactive(subset(frame_codes, NAICS == input$industry)) 

  
  # Use the DataTables library and the buttons extension to create the datatable.
  # Filter adds a search bar to each column. If the columns are numeric, its a range slider, needs to be characters to be a search bar.
  # Add buttons and the ability to reorder the columns by drag and drop.
  output$tbl1 = renderDataTable(
    datatableInput1(),
    filter = "top",
    class = "cell-border stripe", 
    extensions = list("Buttons" = NULL, "ColReorder" = NULL),
    options = list(dom = "Bfrt",
                   paging = FALSE,
                   scrollX = TRUE,
                   scrollY = "80vh",
                   colReorder = TRUE,
                   buttons = list(list(extend = "colvis", text = "Show/Hide Columns"), "copy", "csv", "print")),
    server = TRUE
  )
  
  output$tbl2 <- DT::renderDataTable(
    datatableInput2(),
    filter = "top",
    class = "cell-border stripe", 
    extensions = list("Buttons" = NULL, "ColReorder" = NULL),
    options = list(dom = "Bfrt",
                   paging = FALSE,
                   scrollX = TRUE,
                   scrollY = "80vh",
                   colReorder = TRUE,
                   buttons = list(list(extend = "colvis", text = "Show/Hide Columns"), "copy", "csv", "print")),
    server = TRUE
  )
  
 # Function to Cluster by EIN  
  datatableInput2 <- eventReactive(input$action, {
    
    # Your arbitrary R code goes here
      
       test<-subset(frame_codes[c("LDB_NUM", "EIN", "NAICS", "TOT_WAGES","TOT_EMP")], NAICS == input$industry )
      
      #### Begin Clustering by EIN ####
      
      newdata<-arrange(test, desc(TOT_EMP))
      cluster <- arrange(newdata, EIN, desc(TOT_EMP), desc(TOT_WAGES))
      
      # Seperate into Headers, Singles, and Members
      
      myid.uni <- unique(cluster$EIN)
      a<-length(myid.uni)
      
      headers <- c()
      members<-c()
      singles<-c()
      d<-c()
      w<-c()
      
      # Function to seperate the different Rec_Types
      for (i in 1:a) {
        
        temp<-subset(cluster, EIN==myid.uni[i])
        
        if ( (dim(temp)[1] < 2 ) | myid.uni[i]==0 ) 
        {
          singles.temp<-temp
          singles<-rbind(singles, singles.temp)
          
          
        }
        else 
        {
          
          header.temp<-temp[1,]
          headers<-rbind(headers, header.temp)
          
          members.temp <-temp[1:dim(temp)[1],]
          members<-rbind(members, members.temp)
          
          d.temp<- sum(temp[1:dim(temp)[1],]$TOT_EMP)
          d<-rbind(d,d.temp)
          
          w.temp<- sum(temp[1:dim(temp)[1],]$TOT_WAGES)
          w<-rbind(w,w.temp)
          
        }
        
        
      }
      
      
      h<-headers[order(headers$EIN, -headers$TOT_EMP),]
      m<-members[order(members$EIN, -members$TOT_EMP),]
      s<-singles[order(singles$EIN, -singles$TOT_EMP, -singles$TOT_WAGES),]
      
      h.chars<-replicate(dim(headers)[1],"H")
      m.chars<-replicate(dim(members)[1],"M")
      s.chars<-replicate(dim(singles)[1],"S")
      
      # Make Headers with Total Header Employment
      comb.headers<-data.frame(h,w,d,h.chars)
      
      drops<-c("TOT_EMP", "TOT_WAGES")
      comb.headers<-comb.headers[,!(names(comb.headers) %in% drops) ]
      colnames(comb.headers)[4]<-"TOT_WAGES"
      colnames(comb.headers)[5]<-"TOT_EMP"
      colnames(comb.headers)[6]<-"Rec_Type"
      
      # Make Members with their individual employment
      
      comb.members<-data.frame(m,m.chars)
      colnames(comb.members)[6]<-"Rec_Type"
      
      
      # Make Singles with their individual employment
      
      comb.singles<-data.frame(s,s.chars)
      colnames(comb.singles)[6]<-"Rec_Type"
      comb.singles<-arrange(comb.singles, desc(TOT_EMP), desc(TOT_WAGES))
      
      
      hs.cluster<-rbind(comb.headers,comb.singles)
      hs.cluster<-arrange(hs.cluster, desc(TOT_EMP))
      
      
      # Add cluster IDs 
      # Actual clustering function
      
      df<-hs.cluster
      
      a<-dim(df)[1]
      b<-dim(comb.headers)[1]
      count<-1
      temp4<-c()
      
      
      for(i in 1:a)
      {
        
        temp.s<-c()
        temp.h<-c()
        temp2<-c()
        temp3<-c()
        
        if(grepl( 'S', df[i,6]))
        {
          temp.s<-rbind(temp.s,df[i,])
          temp.s$Cluster_ID<-NA
          temp.s<-temp.s[c(7,1:6)]
          temp4<-rbind(temp4,temp.s)
          
        }
        else
        {
          
          temp.h<-df[i,]
          temp2<-merge(temp.h, comb.members, by='EIN', all.x=TRUE)
          temp2<-temp2[-c(2:6)]
          colnames(temp2)[2]<-"LDB_NUM"
          colnames(temp2)[3]<-"NAICS"
          colnames(temp2)[4]<-"TOT_WAGES"
          colnames(temp2)[5]<-"TOT_EMP"
          colnames(temp2)[6]<-"Rec_Type"
          
          temp2<-temp2[order(-temp2$TOT_EMP),]
          temp3<-rbind(temp.h,temp2)
          
          
          e<-c()
          e<-dim(temp3)[1]
          temp3$Cluster_ID<-rep(count,e)
          temp3<-temp3[c(7,1:6)]
          count<-count+1
          
          
          temp4<-rbind(temp4,temp3)
        }
        
      }
      
      temp5 <-c()
      temp5 <-temp4
      temp5$Rank<-c(1:dim(temp4)[1])
      temp5 <- merge(temp5,Tail, by="LDB_NUM", all.x=TRUE)
      temp5 <- arrange(temp5, Rank)
      
      
      count<-1
      for(i in 1:dim(temp5)[1])
      {
        if(grepl('H',temp5[i,7]))
        {
          temp5[i,1]=count
          count<-count+1
        }
        
      }
      
      final.cluster<-temp5 # Contains Clustered Frame
      return(final.cluster) # Return Clustered Frame
      
  }) # End Function to Cluster by EIN
      
  
  
} # End Server

shinyApp(ui, server)

observeEvent & eventReactive execute repeateadly when going back to side page

Prerequisites
Package version: 0.1.1
Shiny version: 1.3.2
System: Windows 7, google chrome Version 75.0.3770.100

Description

When I return to a previous page either by clicking on the browser´s back button or with the change_page() function and then move again to the current page, observeEvent and eventReactive functions are triggered an additional time.

I include a minimal example, which consists of an example from the shiny.router tutorial page, that I extended to include a selectInput control. If you go back to the first page and then to the side page again and select an element from the selectInput control, the observeEvent function gets triggered an additional time. If you do this again, the function get triggered three times, and so on.

library(shiny)
library(shiny.router)

# This generates menu in user interface with links.
menu <- (
    tags$ul(
        tags$li(a(class = "item", href = route_link("home"), "Home page")),
        tags$li(a(class = "item", href = route_link("side"), "Side page"))
    )
)

# This creates UI for each page.
page <- function(title, content) {
    div(
        menu,
        titlePanel(title),
        p(content),
        actionButton("switch_page", "Click to switch page!")
    )
}

my_page<-function(){
    div(
        uiOutput("my_dropdown_list")
    )}

# Both sample pages.
home_page <- page("Home page", uiOutput("current_page"))
side_page <- my_page()

my_page_server <- function(input, output, session) {

    observeEvent(input$my_dropdown_list,{
        
        cat(paste0("selected element ", input$my_dropdown_list, "\n"))
        
    }, ignoreInit=TRUE, ignoreNULL=TRUE)
    
    output$my_dropdown_list <- renderUI({
        selectInput("my_dropdown_list","Choices:", 
                    choices= c("A","B", "C"),
                    multiple=FALSE,
                    selectize=TRUE
        )
        
    })
}

# Creates router. We provide routing path, a UI as
# well as a server-side callback for each page.
router <- make_router(
    route("home", home_page, NA),
    route("side", side_page, my_page_server)
)

# Create output for our router in main UI of Shiny app.
ui <- shinyUI(fluidPage(
    router_ui()
))

# Plug router into Shiny server.
server <- shinyServer(function(input, output, session) {
    router(input, output, session)
    
    output$current_page <- renderText({
        page <- get_page(session)
        sprintf("Welcome on %s page!", page)
    })
    
    observeEvent(input$switch_page, {
        if (is_page("home")) {
            change_page("side")
        } else if (is_page("side")) {
            change_page("home")
        }
        
    })
})

# Run server in a standard way.
shinyApp(ui, server)

semantic_DT: selected rows are not highlghted

When clicking on a row, it is not highlighted.

if (interactive()){
    library(shiny)
    library(shiny.semantic)
    
    ui <- semanticPage(
        semantic_DTOutput("table"),
        verbatimTextOutput("DT_selection")
    )
    server <- function(input, output, session) {
        output$table <- DT::renderDataTable(
            semantic_DT(iris)
        )
        
        output$DT_selection <- renderPrint({
            print(input$table_rows_selected)
        })
    }
    shinyApp(ui, server)
}

Also, when trying to change the table style (e.g. by adding style = 'default') , an error message appears.
Error: formal argument "style" matched by multiple actual arguments

[Bug]: shiny.router does not work when deploying a shiny app in docker

Guidelines

  • I agree to follow this project's Contributing Guidelines.

Project Version

0.2.3

Platform and OS Version

macOS 13.0.1

Existing Issues

No response

What happened?

Deployed a simple shiny app that uses shiny.router to display two pages. I deployed a docker container locally to test (because eventually want to deployed in Cloud Run). However, the app does not run.

Steps to reproduce

  1. Wrote simple shiny app that displays two pages. You can change pages by pressing a button.
  2. Build a container locally in docker: e.g. docker build -t shinyrouter .
  3. Run it: e.g. docker run -d -p 3838:3838 shinyrouter
  4. Open a browser and type http://localhost:3838
    ...

Expected behavior

Application should have run:
app

Attachments

Shiny app code:

library(shiny)
library(shiny.router)
library(DT)

# This is where the UI of page 1 is specified
home_page <- div(
  titlePanel("Home page"),
  p("This is the home page!"),
  plotOutput("graph"),
  fluidRow(
    column(12,
           align = "right",
           actionButton("next_page", "Next")
    )
  )
  
)

# This is where the UI of page 2 is specified
another_page <- div(
  titlePanel("Another page"),
  p("This is the another page!"),
  DTOutput("table"),
  fluidRow(
    column(12,
           align = "right",
           actionButton("prev_page", "Back to Main")
    )
  ),
  verbatimTextOutput("rows")
)

#this connects each page with an HTML address 
router <- make_router(
  route("/", home_page, NA),
  route("another", another_page, NA)
)

# This is where it is all put together - only need to point to the router
ui <- fluidPage(
  router$ui
)

server <- function(input, output, session) {
  # important to add this line in the server function
  router$server(input, output, session)
  
  # When the button is clicked in page 1 go to page 2
  observeEvent(input$next_page,
               change_page("another")
  )
  # When the button is clicked on page 2 go back to page 1
  observeEvent(input$prev_page,
               change_page("/")
  )
  
  # other functions on the server side
  output$graph <- renderPlot({
    x <- runif(100)
    y <- runif(100)
    plot(x, y)
  })
  
  output$table <- renderDT({
    iris
  })
  
  # get specific rows from the table and extract the specific value
  output$rows = renderPrint({
    s <- input$table_rows_selected
    if (length(s)) {
      print(iris[s, "Sepal.Width"], sep = '\n')
    }
  })
}

shinyApp(ui, server)

Dockerfile:

FROM rocker/shiny-verse:4.2.2

# system libraries of general use
RUN apt-get update && apt-get install -y \
    libcurl4-gnutls-dev \
    libssl-dev

RUN install2.r --deps TRUE --ncpus -1 shiny.router

# copy the app to the image

COPY app.R /srv/shiny-server/app.R
#RUN chmod +x /srv/shiny-server/shiny-server.sh

# select port
EXPOSE 3838
# allow permission
RUN sudo chown -R shiny:shiny /srv/shiny-server
# run app
CMD ["/usr/bin/shiny-server"]

Screenshots or Videos

error_screen

Additional Information

No response

disable_bootstrap_on_bookmark() errors out with development version of Shiny

Guidelines

  • I agree to follow this project's Contributing Guidelines.

What happened?

If you have the development version of shiny (remotes::install_github("rstudio/shiny")), then

shiny.router::disable_bootstrap_on_bookmark("test_bookmark")

results in the error

Error in `renderDependencies(resolveDependencies(list(func_bootstrapLib())), 
    srcType = "href")`: Dependency bootstrap 3.4.1 does not have a usable source

which is most likely a result of rstudio/shiny#3537

Steps to reproduce

remotes::install_github("rstudio/shiny")

Question: How to use shiny.router with moduleServer?

hi there,

I have a reasonably large app using shiny.fluent and shiny.router (thanks for these packages they are really great), with a structure similar to the code below:

library(shiny)
library(shiny.fluent)
library(shiny.router)

# navigation pane objects
navigation_styles <- list(
  root = list(height = "100%", width = "30%", boxSizing = "border-box", 
              border = "1px solid #eee", overflowY = "auto"))

link_groups <- list(
  list(
    links = list(
      list(name = 'Home', url = '#!/', key = 'home'),
      list(name = "Documents", key = "documents", url = '#!/documents'),
      list(name = "Pages", key = "pages", url = '#!/pages')
    )))

# page UIs
home_page_ui <- tagList(p("Homepage"))

documents_page_ui <- function(id){
  ns <- NS(id)
  tagList(textOutput(ns("title")))
}

pages_page_ui <- function(id){
  ns <- NS(id)
  tagList(textOutput(ns("title")))
}

# page servers
documents_page_server <- function(id = "documents1"){
  moduleServer( id, function(input, output, session){
    ns <- session$ns
    
    output$title <- renderText("Documents Page")
  })
}

pages_page_server <- function(id = "pages1"){
  moduleServer( id, function(input, output, session){
    ns <- session$ns
    
    output$title <- renderText("Pages Page")
  })
}


# Creates router. We provide routing path, a UI as
# well as a server-side callback for each page.
router <- make_router(
  route("/", home_page_ui),
  route("documents", documents_page_ui("documents1")),
  route("pages", pages_page_ui("pages1"))
)


shinyApp(
  ui = fluidPage(
    Nav(
      groups = link_groups,
      selectedKey = "home",
      styles = navigation_styles
    ), 
    fluidPage(
      router$ui
    )
  ),
  
  server = function(input, output, session) {
    router$server(input, output, session)
    
    # page servers
    documents_page_server("documents1")
    pages_page_server("pages1")
  }
)

Each of my pages are inside modules because they are quite similar and it helps me avoid namespace conflicts.
Notice that my page servers are just in the main server function, not in the router.

This works okay, but suffers from the issue of every page reloading when something changes, not just the current page ( #70). From what I understand from reading other issues, this issue has been fixed in the current version of shiny.router, but requires calling the page server from the router - ie. using route(path, ui, server = ACTUAL_SERVER_FUNCTION). Is that correct?

I can only get that to work if the pages are not modules that use moduleServer(). Is there any way to get route to work with modules? And is there any way to pass parameters (e.g. a set of reactive values) to those server modules if it does work?

Cheers

shiny.router 0.1.1 cannot work with R 3.5.0 or higher

Thanks again for your great effort on shiny.router and it worked perfect on our application. But we came across a problem after upgrading to R3.5.0. We also tested the following sample application and we observe the same issue.

Source code:

library(shiny)
#devtools::install_github("Appsilon/shiny.router")
library(shiny.router)

# This generates menu in user interface with links.
menu <- (
  tags$ul(
    tags$li(a(class = "item", href = "/", "Page")),
    tags$li(a(class = "item", href = "/other", "Other"))
  )
)

# This creates UI for each page.
page <- function(title, content) {
  div(
    menu,
    titlePanel(title),
    p(content)
  )
}

# Both sample pages.
root_page <- page("Home page", "Welcome on sample routing page!")
other_page <- page("Some other page", "Lorem ipsum dolor sit amet")

# Creates router. We provide routing path and UI for this page.
router <- make_router(
  route("/", root_page),
  route("/other", other_page)
)

# Creat output for our router in main UI of Shiny app.
ui <- shinyUI(fluidPage(
  router_ui()
))

# Plug router into Shiny server.
server <- shinyServer(function(input, output) {
  router(input, output)
})

# Run server in a standard way.
shinyApp(ui, server)

R version: 3.5.0 or higher

image

Issues:
When we click the "Other", it shows the following error:
image

BTW, do you have plan to push it to CRAN?

Modifying query params ends Shiny session

Prerequisites

Package version: latest github version (0.1.1) with shiny 1.3.1
System Details: Windows 10
Running platform: clean R session, testing app in Google chrome

Description

Modifying query params ends Shiny session.

Our use case is that user authentication is done with reactiveValues in Shiny session and all the info is lost when we use links with query params.

Expected behavior:
Going from app_url/?foo=bar/#!/route to app_url/?foo=baz/#!/route should not end Shiny session.

Actual behavior:
When changing query strings, e.g. going from app_url/?foo=bar/#!/route to app_url/?foo=baz/#!/route the Shiny session ends (see MWE below).

Possible fix:
We worked around this by modifying parse_url_path in R/pages.R to support query strings of the form app_url/#!/route?foo=bar and the session apperas to be maintained between router calls.

Minimal example - steps to reproduce

MWE to reproduce hosted here: https://github.com/piotrbajger/query.mwe.

Run the app with RScript runapp.R, click on the links and inspect the console output to see that the session ends.

Import shiny by default

The package uses lots of shiny function recalls with shiny::. We should import shiny within NAMESPACE and use raw shiny-function names.

[Bug]: 404 page does not work when a user opens a non-valid link without going to a valid one first

Guidelines

  • I agree to follow this project's Contributing Guidelines.

Project Version

0.3.0

Platform and OS Version

No response

Existing Issues

No response

What happened?

404 page does not work when a user opens a non-valid link without going to a valid one first

Steps to reproduce

#Shiny Router 0.3.0 ----------
options(shiny.port = 3333)
install.packages("[email protected]") #Restart session if needed
library(shiny.router)
library(shiny)

router <- router_ui(
  route("/", shiny::tags$div(shiny::tags$span("Hello world"))),
  route("/main", shiny::tags$div(h1("Main page"), p("Lorem ipsum."))),
  page_404 = page404(tags$div(tags$h1("This is the 404 Page")))
)

shinyApp(
  router,
  function(input, output, session) {router_server()}
)

# 1. Visit http://localhost:3333/#!/non-existing-page directly (without ever visiting homepage) from your browser
# 2. Visit http://localhost:3333/#!/ first and then visit http://localhost:3333/#!/non-existing-page

Expected behavior

404 page is presented when a user opens a non-valid link.

Attachments

No response

Screenshots or Videos

No response

Additional Information

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.