Appsilon's shiny.router - Official Release and New Features

By:
Krystian Igras
September 19, 2018

<em><strong>Updated</strong>: November 1, 2022.</em> [UPDATE - December 2020] - A new version of the package was recently released. With it, we decided to write a new article on the library alongside a short interactive tutorial. You can read it <a href="https://appsilon.com/shiny-router-020/" target="_blank" rel="noopener noreferrer">here</a>. In web applications, routing is the process of using URLs to drive the user interface. Routing adds more possibilities and flexibility while building a complex and advanced web application, offering dividing the app into separated sections. If you're an avid fan of R Shiny, then the shiny router package provides a solution to your routing problems. In this article, we'll share what's new with interactive code examples. <img class="wp-image-1293 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b27160492f6bc11385ac20_1.gif" alt="shiny.router routing" width="693" height="293" /> Image 1 - shiny.router demonstration Table of contents: <ul><li><a href="#newfeatures">shiny router - New Features</a><ul><li><a href="#feature1">Routing moved fully to R assets</a></li><li><a href="#feature2">Separated server for each bookmark</a></li></ul> </li> <li><a href="#parameters">How to Pass Parameters to an App using GET URL Variables</a></li> <li><a href="#server-side">Operating Routing from the Server Side in shiny router</a></li> <li><a href="#stylings">Add Stylings to shiny router with Boostrap and Semantic UI</a></li> <li></li> <li><a href="#future">Further Steps and Future Plans</a></li> </ul> <hr /> <h2>Shiny Router - New Features</h2> Contributing to open source is incorporated into Appsilon's mission. Last week we updated the i18n internationalization package, now it's time for the router. Our <a href="https://github.com/Appsilon/shiny.router">shiny.router</a> package provides you with an easy solution on how to add routing to your Shiny application. Since the last release we managed to improve and add some great features to it. Find them on the list below! <h3 id="feature1">Routing moved fully to R assets</h3> Previously the package was based on the external <a href="https://visionmedia.github.io/page.js/">page.js</a> library. Thanks to the use of a Shiny session object we moved it fully to R. <h3 id="feature2">Separated server for each bookmark</h3> Now each bookmark can be isolated and fully working shiny app. The new feature allows you not only to separate UI for each bookmark - but you may also define its own server now. Just check the below example! <figure class="highlight"> <pre class=" language-r"><code class=" language-r" data-lang="r"><span class="hljs-keyword">library</span>(shiny) <span class="hljs-keyword">library</span>(shiny.router) <br><span class="hljs-comment"># This creates UI for each page.</span> page &lt;- <span class="hljs-keyword">function</span>(title, content) {  div(    titlePanel(title),    p(content),    uiOutput(<span class="hljs-string">"power_of_input"</span>)  ) } <br><span class="hljs-comment"># Part of both sample pages.</span> home_page &lt;- page(<span class="hljs-string">"Home page"</span>, <span class="hljs-string">"This is the home page!"</span>) side_page &lt;- page(<span class="hljs-string">"Side page"</span>, <span class="hljs-string">"This is the side page!"</span>) <br><span class="hljs-comment"># Callbacks on the server side for the sample pages</span> home_server &lt;- <span class="hljs-keyword">function</span>(input, output, session) {  output$power_of_input &lt;- renderUI({    HTML(paste(      <span class="hljs-string">"I display &lt;strong&gt;square&lt;/strong&gt; of input and pass result to &lt;code&gt;output$power_of_input&lt;/code&gt;: "</span>,      as.numeric(input$int) ^ <span class="hljs-number">2</span>))  }) } <br>side_server &lt;- <span class="hljs-keyword">function</span>(input, output, session) {  output$power_of_input &lt;- renderUI({    HTML(paste(      <span class="hljs-string">"I display &lt;strong&gt;cube&lt;/strong&gt; of input and &lt;strong&gt;also&lt;/strong&gt; pass result to &lt;code&gt;output$power_of_input&lt;/code&gt;: "</span>,      as.numeric(input$int) ^ <span class="hljs-number">3</span>))  }) } <br><span class="hljs-comment"># Create routing. We provide routing path, a UI as well as a server-side callback for each page.</span> router &lt;- make_router(  route(<span class="hljs-string">"home"</span>, home_page, home_server),  route(<span class="hljs-string">"side"</span>, side_page, side_server) ) <br><span class="hljs-comment"># Create output for our router in main UI of Shiny app.</span> ui &lt;- shinyUI(fluidPage(  shiny::sliderInput(<span class="hljs-string">"int"</span>, <span class="hljs-string">"Choose integer:"</span>, -<span class="hljs-number">10</span>, <span class="hljs-number">10</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>),  router_ui() )) <br><span class="hljs-comment"># Plug router into Shiny server.</span> server &lt;- shinyServer(<span class="hljs-keyword">function</span>(input, output, session) {  router(input, output, session) }) <br><span class="hljs-comment"># Run server in a standard way.</span> shinyApp(ui, server)</code></pre> </figure> <img class="wp-image-1299 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d6fa215de09d850f0c06_ec4ea295_2.gif" alt="shiny.router - separate servers" width="535" height="252" /> Image 2 - R Shiny app with two routes <h2>How to Pass Parameters to an App using GET URL Variables</h2> <figure class="highlight"> <pre class=" language-r"><code class=" language-r" data-lang="r"><span class="hljs-keyword">library</span>(shiny) <span class="hljs-keyword">library</span>(shiny.router) <br><span class="hljs-comment"># Main page UI.</span> home_page &lt;- div(  titlePanel(<span class="hljs-string">"Home page"</span>),  p(<span class="hljs-string">"This is the home page!"</span>),  uiOutput(<span class="hljs-string">"power_of_input"</span>) ) <br><span class="hljs-comment"># Creates routing. We provide routing path, a UI as well as a server-side callback for each page.</span> router &lt;- make_router(  route(<span class="hljs-string">"/"</span>, home_page, <span class="hljs-literal">NA</span>) ) <br><span class="hljs-comment"># Create output for our router in main UI of Shiny app.</span> ui &lt;- shinyUI(fluidPage(  shiny::sliderInput(<span class="hljs-string">"int"</span>, <span class="hljs-string">"Choose integer:"</span>, -<span class="hljs-number">10</span>, <span class="hljs-number">10</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>),  router_ui() )) <br><span class="hljs-comment"># Plug router into Shiny server.</span> server &lt;- shinyServer(<span class="hljs-keyword">function</span>(input, output, session) {  router(input, output, session)    component &lt;- reactive({    <span class="hljs-keyword">if</span> (is.null(get_query_param()$add)) {      <span class="hljs-keyword">return</span>(<span class="hljs-number">0</span>)    }    as.numeric(get_query_param()$add)  })    output$power_of_input &lt;- renderUI({    HTML(paste(      <span class="hljs-string">"I display input increased by &lt;code&gt;add&lt;/code&gt; GET parameter from app url and pass result to &lt;code&gt;output$power_of_input&lt;/code&gt;: "</span>,      as.numeric(input$int) + component()))  }) }) <br><span class="hljs-comment"># Run server in a standard way.</span> shinyApp(ui, server)</code></pre> </figure> <img class="wp-image-1300 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b2714f49d471fc2a9e6e4c_3.gif" alt="shiny.router - passing get parameter" width="718" height="252" /> Image 3 - Passing parameters <h2 id="server-side">Operating Routing from the Server Side in a shiny router</h2><ul><li><code>route_link</code> - function for changing URL for bookmark by adding hashbang (#!) prefix</li><li><code>change_page</code> - function for changing the currently displayed page</li><li><code>get_page</code> - function to extract the "hash" part of the URL</li><li><code>is_page</code> - a function that verifies if the current page was passed successfully.</li></ul> <figure class="highlight"> <pre class=" language-r"><code class=" language-r" data-lang="r"><span class="hljs-keyword">library</span>(shiny) <span class="hljs-keyword">library</span>(shiny.router) <br><span class="hljs-comment"># This generates menu in user interface with links.</span> menu &lt;- (  tags$ul(    tags$li(a(class = <span class="hljs-string">"item"</span>, href = route_link(<span class="hljs-string">"home"</span>), <span class="hljs-string">"Home page"</span>)),    tags$li(a(class = <span class="hljs-string">"item"</span>, href = route_link(<span class="hljs-string">"side"</span>), <span class="hljs-string">"Side page"</span>))  ) ) <br><span class="hljs-comment"># This creates UI for each page.</span> page &lt;- <span class="hljs-keyword">function</span>(title, content) {  div(    menu,    titlePanel(title),    p(content),    actionButton(<span class="hljs-string">"switch_page"</span>, <span class="hljs-string">"Click to switch page!"</span>)  ) } <br><span class="hljs-comment"># Both sample pages.</span> home_page &lt;- page(<span class="hljs-string">"Home page"</span>, uiOutput(<span class="hljs-string">"current_page"</span>)) side_page &lt;- page(<span class="hljs-string">"Side page"</span>, uiOutput(<span class="hljs-string">"current_page"</span>)) <br><span class="hljs-comment"># Creates router. We provide routing path, a UI as</span> <span class="hljs-comment"># well as a server-side callback for each page.</span> router &lt;- make_router(  route(<span class="hljs-string">"home"</span>, home_page, <span class="hljs-literal">NA</span>),  route(<span class="hljs-string">"side"</span>, side_page, <span class="hljs-literal">NA</span>) ) <br><span class="hljs-comment"># Create output for our router in main UI of Shiny app.</span> ui &lt;- shinyUI(fluidPage(  router_ui() )) <br><span class="hljs-comment"># Plug router into Shiny server.</span> server &lt;- shinyServer(<span class="hljs-keyword">function</span>(input, output, session) {  router(input, output, session)    output$current_page &lt;- renderText({    page &lt;- get_page(session)    sprintf(<span class="hljs-string">"Welcome on %s page!"</span>, page)  })    observeEvent(input$switch_page, {    <span class="hljs-keyword">if</span> (is_page(<span class="hljs-string">"home"</span>)) {      change_page(<span class="hljs-string">"side"</span>)    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (is_page(<span class="hljs-string">"side"</span>)) {      change_page(<span class="hljs-string">"home"</span>)    }  }) }) <br><span class="hljs-comment"># Run server in a standard way.</span> shinyApp(ui, server)</code></pre> </figure> <img class="wp-image-1301 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b2710e6912befbe2c7e56f_4.gif" alt="shiny.router - navigation functions" width="322" height="218" /> Image 4 - Server-side routing <h2 id="stylings">Add Stylings to the shiny router with Boostrap and Semantic UI</h2> You can suppress <a href="https://getbootstrap.com/">Bootstrap</a> dependency on the specified bookmark. You can switch between Bootstrap and Semantic UI pages or disable styles. This is especially useful when using both Bootstrap and <a href="https://semantic-ui.com/">semantic-UI</a> frameworks in one application.   <figure class="highlight"> <pre class=" language-r"><code class=" language-r" data-lang="r"> <span class="hljs-keyword">library</span>(shiny) <span class="hljs-keyword">library</span>(shiny.router) <span class="hljs-keyword">library</span>(shiny.semantic) <br><span class="hljs-comment"># Both sample pages.</span> bootstrap_page &lt;- fluidPage(  sidebarLayout(    sidebarPanel(      sliderInput(<span class="hljs-string">"obs_bootstrap"</span>,                  <span class="hljs-literal">NULL</span>,                  min = <span class="hljs-number">0</span>,                  max = <span class="hljs-number">100</span>,                  value = <span class="hljs-number">50</span>,                  step = <span class="hljs-number">1</span>)    ),    mainPanel(      p(<span class="hljs-string">"Selected value:"</span>),      textOutput(<span class="hljs-string">"value_bootstrap"</span>)    )  ) ) <br>semanticui_page &lt;- semanticPage(  slider_input(<span class="hljs-string">"obs_semantic"</span>,              min = <span class="hljs-number">0</span>,              max = <span class="hljs-number">100</span>,              value = <span class="hljs-number">50</span>,              step = <span class="hljs-number">1</span>),  p(<span class="hljs-string">"Selected value:"</span>),  textOutput(<span class="hljs-string">"value_semantic"</span>) ) <br><span class="hljs-comment"># Creates router. We provide routing path, a UI as</span> <span class="hljs-comment"># well as a server-side callback for each page.</span> router &lt;- make_router(  route(<span class="hljs-string">"bootstrap"</span>, bootstrap_page),  route(<span class="hljs-string">"semantic"</span>, semanticui_page),  page_404 = page404(<span class="hljs-string">"You opened non existing bookmark!"</span>) ) <br><span class="hljs-comment"># Create output for our router in main UI of Shiny app.</span> ui &lt;- shinyUI(  tagList(    tags$head(      singleton(disable_bootstrap_on_bookmark(<span class="hljs-string">"semantic"</span>))    ),    router_ui()  ) ) <br><span class="hljs-comment"># Plug router into Shiny server.</span> server &lt;- shinyServer(<span class="hljs-keyword">function</span>(input, output, session) {  router(input, output, session)  output$value_bootstrap &lt;- renderText(input$obs_bootstrap)  output$value_semantic &lt;- renderText(input$obs_semantic) }) <br><span class="hljs-comment"># Run server in a standard way.</span> shinyApp(ui, server)</code></pre> </figure> <img class="wp-image-1302 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b271658a44a285470c5e4f_5.gif" alt="shiny.router - more addins" width="459" height="248" /> Image 5 - shiny.router stylings <hr /> <h2>How to Get shiny router</h2> <img class="wp-image-1303 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b0234edde585f52453aa67_RGB-electronic-version-05-e1537358166566.webp" alt="shiny.router hex sticker" width="250" height="289" /> Image 6 - shiny.router logo The package is available both on <a href="https://cran.r-project.org/web/packages/shiny.router/index.html">R Cran</a> and <a href="https://github.com/Appsilon/shiny.router">Github</a>. If you will stumble upon any issues please file them on GitHub where our team will reply. Are you using already our router package in your shiny projects? <a href="mailto:hello@wordpress.appsilon.com"><em><strong>Say hello</strong></em></a> to us and share your story - it will help us make our open source better.  Look for us at R events and collect our hex stickers! &nbsp; <h2 id="future">Further Steps and Future Plans</h2> We are planning to constantly work on the package to make it more versatile. As the next steps, we want to allow passing parameters between separated bookmarks servers and the ability to save the application state. We hope that you will appreciate the improvements we did within the last two years. Feedback will be very valuable for us.

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!

Is Your Software GxP Compliant?

Download a checklist designed for clinical managers in data departments to make sure that software meets requirements for FDA and EMA submissions.
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
r
tutorial
open source
shiny dashboards