├── .gitignore
├── split-code
├── ui
│ ├── tab1.R
│ └── tab2.R
├── server
│ ├── tab1.R
│ └── tab2.R
├── app.R
└── README.md
├── hide-tab
├── hide-tab.gif
├── README.md
└── app.R
├── url-inputs
├── url-inputs.gif
├── README.md
└── app.R
├── plot-spinner
├── plot-spinner.gif
├── www
│ └── spinner.gif
├── app.R
└── README.md
├── busy-indicator
├── busy-indicator.gif
├── www
│ └── ajax-loader-bar.gif
├── app.R
├── README.md
└── helpers.R
├── dropdown-groups
├── dropdown-groups.png
├── app.R
└── README.md
├── loading-screen
├── loading-screen.gif
├── README.md
└── app.R
├── multiple-pages
├── multiple-pages.gif
├── README.md
└── app.R
├── navigate-history
├── navigate-history.gif
├── www
│ └── app-shinyjs.js
├── README.md
└── app.R
├── select-input-large
├── selectize-large.png
├── README.md
└── app.R
├── run-arbitrary-code
├── run-arbitrary-code.gif
├── README.md
└── app.R
├── serve-images-files
├── serve-images-files.gif
├── README.md
└── app.R
├── shinydashboard-sidebar-hide
├── shinydashboard-sidebar-hide.gif
├── app.R
└── README.md
├── auto-kill-app
├── app.R
└── README.md
├── proxy-click
├── README.md
└── app.R
├── debug-value
├── app.R
└── README.md
├── advanced-shiny.Rproj
├── simple-toggle
├── app.R
└── README.md
├── close-window
├── README.md
└── app.R
├── server-to-ui-variable
├── app.R
└── README.md
├── upload-file-names
├── README.md
└── app.R
├── message-r-to-javascript
├── app.R
└── README.md
├── show-warnings-messages
├── README.md
└── app.R
├── multiple-scrollspy-basic
├── www
│ ├── app.css
│ └── app-shinyjs.js
├── README.md
└── app.R
├── error-custom-message
├── app.R
└── README.md
├── navbar-add-text
├── README.md
└── app.R
├── fb-share-img
├── README.md
├── app.R
└── www
│ └── app.js
├── forked-task
├── README.md
├── app.R
└── create_forked_task.R
├── message-javascript-to-r-force
├── app.R
└── README.md
├── javascript-to-r-handler
├── app.R
└── README.md
├── multiple-scrollspy-advanced
├── www
│ ├── app.css
│ └── app-shinyjs.js
├── README.md
└── app.R
├── update-input
├── README.md
├── app.R
├── www
│ └── app-shinyjs.js
└── update-input.R
├── reactive-trigger
└── README.md
├── fb-login
├── app.R
├── api.R
├── README.md
└── www
│ ├── api.js
│ └── app.js
├── message-javascript-to-r
├── app.R
└── README.md
├── reactive-dedupe
└── README.md
├── api-ajax
├── api.R
├── app.R
├── www
│ ├── app.js
│ └── api.js
└── README.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 |
--------------------------------------------------------------------------------
/split-code/ui/tab1.R:
--------------------------------------------------------------------------------
1 | tabPanel(
2 | "Tab 1",
3 | uiOutput("content1")
4 | )
--------------------------------------------------------------------------------
/split-code/ui/tab2.R:
--------------------------------------------------------------------------------
1 | tabPanel(
2 | "Tab 2",
3 | uiOutput("content2")
4 | )
--------------------------------------------------------------------------------
/split-code/server/tab1.R:
--------------------------------------------------------------------------------
1 | output$content1 <- renderUI({
2 | "Tab 1 content"
3 | })
--------------------------------------------------------------------------------
/split-code/server/tab2.R:
--------------------------------------------------------------------------------
1 | output$content2 <- renderUI({
2 | "Tab 2 content"
3 | })
--------------------------------------------------------------------------------
/hide-tab/hide-tab.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/hide-tab/hide-tab.gif
--------------------------------------------------------------------------------
/url-inputs/url-inputs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/url-inputs/url-inputs.gif
--------------------------------------------------------------------------------
/plot-spinner/plot-spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/plot-spinner/plot-spinner.gif
--------------------------------------------------------------------------------
/plot-spinner/www/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/plot-spinner/www/spinner.gif
--------------------------------------------------------------------------------
/busy-indicator/busy-indicator.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/busy-indicator/busy-indicator.gif
--------------------------------------------------------------------------------
/dropdown-groups/dropdown-groups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/dropdown-groups/dropdown-groups.png
--------------------------------------------------------------------------------
/loading-screen/loading-screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/loading-screen/loading-screen.gif
--------------------------------------------------------------------------------
/multiple-pages/multiple-pages.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/multiple-pages/multiple-pages.gif
--------------------------------------------------------------------------------
/navigate-history/navigate-history.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/navigate-history/navigate-history.gif
--------------------------------------------------------------------------------
/busy-indicator/www/ajax-loader-bar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/busy-indicator/www/ajax-loader-bar.gif
--------------------------------------------------------------------------------
/select-input-large/selectize-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/select-input-large/selectize-large.png
--------------------------------------------------------------------------------
/run-arbitrary-code/run-arbitrary-code.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/run-arbitrary-code/run-arbitrary-code.gif
--------------------------------------------------------------------------------
/serve-images-files/serve-images-files.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/serve-images-files/serve-images-files.gif
--------------------------------------------------------------------------------
/shinydashboard-sidebar-hide/shinydashboard-sidebar-hide.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/advanced-shiny/master/shinydashboard-sidebar-hide/shinydashboard-sidebar-hide.gif
--------------------------------------------------------------------------------
/auto-kill-app/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage()
4 |
5 | server <- function(input, output, session) {
6 | session$onSessionEnded(stopApp)
7 | }
8 |
9 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/proxy-click/README.md:
--------------------------------------------------------------------------------
1 | # Press the Enter key to simulate a button press
2 |
3 | This is a simple app with a tiny bit of JavaScript that shows you how to add an attribute to an element that will cause a Enter key press inside that element to trigger a click on a button.
--------------------------------------------------------------------------------
/debug-value/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | verbatimTextOutput("debug")
5 | )
6 |
7 | server <- function(input, output, session) {
8 | output$debug <- renderPrint({
9 | sessionInfo()
10 | })
11 | }
12 |
13 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/url-inputs/README.md:
--------------------------------------------------------------------------------
1 | # Prepopulate Shiny inputs when an app loads based on URL parameters
2 |
3 | This simple app demonstrates how you can fill out certain input fields when a Shiny app loads based on URL parameters.
4 |
5 | ---
6 |
7 | [](./url-inputs.gif)
--------------------------------------------------------------------------------
/advanced-shiny.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
--------------------------------------------------------------------------------
/dropdown-groups/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | selectInput("country", "Select country", list(
5 | "Europe" = c("Germany", "Spain"),
6 | "North America" = c("Canada", "United States" = "USA")
7 | ))
8 | )
9 |
10 | server <- function(input, output, session) {
11 | }
12 |
13 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/simple-toggle/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | actionButton("toggle", "Toggle the following text"),
5 | conditionalPanel(
6 | condition = "input.toggle % 2 == 0",
7 | "This text gets toggled on and off"
8 | )
9 | )
10 |
11 | server <- function(input, output, session) {
12 | }
13 |
14 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/close-window/README.md:
--------------------------------------------------------------------------------
1 | # Close the window (and stop the app) with a button click
2 |
3 | This simple example shows how you can have a button that, when clicked, will close the current browser tab and stop the running Shiny app (you can choose to do only one of these two actions).
4 |
5 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to call custom JavaScript functions.
--------------------------------------------------------------------------------
/select-input-large/README.md:
--------------------------------------------------------------------------------
1 | # Select input with more breathing room
2 |
3 | One common CSS question in Shiny is how to make the select input dropdown menu have some more whitespace. It's actually very easy to do with just two CSS rules, as demonstrated in this example.
4 |
5 | [See a real shiny app where I used this concept](http://daattali.com/shiny/shinyjs-demo/)
6 |
7 | ---
8 |
9 | [](./selectize-large.png)
--------------------------------------------------------------------------------
/select-input-large/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | css <- "
4 | #large .selectize-input { line-height: 40px; }
5 | #large .selectize-dropdown { line-height: 30px; }"
6 |
7 | ui <- fluidPage(
8 | tags$style(type='text/css', css),
9 | selectInput("select1", "Regular select", LETTERS),
10 | div(id = "large",
11 | selectInput("select2", "Large select", LETTERS)
12 | )
13 | )
14 |
15 | server <- function(input, output, session) {}
16 |
17 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/close-window/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | jscode <- "shinyjs.closeWindow = function() { window.close(); }"
5 |
6 | ui <- fluidPage(
7 | useShinyjs(),
8 | extendShinyjs(text = jscode, functions = c("closeWindow")),
9 | actionButton("close", "Close window")
10 | )
11 |
12 | server <- function(input, output, session) {
13 | observeEvent(input$close, {
14 | js$closeWindow()
15 | stopApp()
16 | })
17 | }
18 |
19 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/server-to-ui-variable/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | selectInput("num", "Choose a number", 1:10),
5 | conditionalPanel(
6 | condition = "output.square",
7 | "That's a perfect square!"
8 | )
9 | )
10 |
11 | server <- function(input, output, session) {
12 | output$square <- reactive({
13 | sqrt(as.numeric(input$num)) %% 1 == 0
14 | })
15 | outputOptions(output, 'square', suspendWhenHidden = FALSE)
16 | }
17 |
18 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/upload-file-names/README.md:
--------------------------------------------------------------------------------
1 | # Fix filenames of files selected via fileInput()
2 |
3 | When selecting files using a `fileInput()`, the filenames of the selected files are not retained. This is not usually a problem because usually you only care about the contents of a file and not its name. But sometimes you may actually need to know the original name of each selected file. This example shows how to write a simple function `fixUploadedFilesNames()` to rename uploaded files back to their original names.
--------------------------------------------------------------------------------
/hide-tab/README.md:
--------------------------------------------------------------------------------
1 | # Hide a tab
2 |
3 | This app demonstrates how `shinyjs` can be used to hide/show a specific tab in a `tabsetPanel`. In order to use this trick, the `tabsetPanel` must have an id. Using this id and the value of the specific tab you want to hide/show, you can call `shinyjs::hide()`/`shinyjs::show()`/`shinyjs::toggle()`.
4 |
5 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to show/hide the tab.
6 |
7 | ---
8 |
9 | [](./hide-tab.gif)
--------------------------------------------------------------------------------
/message-r-to-javascript/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | jscode <- '
4 | Shiny.addCustomMessageHandler("mymessage", function(message) {
5 | alert(message);
6 | });
7 | '
8 |
9 | ui <- fluidPage(
10 | tags$head(tags$script(HTML(jscode))),
11 | textInput("text", "Enter text", "Hello"),
12 | actionButton("btn", "JavaScript popup")
13 | )
14 |
15 | server <- function(input, output, session) {
16 | observeEvent(input$btn, {
17 | session$sendCustomMessage("mymessage", input$text)
18 | })
19 | }
20 |
21 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/url-inputs/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | textInput("name", "Name"),
5 | numericInput("age", "Age", 25)
6 | )
7 |
8 | server <- function(input, output, session) {
9 | observe({
10 | query <- parseQueryString(session$clientData$url_search)
11 | if (!is.null(query[['name']])) {
12 | updateTextInput(session, "name", value = query[['name']])
13 | }
14 | if (!is.null(query[['age']])) {
15 | updateNumericInput(session, "age", value = query[['age']])
16 | }
17 | })
18 | }
19 |
20 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/show-warnings-messages/README.md:
--------------------------------------------------------------------------------
1 | # Show a function's messages and warnings to the user
2 |
3 | Sometimes when you call a function, it may print out some messages and/or warnings to the console. If you want to be able to relay these warnings/messages to your app in real time, you can combine `withCallingHandlers` with the `html` function from [shinyjs](https://github.com/daattali/shinyjs).
4 |
5 | (Originally developed as an [answer on StackOverflow](http://stackoverflow.com/questions/30474538/possible-to-show-console-messages-written-with-message-in-a-shiny-ui))
6 |
--------------------------------------------------------------------------------
/split-code/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- navbarPage(
4 | title = "Split app code across multiple files",
5 | # include the UI for each tab
6 | source(file.path("ui", "tab1.R"), local = TRUE)$value,
7 | source(file.path("ui", "tab2.R"), local = TRUE)$value
8 | )
9 |
10 | server <- function(input, output, session) {
11 | # Include the logic (server) for each tab
12 | source(file.path("server", "tab1.R"), local = TRUE)$value
13 | source(file.path("server", "tab2.R"), local = TRUE)$value
14 | }
15 |
16 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/multiple-scrollspy-basic/www/app.css:
--------------------------------------------------------------------------------
1 | #tab1-content div {
2 | height: 500px;
3 | border: 1px solid #DDD;
4 | }
5 | #tab2-content div {
6 | height: 900px;
7 | border: 1px solid #DDD;
8 | }
9 | #myscrollspy {
10 | position: fixed;
11 | top: 300px;
12 | left: 100px;
13 | }
14 | #myscrollspy li > a {
15 | padding: 0;
16 | width: 15px;
17 | height: 15px;
18 | background-color: #DDD;
19 | margin-bottom: 5px;
20 | }
21 | #myscrollspy li > a:hover {
22 | background-color: #AAA;
23 | }
24 | #myscrollspy li.active > a {
25 | background-color: #444;
26 | }
--------------------------------------------------------------------------------
/server-to-ui-variable/README.md:
--------------------------------------------------------------------------------
1 | # Use a variable from the server in a UI `conditionalPanel()`
2 |
3 | When using a conditional panel in the UI, the condition is usually an expression that uses an input value. But what happens when you want to use a conditional panel with a more complex condition that is not necessarily directly related to an input field? This example shows how to define an output variable in the server code that you can use in the UI. An alternative approach is to use the `show()` and `hide()` functions from the [shinyjs](https://github.com/daattali/shinyjs) package.
--------------------------------------------------------------------------------
/error-custom-message/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | css <- "
4 | .shiny-output-error { visibility: hidden; }
5 | .shiny-output-error:before {
6 | visibility: visible;
7 | content: 'An error occurred. Please contact the admin.'; }
8 | }
9 | "
10 |
11 | ui <- fluidPage(
12 | tags$style(type="text/css", css),
13 | textOutput("text1"),
14 | textOutput("text2")
15 | )
16 |
17 | server <- function(input, output, session) {
18 | output$text1 <- renderText({
19 | stop("Some error")
20 | })
21 | output$text2 <- renderText({
22 | "Hello"
23 | })
24 | }
25 |
26 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/error-custom-message/README.md:
--------------------------------------------------------------------------------
1 | # Show user a generic error message when a Shiny error occurs in an output
2 |
3 | When a Shiny output encounters an error, the exact error message will be shown to the user in place of the output. This is generally a good feature because it's easier to debug when you know the exact error. But sometimes this is undesireable if you want to keep the specifics of what happened unknown to the user, and you prefer to just show the user a generic "Some error occurred; please contact us" message. This may sound counter intuitive, but you can actually do this with a tiny bit of CSS, as this example shows.
--------------------------------------------------------------------------------
/loading-screen/README.md:
--------------------------------------------------------------------------------
1 | # Loading screen
2 |
3 | This simple app shows how to add a "Loading..." screen overlaying the main app while the app's server is initializing. The main idea is to include an overlay element that covers the entire app (using CSS), hide the main app's UI, and at the end of the server function show the UI and hide the overlay.
4 |
5 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to show/hide elements.
6 |
7 | [See a real shiny app where I used this concept](http://daattali.com/shiny/ddpcr/)
8 |
9 | ---
10 |
11 | [](./loading-screen.gif)
--------------------------------------------------------------------------------
/simple-toggle/README.md:
--------------------------------------------------------------------------------
1 | # Toggle a UI element (alternate between show/hide) with a button
2 |
3 | Sometimes you want to toggle a section of the UI every time a button is clicked. Since each time a button is clicked, its value is increased by 1, you can use that to toggle an element: place the element inside a `conditionalPanel()`, and in the `condition`, check for the value of the button modulo 2 (to check if the button has been pressed an even or odd number of times). This is the most basic toggling behaviour. If you want anything more advanced, you can use the `toggle()` function from the [shinyjs](https://github.com/daattali/shinyjs) package.
--------------------------------------------------------------------------------
/dropdown-groups/README.md:
--------------------------------------------------------------------------------
1 | # Select input with groupings of options
2 |
3 | This isn't really a trick as much as an [undocumented feature](https://github.com/rstudio/shiny/issues/1321) in Shiny that not many people know about. Usually when people write dropdowns in Shiny, all the options are just provided as one long list. But it is possible to have groups of items, and it's very easy to do, for example:
4 |
5 | ```
6 | selectInput("country", "Select country", list(
7 | "Europe" = c("Germany", "Spain"),
8 | "North America" = c("Canada", "United States" = "USA")
9 | ))
10 | ```
11 |
12 | ---
13 |
14 | [](./dropdown-groups.png)
--------------------------------------------------------------------------------
/navbar-add-text/README.md:
--------------------------------------------------------------------------------
1 | # Adding text (or inputs) to the navigation bar in a navbarPage
2 |
3 | Traditionally, a `navbarPage()` only accepts tabs and menu items inside of it. Even though the Bootstrap navbar (which is what Shiny uses) supports adding text and input widgets into the navbar, Shiny doesn't have support for that. This app shows you how to very easily achieve that.
4 |
5 | The idea is to write a wrapper around `navbarPage()` that accepts all the same parameters and simply calls `navbarPage()`, but also accepts a `text` parameter. After creating the navbar page with Shiny, we can modify the generated HTML to simply add the HTML for the text, as the app code shows.
6 |
--------------------------------------------------------------------------------
/shinydashboard-sidebar-hide/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinydashboard)
3 | library(shinyjs)
4 |
5 | ui <- dashboardPage(
6 | dashboardHeader(),
7 | dashboardSidebar(),
8 | dashboardBody(
9 | useShinyjs(),
10 | actionButton("showSidebar", "Show sidebar"),
11 | actionButton("hideSidebar", "Hide sidebar")
12 | )
13 | )
14 |
15 | server <-function(input, output) {
16 | observeEvent(input$showSidebar, {
17 | shinyjs::removeClass(selector = "body", class = "sidebar-collapse")
18 | })
19 | observeEvent(input$hideSidebar, {
20 | shinyjs::addClass(selector = "body", class = "sidebar-collapse")
21 | })
22 | }
23 |
24 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/auto-kill-app/README.md:
--------------------------------------------------------------------------------
1 | # Automatically stop a Shiny app when closing the browser tab
2 |
3 | When developing a Shiny app and running the app in the browser (as opposed to inside the RStudio Viewer), it can be annoying that when you close the browser window, the app is still running and you need to manually press "Esc" to kill it. By adding a single line to the server code `session$onSessionEnded(stopApp)`, a Shiny app will automatically stop running whenever the browser tab (or any session) is closed.
4 |
5 | Note that this can be useful for local development, but you should be very careful not to deploy this code in a real server because you don't want your real Shiny app to stop every time a user leaves the app.
--------------------------------------------------------------------------------
/navigate-history/www/app-shinyjs.js:
--------------------------------------------------------------------------------
1 | // whenever the user navigates with the previous/next buttons in the browser,
2 | // tell the Shiny app to restore the history based on the URL navigated to
3 | shinyjs.init = function() {
4 | window.onpopstate = function (event) {
5 | Shiny.onInputChange('navigatedTo', location.search);
6 | }
7 | }
8 |
9 | // update the URL to reflect the current state
10 | shinyjs.updateHistory = function(params) {
11 | var queryString = [];
12 | for (var key in params) {
13 | queryString.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
14 | }
15 | queryString = '?' + queryString.join('&');
16 | history.pushState(null, null, queryString)
17 | }
--------------------------------------------------------------------------------
/fb-share-img/README.md:
--------------------------------------------------------------------------------
1 | # Sharing images on Facebook
2 |
3 | There are two ways to share images on Facebook, and hence there are two share buttons in the sample app. The first one (easier) uses the conventional way of sharing an image on Facebook using an image URL and will ask the user to confirm sharing. The second method shares a base64 encoded image (which means we get get the image data straight from Shiny) and can be done completely programatically, but it's a little bit more hacky.
4 |
5 | A user will be asked to log in to Facebook if they aren't logged in. Note that the Facebook app settings do not allow a URL of `localhost` or `127.0.0.1` to be used, so in order to run this app locally you will need to change the URL to `fuf.me`.
--------------------------------------------------------------------------------
/shinydashboard-sidebar-hide/README.md:
--------------------------------------------------------------------------------
1 | # Hide/show shinydashboard sidebar programmatically
2 |
3 | A common question regarding `shinydashboard` is how to programmatically hide/show the sidebar.
4 |
5 | To solve this problem, I first looked at the HTML and tried to see what happens when the button to toggle the sidebar is clicked. It seems like the `
` HTML tag gains and loses a CSS class "sidebar-collapse" when the toggle button is clicked. Therefore, all we have to do to show/hide the sidebar is simply remove/add that class to the document's body tag. This can easily be done using the [shinyjs](https://github.com/daattali/shinyjs) package.
6 |
7 | ---
8 |
9 | [](./shinydashboard-sidebar-hide.gif)
--------------------------------------------------------------------------------
/hide-tab/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | shinyApp(
5 | ui = fluidPage(
6 | useShinyjs(),
7 | checkboxInput("foo", "Show tab2", TRUE),
8 | tabsetPanel(
9 | id = "navbar",
10 | tabPanel(title = "tab1",
11 | value = "tab1",
12 | h1("Tab 1")
13 | ),
14 | tabPanel(title = "tab2",
15 | value = "tab2",
16 | h1("Tab 2")
17 | ),
18 | tabPanel(title = "tab3",
19 | value = "tab3",
20 | h1("Tab 3")
21 | )
22 | )
23 | ),
24 | server = function(input, output) {
25 | observe({
26 | toggle(condition = input$foo, selector = "#navbar li a[data-value=tab2]")
27 | })
28 | }
29 | )
--------------------------------------------------------------------------------
/forked-task/README.md:
--------------------------------------------------------------------------------
1 | # Create a cancellable long-running task
2 |
3 | In shiny (or R in general), when you start running a function, you generally cannot do anything else until that function completes. This means that if the user of a shiny app does something that results in a 2-minute calculation, the entire app becomes unusable and the user has to wait 2 minutes before they can interact with the app again. This has been a problem for some people, and the shiny team is currently looking into providing a solution for this. In the meantime, Joe Cheng (author of Shiny) [came up with a nice workaroud](https://gist.github.com/jcheng5/9504798d93e5c50109f8bbaec5abe372). His solution will likely not work on Windows and it is not a robust fool-proof solution, but itcan get the job done.
--------------------------------------------------------------------------------
/split-code/README.md:
--------------------------------------------------------------------------------
1 | # Split app code across multiple files (when codebase is large)
2 |
3 | When creating Shiny apps with a lot of code and a complex UI, it can sometimes get very messy and difficult to maintain your code when it's all in one file. What you can do instead is have one "main" UI and "main" server and split your UI and server code into multiple files. This can make your code much more manageable and easier to develop when it grows large. You can split the code however you want, but I usually like to split it logically: for example, if my app has 4 tabs then the UI for each tab would be in its own file and the server code for each tab would be in its own file. The example code here shows how to separate the code of an app with two tabs into 2 UI files and 2 server files (one for each tab).
--------------------------------------------------------------------------------
/multiple-scrollspy-basic/www/app-shinyjs.js:
--------------------------------------------------------------------------------
1 | // initialize common scrollspy control
2 | shinyjs.init = function() {
3 | $('body').scrollspy({ target: '#myscrollspy' });
4 | }
5 |
6 | // update scrollspy control with the contents of the current tab
7 | // and refresh the control to make the changes take effect
8 | shinyjs.updateScrollspy = function(tab) {
9 | if (tab == 'tab1' || tab == 'tab2') {
10 | var $tabContent = $('#' + tab + '-content');
11 | var tabSections = $tabContent.find('.scrollspy-section');
12 | var scrollspyHtml = '';
13 | $.each(tabSections, function(idx, el) {
14 | scrollspyHtml += '';
15 | });
16 | $('#myscrollspy').children('ul').html(scrollspyHtml);
17 | $('body').scrollspy('refresh');
18 | }
19 | }
--------------------------------------------------------------------------------
/show-warnings-messages/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | foo <- function() {
4 | message("one message")
5 | Sys.sleep(1)
6 | message("two messages")
7 | Sys.sleep(1)
8 | warning("...and a warning")
9 | }
10 |
11 | ui <- fluidPage(
12 | shinyjs::useShinyjs(),
13 | actionButton("btn","Click me"),
14 | textOutput("text")
15 | )
16 |
17 | server <- function(input, output, session) {
18 | observeEvent(input$btn, {
19 | withCallingHandlers({
20 | shinyjs::html(id = "text", html = "")
21 | foo()
22 | },
23 | message = function(m) {
24 | shinyjs::html(id = "text", html = m$message, add = TRUE)
25 | },
26 | warning = function(m) {
27 | shinyjs::html(id = "text", html = m$message, add = TRUE)
28 | })
29 | })
30 | }
31 |
32 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/fb-share-img/app.R:
--------------------------------------------------------------------------------
1 | # Dean Attali, July 2015
2 |
3 | library(shiny)
4 |
5 | ui <- fluidPage(
6 | tags$head(
7 | includeScript("www/app.js")
8 | ),
9 | h3("In order for Facebook sharing to work, you need to change the URL from 127.0.0.1 to 'fuf.me'"),
10 | actionButton("fbShareBtn", "Share image with popup (URL)"), br(), br(),
11 | actionButton("fbShare64Btn", "Share image programatically (base-64, generated in Shiny)"),
12 | plotOutput("plot")
13 | )
14 |
15 | server <- function(input, output, session) {
16 | # generate a random plot each time with timestamp, just to have a unique plot each time
17 | output$plot <- renderPlot({
18 | num <- as.integer(sample(5:100, 1))
19 | plot(1:num, main = paste(num, Sys.time()))
20 | })
21 | }
22 |
23 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/message-javascript-to-r-force/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | jscode <- '
4 | $(function() {
5 | $(document).keypress(function(e) {
6 | if (e.key == "1" || e.key == "2" || e.key == "3") {
7 | Shiny.onInputChange("clickbox", [e.key, Math.random()]);
8 | }
9 | });
10 | });
11 | '
12 |
13 | ui <- fluidPage(
14 | tags$head(tags$script(HTML(jscode))),
15 | h3("Press the keys 1, 2, 3 on your keyboard to tick/untick the boxes"),
16 | checkboxInput("check1", "One"),
17 | checkboxInput("check2", "Two"),
18 | checkboxInput("check3", "Three")
19 | )
20 |
21 | server <- function(input, output, session) {
22 | observeEvent(input$clickbox, {
23 | boxname <- paste0("check", input$clickbox[1])
24 | updateCheckboxInput(session, boxname, value = !input[[boxname]])
25 | })
26 | }
27 |
28 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/javascript-to-r-handler/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | jscode <- '
4 | $(function() {
5 | $(document).keypress(function(e) {
6 | Shiny.onInputChange("keypress1", e.key);
7 | Shiny.onInputChange("keypress2:mylist", e.key);
8 | });
9 | });
10 | '
11 |
12 | shiny::registerInputHandler("mylist", function(data, ...) {
13 | list(data)
14 | }, force = TRUE)
15 |
16 | ui <- fluidPage(
17 | tags$head(tags$script(HTML(jscode))),
18 | h3("Press any key"),
19 | "Raw key press:",
20 | verbatimTextOutput("text1"),
21 | "Key press wrapped in a list:",
22 | verbatimTextOutput("text2")
23 | )
24 |
25 | server <- function(input, output, session) {
26 | output$text1 <- renderPrint({
27 | input$keypress1
28 | })
29 | output$text2 <- renderPrint({
30 | input$keypress2
31 | })
32 | }
33 |
34 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/multiple-pages/README.md:
--------------------------------------------------------------------------------
1 | # Shiny app with sequence of pages
2 |
3 | This app demonstrates how to write a Shiny app that has a sequence of different pages, where the user can navigate to the next/previous page. This can be useful in many scenarios that involve a multi-step process. This behaviour can also be achieved by simply using tabs, but when using tabs the user can freely move from any tab to any other tab, while this approach restricts the user to only move to the previous/next step, and can also control when the user can move on to the next page (by enabling/disabling the navigation buttons using `shinyjs::enable` and `shinyjs::disable`).
4 |
5 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to show/hide the pages and to enable/disable the buttons.
6 |
7 | ---
8 |
9 | [](./multiple-pages.gif)
--------------------------------------------------------------------------------
/multiple-scrollspy-advanced/www/app.css:
--------------------------------------------------------------------------------
1 | #tab1-content .scrollspy-section {
2 | height: 500px;
3 | border: 1px solid #DDD;
4 | }
5 | #tab1-scrollspy {
6 | position: fixed;
7 | }
8 | #tab1-scrollspy li > a {
9 | background-color: #FFF;
10 | }
11 | #tab1-scrollspy li > a:hover {
12 | background-color: #EEE;
13 | }
14 | #tab1-scrollspy li.active > a {
15 | background-color: #AAA;
16 | }
17 | #tab2-content .scrollspy-section {
18 | height: 900px;
19 | border: 1px solid #DDD;
20 | }
21 | #tab2-scrollspy {
22 | position: fixed;
23 | top: 300px;
24 | }
25 | #tab2-scrollspy li > a {
26 | padding: 0;
27 | width: 15px;
28 | height: 15px;
29 | background-color: #DDD;
30 | margin-bottom: 5px;
31 | }
32 | #tab2-scrollspy li > a:hover {
33 | background-color: #AAA;
34 | }
35 | #tab2-scrollspy li.active > a {
36 | background-color: #444;
37 | }
--------------------------------------------------------------------------------
/update-input/README.md:
--------------------------------------------------------------------------------
1 | # Update multiple Shiny inputs without knowing input type
2 |
3 | Shiny allows you to update an input element only if you know the type of input. Furthermore, Shiny only allows you to update input elements one by one. This Shiny app shows how you can update an input only using its ID and without knowing its type, and how to update multiple inputs together.
4 |
5 | First, the Shiny input builder functions are wrapped by a custom function that adds the input type information to the input's HTML. Then whenever you want to update an input only based on its ID, we can use JavaScript to determine the type of input, report it back to Shiny, and then call the correct update function. This is done with a call to `updateShinyInput(session, "inputid", "new value")`. You can also use `updateShinyInputs(list(...))` to update many inputs together in one call.
--------------------------------------------------------------------------------
/plot-spinner/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | mycss <- "
4 | #plot-container {
5 | position: relative;
6 | }
7 | #loading-spinner {
8 | position: absolute;
9 | left: 50%;
10 | top: 50%;
11 | z-index: -1;
12 | margin-top: -33px; /* half of the spinner's height */
13 | margin-left: -33px; /* half of the spinner's width */
14 | }
15 | #plot.recalculating {
16 | z-index: -2;
17 | }
18 | "
19 |
20 | ui <- fluidPage(
21 | tags$head(tags$style(HTML(mycss))),
22 | actionButton("btn", "Plot (takes 2 seconds)"),
23 | div(id = "plot-container",
24 | tags$img(src = "spinner.gif",
25 | id = "loading-spinner"),
26 | plotOutput("plot")
27 | )
28 | )
29 |
30 | server <- function(input, output, session) {
31 | output$plot <- renderPlot({
32 | input$btn
33 |
34 | Sys.sleep(2)
35 | plot(rnorm(50))
36 | })
37 | }
38 |
39 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/update-input/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | source("update-input.R")
5 |
6 | ui <- fluidPage(
7 | useShinyjs(),
8 | extendShinyjs("www/app-shinyjs.js", functions = c("getInputType")),
9 |
10 | textInput("text", "Text input", "some text"),
11 | selectInput("select", "Select input", LETTERS),
12 | numericInput("numeric", "Numeric input", 5),
13 | actionButton("btn", "Update text to 'new value', select to 'G', and number to '9'", class = "btn-primary")
14 | )
15 |
16 | server <- function(input, output, session) {
17 | observeEvent(input$btn, {
18 | newValues <- list("text" = "new value",
19 | "select" = "G",
20 | "numeric" = 9)
21 | updateShinyInputs(session, newValues)
22 | # OR one by one
23 | # updateShinyInput(session, "text", "new value")
24 | })
25 | }
26 |
27 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/multiple-scrollspy-basic/README.md:
--------------------------------------------------------------------------------
1 | # Multiple scrollspy on same page - basic
2 |
3 | The Bootstrap *scrollspy* plugin does not support multiple scrollspy objects per page. This Shiny app demonstrates how to support scrollspy on multiple tabs by having one common scrollspy control that gets updated via JavaScript whenever a tab is changed to reflect the contents of the new tab. Look at the code to see how this is achieved.
4 |
5 | This approach is useful if you don't want to have to hardcode the scrollspy code since it will automatically generate the scrollspy control for each tab. However, the major disadvantage of this approach is that if you want different pages to have very different looking scrollspy controls, it will be hard to achieve with this method because only one common control is created.
6 |
7 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to call custom JavaScript functions.
--------------------------------------------------------------------------------
/serve-images-files/README.md:
--------------------------------------------------------------------------------
1 | # Serve files (images/text files/etc) instead of webpages from a Shiny app
2 |
3 | It is possible to serve an image or another file directly from your Shiny app instead of a webpage. The method shown here is a simple proof-of-concept of how to achieve this functionality. It supports passing GET parameters to the file-generating logic so that the file can be parameterized.
4 |
5 | The main idea is to use `session$clientData$registerDataObj` to associate a file with a specific endpoint URL. Since this URL is generated dynamically and cannot be used by the user before accessing the app, the Shiny app automatically redirects the user to the requested file after generating it and figuring out the URL internally.
6 |
7 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package.
8 |
9 | ---
10 |
11 | [](./serve-images-files.gif)
--------------------------------------------------------------------------------
/proxy-click/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | jscode <- '
4 | $(function() {
5 | var $els = $("[data-proxy-click]");
6 | $.each(
7 | $els,
8 | function(idx, el) {
9 | var $el = $(el);
10 | var $proxy = $("#" + $el.data("proxyClick"));
11 | $el.keydown(function (e) {
12 | if (e.keyCode == 13) {
13 | $proxy.click();
14 | }
15 | });
16 | }
17 | );
18 | });
19 | '
20 |
21 | ui <- fluidPage(
22 | tags$head(tags$script(HTML(jscode))),
23 | actionButton("btn", "Click me to print the value in the text field"),
24 | div("Or press Enter when the text field is focused to \"press\" the button"),
25 | tagAppendAttributes(
26 | textInput("text", NULL, "foo"),
27 | `data-proxy-click` = "btn"
28 | )
29 | )
30 |
31 | server <- function(input, output, session) {
32 | observeEvent(input$btn, {
33 | cat(input$text, "\n")
34 | })
35 | }
36 |
37 | shinyApp(ui, server)
38 |
--------------------------------------------------------------------------------
/upload-file-names/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | fixUploadedFilesNames <- function(x) {
4 | if (is.null(x)) {
5 | return()
6 | }
7 |
8 | oldNames = x$datapath
9 | newNames = file.path(dirname(x$datapath),
10 | x$name)
11 | file.rename(from = oldNames, to = newNames)
12 | x$datapath <- newNames
13 | x
14 | }
15 |
16 | ui <- fluidPage(
17 | fileInput("file", "Choose files", multiple = TRUE),
18 | h3("Original file input value"),
19 | dataTableOutput("originalfiles"),
20 | h3("New file input value"),
21 | dataTableOutput("newfiles")
22 | )
23 |
24 | server <- function(input, output, session) {
25 | output$originalfiles <- renderDataTable(
26 | input$file,
27 | options = list(dom = "", searching = FALSE)
28 | )
29 | output$newfiles <- renderDataTable(
30 | fixUploadedFilesNames(input$file),
31 | options = list(dom = "", searching = FALSE)
32 | )
33 | }
34 |
35 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/multiple-pages/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | NUM_PAGES <- 5
5 |
6 | ui <- fluidPage(
7 | useShinyjs(),
8 | hidden(
9 | lapply(seq(NUM_PAGES), function(i) {
10 | div(
11 | class = "page",
12 | id = paste0("step", i),
13 | "Step", i
14 | )
15 | })
16 | ),
17 | br(),
18 | actionButton("prevBtn", "< Previous"),
19 | actionButton("nextBtn", "Next >")
20 | )
21 |
22 | server <- function(input, output, session) {
23 | rv <- reactiveValues(page = 1)
24 |
25 | observe({
26 | toggleState(id = "prevBtn", condition = rv$page > 1)
27 | toggleState(id = "nextBtn", condition = rv$page < NUM_PAGES)
28 | hide(selector = ".page")
29 | show(sprintf("step%s", rv$page))
30 | })
31 |
32 | navPage <- function(direction) {
33 | rv$page <- rv$page + direction
34 | }
35 |
36 | observeEvent(input$prevBtn, navPage(-1))
37 | observeEvent(input$nextBtn, navPage(1))
38 | }
39 |
40 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/plot-spinner/README.md:
--------------------------------------------------------------------------------
1 | # Show a spinning "loading" animation while a plot is recalculating
2 |
3 | **Update:** There is now a package [`shinycssloaders`](https://cran.r-project.org/package=shinycssloaders) to do this easier!
4 |
5 | When a Shiny plot is recalculating, the plot gets grayed out. This app shows how you can add a spinner wheel on top of the plot while it is recalculating, to make it clear to the user that the plot is reloading. There can be many different ways to achieve a similar result using different combinations of HTML/CSS, this example is just the simplest one I came up with.
6 |
7 | The idea is to place a spinner image in the same container as the plot, center it, and give it a below-default z-index. Whenever the plot is recalculting, make the plot's z-index even lower so that the spinner will show.
8 |
9 | [See a real shiny app where I used this concept](http://daattali.com/shiny/ddpcr/)
10 |
11 | ---
12 |
13 | [](./plot-spinner.gif)
14 |
--------------------------------------------------------------------------------
/run-arbitrary-code/README.md:
--------------------------------------------------------------------------------
1 | # Run arbitrary code live in Shiny - great for testing during development
2 |
3 | **NOTE: This is now part of the `shinyjs` package. All you need to achieve the results below is to run two functions in your shiny app - look at the documentation for `?shinyjs::runcode`.**
4 |
5 | When I develop Shiny apps or packages for Shiny, I often find myself wanting to be able to run R code on-demand while the app is running. Outside of Shiny, in regular R programming, we have the R console where we can run any command at any point in time, but in Shiny we don't really have that. So while developing, I often add a text input that lets me type any R code into it and then run it. This is very useful for testing.
6 |
7 | **WARNING:** Do not provide this in a Shiny app that anyone else can access. This should be **strictly used for testing locally**. Having other people run arbitrary code in your app is asking for trouble.
8 |
9 | ---
10 |
11 | [](./run-arbitrary-code.gif)
12 |
--------------------------------------------------------------------------------
/loading-screen/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | appCSS <- "
5 | #loading-content {
6 | position: absolute;
7 | background: #000000;
8 | opacity: 0.9;
9 | z-index: 100;
10 | left: 0;
11 | right: 0;
12 | height: 100%;
13 | text-align: center;
14 | color: #FFFFFF;
15 | }
16 | "
17 |
18 | ui <- fluidPage(
19 | useShinyjs(),
20 | inlineCSS(appCSS),
21 |
22 | # Loading message
23 | div(
24 | id = "loading-content",
25 | h2("Loading...")
26 | ),
27 |
28 | # The main app code goes here
29 | hidden(
30 | div(
31 | id = "app-content",
32 | p("This is a simple example of a Shiny app with a loading screen.")
33 | )
34 | )
35 | )
36 |
37 | server <- function(input, output) {
38 | # Simulate work being done for 1 second
39 | Sys.sleep(1)
40 |
41 | # Hide the loading message when the rest of the server function has executed
42 | hide(id = "loading-content", anim = TRUE, animType = "fade")
43 | show("app-content")
44 | }
45 |
46 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/busy-indicator/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | source("helpers.R") # Load all the code needed to show feedback on a button click
5 |
6 | ui <- fluidPage(
7 | useShinyjs(),
8 | tags$style(appCSS),
9 | selectInput("select", "Select an option",
10 | c("This one is okay" = "ok",
11 | "This will give an error" = "error")),
12 |
13 | # Wrap the button in the function `withBusyIndicatorUI()`
14 | withBusyIndicatorUI(
15 | actionButton(
16 | "uploadFilesBtn",
17 | "Process data",
18 | class = "btn-primary"
19 | )
20 | )
21 | )
22 |
23 | server <- function(input, output, session) {
24 | observeEvent(input$uploadFilesBtn, {
25 | # When the button is clicked, wrap the code in a call to `withBusyIndicatorServer()`
26 | withBusyIndicatorServer("uploadFilesBtn", {
27 | Sys.sleep(1)
28 | if (input$select == "error") {
29 | stop("choose another option")
30 | }
31 | })
32 | })
33 | }
34 |
35 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/reactive-trigger/README.md:
--------------------------------------------------------------------------------
1 | # Create a reactive trigger
2 |
3 | A reactive trigger can be used when you want to be able to explicitly trigger a reactive expression. You can think of it as being similar to an action button, except instead of clicking on a button to trigger an expression, you can programatically cause the trigger. This concept and code was created by Joe Cheng (author of shiny).
4 |
5 | To use a reactive trigger, define the construct with this code:
6 |
7 | ```
8 | makeReactiveTrigger <- function() {
9 | rv <- reactiveValues(a = 0)
10 | list(
11 | depend = function() {
12 | rv$a
13 | invisible()
14 | },
15 | trigger = function() {
16 | rv$a <- isolate(rv$a + 1)
17 | }
18 | )
19 | }
20 | ```
21 |
22 | Then you can instantiate a reactive trigger with
23 |
24 | ```
25 | myTrigger <- makeReactiveTrigger()
26 | ```
27 |
28 | To use it, you need to call `myTrigger$depend()` in any reactive code that should re-run when the trigger is fired, and you can call `myTrigger$trigger()` to set off the trigger.
--------------------------------------------------------------------------------
/fb-login/app.R:
--------------------------------------------------------------------------------
1 | # Dean Attali, July 2015
2 |
3 | library(shiny)
4 |
5 | ui <- fluidPage(
6 | tags$head(
7 | includeScript("www/api.js"),
8 | includeScript("www/app.js")
9 | ),
10 | actionButton("fbLoginBtn", "Login with Facebook"),
11 | div(id = "fbStatus")
12 | )
13 |
14 | server <- function(input, output, session) {
15 | source("api.R", local = TRUE)$value
16 |
17 | api.fblogin <- function(params) {
18 | fbGraphUrl <- "https://graph.facebook.com"
19 | fbGraphPath <- "me"
20 | token <- params$access_token
21 | url <- sprintf("%s/%s", fbGraphUrl, fbGraphPath)
22 | response <- httr::GET(url, query = list(access_token = token))
23 |
24 | if (httr::status_code(response) == 200) {
25 | name <- httr::content(response)$name
26 | cat(name)
27 | # now we can register the user in our database and process them
28 | return(list(success = TRUE, name = name))
29 | } else {
30 | stop("OpenGraph did not succeed")
31 | }
32 | }
33 | }
34 |
35 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/message-javascript-to-r/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | jscode <- '
4 | $(function() {
5 | $(document).keypress(function(e) {
6 | if (e.key == "1" || e.key == "2" || e.key == "3") {
7 | Shiny.onInputChange("clickbox", e.key);
8 | }
9 | });
10 | });
11 | '
12 |
13 | ui <- fluidPage(
14 | tags$head(tags$script(HTML(jscode))),
15 | h3("Press the keys 1, 2, 3 on your keyboard to tick/untick the boxes"),
16 | checkboxInput("check1", "One"),
17 | checkboxInput("check2", "Two"),
18 | checkboxInput("check3", "Three"),
19 | p("If you noticed that you can't press the same key multiple times in a row,",
20 | a("see the follow-up example",
21 | href = "https://github.com/daattali/advanced-shiny/tree/master/message-javascript-to-r-force"),
22 | "to see how to fix that")
23 | )
24 |
25 | server <- function(input, output, session) {
26 | observeEvent(input$clickbox, {
27 | boxname <- paste0("check", input$clickbox)
28 | updateCheckboxInput(session, boxname, value = !input[[boxname]])
29 | })
30 | }
31 |
32 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/reactive-dedupe/README.md:
--------------------------------------------------------------------------------
1 | # Create a reactive value that only triggers when its value changes
2 |
3 | If you understand shiny and reactivity well, you will know that a reactive expression gets re-run whenever any of its reactive dependencies are invalidated. This is generally the desired behaviour, but there is one caveat that comes up occassionally: even if the underlying value of the reactive dependency hasn't changed, it can still be considered "invalidated". This can mean that a reactive expression will run again with exactly the same values because its dependencies have not changed, even though they're invalidated. Joe Cheng (author of shiny) has [a solution](https://github.com/rstudio/shiny/issues/1484#issuecomment-262812760) for this.
4 |
5 | Define a `dedupe` function as follows:
6 |
7 | ```
8 | dedupe <- function(r) {
9 | makeReactiveBinding("val")
10 | observe(val <<- r(), priority = 10)
11 | reactive(val)
12 | }
13 | ```
14 |
15 | And now you can wrap any `reactive(...)` expression with `dedupe(reactive(...))` so that it will only get invalidated if the actual value changes.
--------------------------------------------------------------------------------
/update-input/www/app-shinyjs.js:
--------------------------------------------------------------------------------
1 | // Given an element id, tell Shiny what the input type of that element is
2 | shinyjs.getInputType = function(params) {
3 | params = shinyjs.getParams(params, {
4 | id : null,
5 | shinyUpdateInputId : null
6 | });
7 | var id = params.id;
8 |
9 | // Escape characterss that have special selector meaning in jQuery
10 | id = id.replace( /(:|\.|\[|\]|,)/g, "\\$1" );
11 | var $el = $('#' + id);
12 | // find the enclosing shiny input container for the given id
13 | if (!$el.hasClass('shiny-input-container')) {
14 | $el = $el.closest('.shiny-input-container');
15 | if (!$el.length) {
16 | console.log('Could not find Shiny input element for id \"' + id + '\"');
17 | return;
18 | }
19 | }
20 | // find the input type of the element
21 | var inputType = $el.data('inputType');
22 | if (!inputType) {
23 | console.log('Could not find Shiny input type for id \"' + id + '\"');
24 | return;
25 | }
26 | // tell Shiny what input type this element is
27 | Shiny.onInputChange(params.shinyUpdateInputId, inputType);
28 | }
--------------------------------------------------------------------------------
/api-ajax/api.R:
--------------------------------------------------------------------------------
1 | # Dean Attali, July 2015
2 |
3 | # listen for when javascript makes an api call
4 | observeEvent(session$input[['api']], {
5 | # grab all the call parameters
6 | params <- session$input[['api']]
7 |
8 | # determine what R method to call
9 | method <- params[['_method']]
10 | method <- sprintf("api.%s", method)
11 |
12 | # save the request ID
13 | reqid <- params[['_reqid']]
14 |
15 | # remove the meta params from the param list
16 | params[['_method']] <- NULL
17 | params[['_reqid']] <- NULL
18 |
19 | # attempt to run the R function and return the response, along with request ID
20 | tryCatch({
21 | response <- do.call(method, as.list(list(params)))
22 | response <- as.list(response)
23 | response['_reqid'] <- reqid
24 |
25 | session$sendCustomMessage(type = "api.callback", response);
26 | },
27 | # if an error occurs, call the error callback
28 | error = function(err) {
29 | response <- list(message = err$message,
30 | `_reqid` = reqid)
31 | session$sendCustomMessage(type = "api.failureCallback", response);
32 | })
33 | })
--------------------------------------------------------------------------------
/api-ajax/app.R:
--------------------------------------------------------------------------------
1 | # Dean Attali, July 2015
2 |
3 | library(shiny)
4 |
5 | ui <- fluidPage(
6 | tags$head(
7 | includeScript("www/api.js"), # Always include this file
8 | includeScript("www/app.js") # JavaScript specific to this app
9 | ),
10 | actionButton("getRversion", "R version API call"),
11 | actionButton("errorFunction", "API call with error")
12 | )
13 |
14 | server <- function(input, output, session) {
15 |
16 | # include the API logic
17 | source("api.R", local = TRUE)$value
18 |
19 | api.getRversion <- function(params) {
20 | # don't forget you have access to all the parameters sent by javascript
21 | # inside the "params" variable
22 |
23 | # need to return a list (can be an empty list)
24 | retval <- list(
25 | success = TRUE,
26 | rversion = R.version.string
27 | )
28 | retval
29 | }
30 |
31 | api.errorExample <- function(params) {
32 | # this function will throw an error to show what happens on the javsacript
33 | # side when an error occurs
34 | stop("sample error message")
35 | }
36 | }
37 |
38 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/fb-login/api.R:
--------------------------------------------------------------------------------
1 | # Dean Attali, July 2015
2 |
3 | # listen for when javascript makes an api call
4 | observeEvent(session$input[['api']], {
5 | # grab all the call parameters
6 | params <- session$input[['api']]
7 |
8 | # determine what R method to call
9 | method <- params[['_method']]
10 | method <- sprintf("api.%s", method)
11 |
12 | # save the request ID
13 | reqid <- params[['_reqid']]
14 |
15 | # remove the meta params from the param list
16 | params[['_method']] <- NULL
17 | params[['_reqid']] <- NULL
18 |
19 | # attempt to run the R function and return the response, along with request ID
20 | tryCatch({
21 | response <- do.call(method, as.list(list(params)))
22 | response <- as.list(response)
23 | response['_reqid'] <- reqid
24 |
25 | session$sendCustomMessage(type = "api.callback", response);
26 | },
27 | # if an error occurs, call the error callback
28 | error = function(err) {
29 | response <- list(message = err$message,
30 | `_reqid` = reqid)
31 | session$sendCustomMessage(type = "api.failureCallback", response);
32 | })
33 | })
--------------------------------------------------------------------------------
/run-arbitrary-code/app.R:
--------------------------------------------------------------------------------
1 | # Dean Attali, October 2016
2 |
3 | library(shiny)
4 | library(shinyjs)
5 |
6 | ui <- fluidPage(
7 | shinyjs::useShinyjs(),
8 | textInput("expr", label = "Enter an R expression",
9 | value = "shinyjs::info('Hello!')"),
10 | actionButton("run", "Run", class = "btn-success"),
11 | shinyjs::hidden(
12 | div(
13 | id = "error",
14 | style = "color: red; font-weight: bold;",
15 | div("Oops, that resulted in an error! Try again."),
16 | div("Error: ", br(),
17 | span(id = "errorMsg", style = "margin-left: 10px;"))
18 | )
19 | )
20 | )
21 |
22 | server <- function(input, output, session) {
23 | shinyEnv <- environment()
24 |
25 | observeEvent(input$run, {
26 | shinyjs::hide("error")
27 |
28 | tryCatch(
29 | isolate(
30 | eval(parse(text = input$expr), envir = shinyEnv)
31 | ),
32 | error = function(err) {
33 | shinyjs::html("errorMsg", as.character(shiny::tags$i(err$message)))
34 | shinyjs::show(id = "error", anim = TRUE, animType = "fade")
35 | }
36 | )
37 | })
38 | }
39 |
40 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/navbar-add-text/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | # Call this function with all the regular navbarPage() parameters, plus a text parameter,
4 | # if you want to add text to the navbar
5 | navbarPageWithText <- function(..., text) {
6 | navbar <- navbarPage(...)
7 | textEl <- tags$p(class = "navbar-text", text)
8 | navbar[[3]][[1]]$children[[1]] <- htmltools::tagAppendChild(
9 | navbar[[3]][[1]]$children[[1]], textEl)
10 | navbar
11 | }
12 |
13 | # Call this function with an input (such as `textInput("text", NULL, "Search")`) if you
14 | # want to add an input to the navbar
15 | navbarPageWithInputs <- function(..., inputs) {
16 | navbar <- navbarPage(...)
17 | form <- tags$form(class = "navbar-form", inputs)
18 | navbar[[3]][[1]]$children[[1]] <- htmltools::tagAppendChild(
19 | navbar[[3]][[1]]$children[[1]], form)
20 | navbar
21 | }
22 |
23 | # When creating the UI, call our wrapper function instead of `navbarPage()`
24 | ui <- navbarPageWithText(
25 | "Test app",
26 | tabPanel("tab1", "tab 1"),
27 | tabPanel("tab2", "tab 2"),
28 | text = "User: Dean"
29 | )
30 |
31 | server <- function(input, output, session) {
32 | }
33 |
34 | shinyApp(ui = ui, server = server)
35 |
--------------------------------------------------------------------------------
/debug-value/README.md:
--------------------------------------------------------------------------------
1 | # Getting the value of an object in a running Shiny app without access to a debugger
2 |
3 | Sometimes you may need to know the value of some variable/function call in a Shiny app when you don't have easy access to debugging tools. For example, suppose you deploy your shiny app on shinyapps.io and it's running into a weird error there. You're sure that it's because one of the packages on the shinyapps.io server is not the version that you expect, but you want to make sure that your suspicion is correct. It's a bit difficult to debug on shinyapps.io (one thing you could do is try to use the log files), so what do you do?
4 |
5 | There's a quick an easy way to see any value in real-time during a Shiny app: simply print it inside a `verbatimTextOutput()`! Make sure to use `renderPrint()` rather than `renderText()` to print the contents of the output.
6 |
7 | Other common usecases: if you're having problems reading a file and you're not sure if the Shiny app is in the directory you expect it to be, you can print `getwd()` to see where it is. Or you can print `list.files()` to see a list of all the files that Shiny can see from where it is. Or you can simply print the value of some variable if you need to know what it is while debugging.
--------------------------------------------------------------------------------
/multiple-scrollspy-advanced/README.md:
--------------------------------------------------------------------------------
1 | # Multiple scrollspy on same page - advanced
2 |
3 | The Bootstrap *scrollspy* plugin does not support multiple scrollspy objects per page.
4 | This Shiny app demonstrates how to support scrollspy on multiple tabs by allowing each tab to have its own independent scrollspy control and using JavaScript to ensure only the scrollspy on the current tab is activated. Look at the code to see how this is achieved.
5 |
6 | This approach is very flexible and great to use when you want to define custom scrollspy controls on each tab. Initially I thought it'd be easy to simply "destroy" a scrollspy control and initialize a different one, but it seems like destorying scrollspy objects is not possible in Bootstrap 3 (although it sounds like in Bootstrap 4 it will be supported), so a more clever trick has to be used in order to "reset" the active scrollspy control.
7 |
8 | This method also supports having a different offset for the scrollspy on each tab by adding a `data-offset` attribute to the scrollspy HTML tag. This method also uses the jQuery scrollTo plugin to animate the scrolling for a better UX.
9 |
10 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to call custom JavaScript functions.
--------------------------------------------------------------------------------
/api-ajax/www/app.js:
--------------------------------------------------------------------------------
1 | // Dean Attali, July 2015
2 |
3 | app = function() {
4 |
5 | return {
6 |
7 | init : function() {
8 |
9 | // register an api call to get the r version when the button is clicked
10 | // (this is clearly not an ideal usecase of an ajax system since this can
11 | // be achieved with pure shiny, but it's a simple demonstration of the system)
12 | $('#getRversion').click(function() {
13 | // each api call MUST have a "_method" param (what to call in R),
14 | // while "_callback" and "_failureCallback" are optional.
15 | // you can also add any other parameters that will be used by the
16 | // function in R
17 | var params = {};
18 | params['_method'] = 'getRversion';
19 | params['_callback'] = function(response) {
20 | alert("response: " + response.rversion);
21 | };
22 | params['someOtherParam'] = 'foobar';
23 | api.call(params);
24 | });
25 |
26 | $('#errorFunction').click(function() {
27 | var params = {};
28 | params['_method'] = 'errorExample';
29 | params['_failureCallback'] = function(response) {
30 | alert("Error: " + response.message);
31 | };
32 | params['someOtherParam'] = 'foobar';
33 | api.call(params);
34 | });
35 | }
36 | }
37 | }();
38 |
39 | $(function () { app.init(); });
--------------------------------------------------------------------------------
/multiple-scrollspy-advanced/www/app-shinyjs.js:
--------------------------------------------------------------------------------
1 | // initialize common scrollspy control
2 | shinyjs.init = function() {
3 | $('body').scrollspy({ target: '.active-scrollspy' });
4 | }
5 |
6 | // update the active scrollspy control and refresh so that it will function
7 | shinyjs.updateScrollspy = function(tab) {
8 | // make all scrollspy controls inactive
9 | $('active-scrollspy').removeClass('active-scrollspy');
10 | // get the content in the current tab
11 | var $tabContent = $('#' + tab + '-content');
12 | if ($tabContent.length) {
13 | // find the scrollspy control in the current tab
14 | var $scrollspy = $tabContent.find('.potential-scrollspy');
15 | if ($scrollspy.length) {
16 | // mark the scrollspy in the current tab as active
17 | $scrollspy.addClass('active-scrollspy');
18 | // figure out the offset for this scrollspy
19 | var offset = 0;
20 | if ($scrollspy.data('offset')) {
21 | offset = $scrollspy.data('offset');
22 | }
23 | // update the scrollspy object
24 | $('body').data('bs.scrollspy').options.offset = offset;
25 | $('body').scrollspy('refresh');
26 | // unbind click events and re-bind clicks to animate scrolling
27 | $scrollspy.unbind('click.scrollto');
28 | $scrollspy.bind('click.scrollto', 'ul li a', function(event) {
29 | event.preventDefault();
30 | $.scrollTo(event.target.hash, 500, {
31 | offset: -offset
32 | });
33 | });
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/message-r-to-javascript/README.md:
--------------------------------------------------------------------------------
1 | # Send a message from R (server) to JavaScript (client)
2 |
3 | While Shiny is very powerful, there are many things that cannot be achieved in R and must be done in JavaScript. When including JavaScript code in a Shiny app, you sometimes need to send a message or a value from R to the JavaScript layer. This example how this is done.
4 |
5 | Sending a message from Shiny to Javascript involves two steps: first, in R you need to call `session$sendCustomMessage(type, message)`, where `type` is a string that specifies what message handler to call and `message` is an R object that gets sent to the message handler. Then you need to write the message handler in JavaScript using the code `Shiny.addCustomMessageHandler(type, function(message) {...});`. Whenever Shiny sends a message of type `type`, the corresponding function in JavaScript will get called with the `message` as its argument. While this may sound complicated, looking at the example code shows how simple this is. Note that for this to work, the shiny server function must be defined with the `session` parameter, ie. `server <- function(input, output, session) {...}`.
6 |
7 | To send a message in the other direction (from JavaScript to R), see [Send a message from JavaScript to R](../message-javascript-to-r).
8 |
9 | Another approach for calling JavaScript functions from Shiny which attempts to make the process easier is using the `extendShinyjs()` function from the [shinyjs](https://github.com/daattali/shinyjs) package.
10 |
11 | [See a real shiny app where I used this concept](http://daattali.com/shiny/cfl/)
--------------------------------------------------------------------------------
/api-ajax/README.md:
--------------------------------------------------------------------------------
1 | # Simple AJAX system for Shiny apps (JS -> R -> JS communication)
2 |
3 | Sometimes it's useful to be able to call an R function from JavaScript and use the return value from R back in JavaScript. This sort of communication is usually done with AJAX in JavaScript. This app shows how to implement a simple and ultra lightweight AJAX-like system in Shiny, to be able to call functions in R.
4 |
5 | Using this system is easy:
6 | 1. Include `api.js` in your Shiny app's UI
7 | 2. Include `api.R` in your Shiny app's server
8 | 3. In your R code: define the functions that you want to be able to call from JavaScript. Each function must have a name beginning with `api.` and take a single argument which is a list of the parameters passed from JavaScript. This function should return a list (can be an empty list), which will be passed to the callback function in JavaScript (if a callback was provided).
9 | 4. In your JavaScript code: make a call to `api.call(params)`, where `params` is an object that contains all the parameters as key-value pairs. You must specify the `_method` parameter -- this is the R function that will get called. For example, if in R you defined a function `api.myfunc` then you can call it in JavaScript with `api.call('_method' : 'myfunc')` (notice that the prefix `api.` is ommitted). You can optionally provide `_callback` and `_failureCallback` functions which can be called when the R function completes or when it throws an error. You can also add any other arbitrary parameters, and they will be available to you in the R function.
10 |
11 | This may sound a bit complicated, but if you take a look at the source code of the sample app you'll see it's actually pretty simple to use.
--------------------------------------------------------------------------------
/forked-task/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | source("create_forked_task.R")
4 |
5 | ui <- fluidPage(
6 | shinyjs::useShinyjs(), # Initialize shinyjs library
7 |
8 | # Buttons to control job
9 | actionButton("start", "Start"),
10 | shinyjs::disabled(actionButton("stop", "Stop")),
11 |
12 | # This will display the job output
13 | tableOutput("out")
14 | )
15 |
16 | server <- function(input, output, session) {
17 | # Make "task" behave like a reactive value
18 | makeReactiveBinding("task")
19 | task <- NULL
20 |
21 | output$out <- renderTable({
22 | # The task starts out NULL but is required. The req() takes
23 | # care of ensuring that we only proceed if it's non-NULL.
24 | req(task)$result()
25 | })
26 |
27 | observeEvent(input$start, {
28 | shinyjs::enable("stop")
29 | shinyjs::disable("start")
30 |
31 | task <<- create_forked_task({
32 | # Pretend this takes a long time
33 | Sys.sleep(5)
34 | cars[sample(nrow(cars), 10),]
35 | })
36 |
37 | # Show progress message during task start
38 | prog <- Progress$new(session)
39 | prog$set(message = "Executing task, please wait...")
40 |
41 | o <- observe({
42 | # Only proceed when the task is completed (this could mean success,
43 | # failure, or cancellation)
44 | req(task$completed())
45 |
46 | # This observer only runs once
47 | o$destroy()
48 |
49 | # Close the progress indicator and update button state
50 | prog$close()
51 | shinyjs::disable("stop")
52 | shinyjs::enable("start")
53 | })
54 | })
55 |
56 | observeEvent(input$stop, {
57 | task$cancel()
58 | })
59 | }
60 |
61 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/message-javascript-to-r/README.md:
--------------------------------------------------------------------------------
1 | # Send a message from JavaScript (client) to R (server)
2 |
3 | In some shiny applications you may want to send a value from JavaScript to the R server. This can be useful in a variety of applications, for example if you want to capture a mouse click or a keyboard press of the user and tell R about it. This example shows how this is done.
4 |
5 | The way this kind of communication is supported in Shiny is by using inputs. Usually when you think of a Shiny input you think of an input control (such as a `textInput` or a `sliderInput`) that gets its value updated when the user interacts with it, and you can access its value in Shiny inside a reactive context such as an `observe()` or a `reactive()` statement using the `input` list. But in order to send a value from JS to R, we can assign a value to an arbitrary input name from JavaScript, and then in Shiny we can use that input inside any reactive contexts as if it was a regular "native" input.
6 |
7 | More specifically, in order to send a value from JavaScript to Shiny, there are two steps: first, in your JavaScript code you need to call `Shiny.onInputChange(name, value)` where `name` is the input name that will be used in Shiny and `value` is any JavaScript object that will be passed to Shiny. Then in your Shiny app you can use use `input$name` (replace `name` with the actual name you used) and it will hold the updated value that was assigned to it in JavaScript. While this may sound complicated, looking at the example code shows how simple this is.
8 |
9 | To send a message in the other direction (from R to JavaScript), see [Send a message from R to JavaScript](../message-r-to-javascript).
10 |
11 | [See a real shiny app where I used this concept](http://daattali.com/shiny/cfl/)
--------------------------------------------------------------------------------
/update-input/update-input.R:
--------------------------------------------------------------------------------
1 | # Create a function that wraps a Shiny input function in code that adds information
2 | # about the tag type
3 | updateableInput <- function(inputType) {
4 | function(...) {
5 | shinyFuncName <- as.character(as.list(match.call()[1]))
6 | shinyFunc <- get(shinyFuncName, envir = as.environment("package:shiny"))
7 | shiny::tagAppendAttributes(
8 | shinyFunc(...),
9 | `data-input-type` = inputType
10 | )
11 | }
12 | }
13 |
14 | # define what Shiny inputs you want to support
15 | # (the following three common input types are tested; the code here probably will
16 | # not work as-is for ALL inputs but you should be able to modify it slightly for
17 | # other inputs)
18 | textInput <- updateableInput("Text")
19 | numericInput <- updateableInput("Numeric")
20 | selectInput <- updateableInput("Select")
21 |
22 | # Update a single Shiny input without specifying its type
23 | updateShinyInput <- function(session, id, value) {
24 | shinyUpdateInputId <- paste0("shiny-update-input-", id)
25 | js$getInputType(id, shinyUpdateInputId)
26 | shiny::observeEvent(session$input[[shinyUpdateInputId]], {
27 | inputType <- session$input[[shinyUpdateInputId]]
28 | updateFunc <- sprintf("update%sInput", inputType)
29 | funcParams <- list(session = session, inputId = id)
30 |
31 | if (inputType == "Select") {
32 | funcParams[['selected']] <- value
33 | } else {
34 | funcParams[['value']] <- value
35 | }
36 |
37 | do.call(updateFunc, funcParams)
38 | })
39 | }
40 |
41 | # Update multiple Shiny inputs simultaneously
42 | updateShinyInputs <- function(session, updates) {
43 | lapply(names(updates), function(id) {
44 | updateShinyInput(session, id, updates[[id]])
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/javascript-to-r-handler/README.md:
--------------------------------------------------------------------------------
1 | # Use a custom function to convert the JavaScript data into an R object
2 |
3 | When using `Shiny.onInputChange(name, data)` (as described [here](../message-javascript-to-r)), you are passing in a JavaScript object (`data`) and expect it to get converted to an R object (`input$name`). This conversion happens by serializing and deserializing the data to and from JSON. Usually `input$name` will look exactly like you'd expect it to, but it is possible for the conversion process to not do exactly what you want. Alternatively, you may just want to alter the data slightly in R before presenting it to Shiny.
4 |
5 | This is where the `shiny::registerInputHandler()` function comes in: it allows you to transform the data passed in from JavaScript before it gets used as `input$`. For example, suppose you use `Shiny.onInputChange("myobj", value)` to send a value from JavaScript to R, but you want `input$myobj` to be automatically converted into a list. There are two simple steps you'd need to follow:
6 |
7 | 1. In JavaScript, change `Shiny.onInputChange("myobj", value)` to `Shiny.onInputChange("myobj:mylist", value)`. Notice that we append `:` to the name of the object. This specifices the type of object that is being passed to Shiny, so that Shiny will know what handler to call when deserialization its value.
8 |
9 | 2. In R, define the following function:
10 |
11 | ```
12 | shiny::registerInputHandler("mylist", function(data, ...) {
13 | list(data)
14 | }, force = TRUE)
15 | ```
16 |
17 | Now if you access `input$myobj` in shiny, the value will be wrapped in a list. Of course this particular example isn't terribly useful, but this principle can be applied in real apps (for example, you can see how I've used it in [`timevis`](https://github.com/daattali/timevis/blob/v0.2/R/utils.R#L2-L7)).
--------------------------------------------------------------------------------
/multiple-scrollspy-basic/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | ui <- navbarPage(
5 | "Bootstrap scrollspy on multiple tabs",
6 | id = "navbar",
7 |
8 | header = div(
9 | useShinyjs(),
10 | extendShinyjs("www/app-shinyjs.js", functions = c("updateScrollspy")),
11 | includeCSS("www/app.css"),
12 |
13 | # create a common scrollspy
14 | div(
15 | id = "myscrollspy",
16 | tags$ul(
17 | class = "nav nav-pills nav-stacked"
18 | )
19 | )
20 | ),
21 |
22 | # tab 1 has 4 sections
23 | tabPanel(
24 | "tab1",
25 | div(id = "tab1-content",
26 | div(id = "section1-1",
27 | class = "scrollspy-section",
28 | p('Section 1-1')
29 | ),
30 | div(id = "section1-2",
31 | class = "scrollspy-section",
32 | p('Section 1-2')
33 | ),
34 | div(id = "section1-3",
35 | class = "scrollspy-section",
36 | p('Section 1-3')
37 | ),
38 | div(id = "section1-4",
39 | class = "scrollspy-section",
40 | p('Section 1-4')
41 | )
42 | )
43 | ),
44 |
45 | # tab 2 has 3 sections
46 | tabPanel(
47 | "tab2",
48 | div(id = "tab2-content",
49 | div(id = "section2-1",
50 | class = "scrollspy-section",
51 | p('Section 2-1')
52 | ),
53 | div(id = "section2-2",
54 | class = "scrollspy-section",
55 | p('Section 2-2')
56 | ),
57 | div(id = "section2-3",
58 | class = "scrollspy-section",
59 | p('Section 2-3')
60 | )
61 | )
62 | )
63 | )
64 |
65 | server <- function(input, output, session) {
66 | # when changing tabs, update the scrollspy control
67 | observeEvent(input$navbar, {
68 | js$updateScrollspy(input$navbar)
69 | })
70 | }
71 |
72 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/fb-login/README.md:
--------------------------------------------------------------------------------
1 | # Facebook login through JavaScript in Shiny
2 |
3 | This app shows how you can use the [AJAX-like system](../api-ajax) in Shiny to authorize a user using Facebook's JavaScript library and pass the user's information to R for processing. You'll need to set up your app with proper settings on Facebook's Developer Dashboard (most importantly the app URL), and add your Facebook App ID as the `appId` parameter in the [app.js](./www/app.js) file (in [this line](https://github.com/daattali/advanced-shiny/blob/6a9c78a2d437f40276842ec76934ac0b890b9d52/fb-login/www/app.js#L20)).
4 |
5 | Here is the series of steps to get your app working locally as of September 2016:
6 |
7 | - Go to [https://developers.facebook.com/apps](https://developers.facebook.com/apps)
8 | - Click on *+ Add a New App* and fill in the required info
9 | - When you get to the new app's Settings page, click on *+ Add Product*
10 | - Select *Facebook Login*
11 | - In the field for *Valid OAuth redirect URIs*, type `http://localhost:5000`
12 | - Click *Save Changes*
13 | - When running your shiny app:
14 | - You'll need to run your Shiny app using port 5000 because that's the port we used in the Facebook settings (eg. `runApp('fb-login', port = 5000)`)
15 | - You'll need to replace the `127.0.0.1` in the URL with `localhost` (Facebook doesn't work with 127.0.0.1)
16 |
17 | When deploying an app on a shiny server or on shinyapps.io, you'll need to adjust the app settings acordingly.
18 |
19 | Unfortunately these steps are not guaranteed to work forever - Facebook is known to change their API often, as well as the Developer Dashboard and all the available settings. I will not be maintaining this code to work with the ever-changing Facebook API, so if you're having problems setting up your app with the correct settings, just know that there are tons of other people also struggling with it, and use Google/Stackoverflow to try to figure it out :)
20 |
--------------------------------------------------------------------------------
/busy-indicator/README.md:
--------------------------------------------------------------------------------
1 | # "Busy..." / "Done!" / "Error" feedback after pressing a button
2 |
3 | When the user clicks on a button, it usually results in some R code being run. Sometimes the resulting code is short and quick and the user doesn't even notice any delay, but sometimes the button click initialiates some long process or computation that can take more than 1 second to complete. In those cases, it might be a bit confusing to the user if there is no immediate feedback notifying that the action is being performed. For example, if the user clicked a button to load data from a database and it takes 3 seconds to connect to the database, it can be useful to show a "Connecting..." and then a "Done!" (or "Error") message, instead of just letting the user wait without seeing any message. Of course when the wait time is only 2-3 seconds it's not a big deal, but you can imagine that for a 20-second process, the user might think that something went wrong if there is no feedback.
4 |
5 | This example shows how to add some immediate feedback to the user after a button is clicked: the button gets disabled, a "loading" icon appears, when the action completes the button gets re-enabled and a checkmark icon appears shortly. If an error occurs, an error icon and message appear. The code here is a little bit more involved than the other examples, but it is not complicated if you go through it line by line. I made the code generalize easily and all the code you need to copy to your app is inside the [helpers.R](./helpers.R) file. Look at the [app.R](./app.R) file to see how to use it in an app - it's very easy.
6 |
7 | Note that I wrote this code before Shiny modules existed, but it can probably be re-written to work as a module. This exercise is left to the reader if you feel so inclined :)
8 |
9 | [See a real shiny app where I used this concept](http://daattali.com/shiny/ddpcr/)
10 |
11 | ---
12 |
13 | [](./busy-indicator.gif)
--------------------------------------------------------------------------------
/api-ajax/www/api.js:
--------------------------------------------------------------------------------
1 | // Dean Attali, July 2015
2 |
3 | api = function() {
4 |
5 | // create a unique id
6 | function guid() {
7 | function s4() {
8 | return Math.floor((1 + Math.random()) * 0x10000)
9 | .toString(16)
10 | .substring(1);
11 | }
12 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
13 | s4() + '-' + s4() + s4() + s4();
14 | }
15 |
16 | // keep track of all the calls currently in progress
17 | calls = {};
18 |
19 | return {
20 |
21 | // this is the main function to use: call "api.call(params)" to make
22 | // an AJAX call
23 | call : function(params) {
24 | var reqid = guid();
25 | calls[reqid] = params;
26 | params['_reqid'] = reqid;
27 | Shiny.onInputChange('api', params);
28 | },
29 |
30 | // callback from R when a call is done
31 | callback : function(response) {
32 | // grab the request id and get the original parameters of this call
33 | var reqid = response._reqid;
34 | delete response['_reqid'];
35 | var call = calls[reqid];
36 |
37 | // remove this call from the list of calls in progress
38 | delete calls[reqid];
39 |
40 | // if the user specified a callback, call it
41 | var callback = call._callback;
42 | if (callback) {
43 | callback(response);
44 | }
45 | },
46 |
47 | // callback from R when an error occurs during an api call
48 | failureCallback : function(response) {
49 | console.log("API error!");
50 | console.log(response);
51 |
52 | var reqid = response._reqid;
53 | delete response['_reqid'];
54 | var call = calls[reqid];
55 | delete calls[reqid];
56 | var callback = call._failureCallback;
57 | if (callback) {
58 | callback(response);
59 | }
60 | }
61 | };
62 | }();
63 |
64 | $(function () {
65 | Shiny.addCustomMessageHandler('api.callback', api.callback);
66 | Shiny.addCustomMessageHandler('api.failureCallback', api.failureCallback);
67 | });
--------------------------------------------------------------------------------
/fb-login/www/api.js:
--------------------------------------------------------------------------------
1 | // Dean Attali, July 2015
2 |
3 | api = function() {
4 |
5 | // create a unique id
6 | function guid() {
7 | function s4() {
8 | return Math.floor((1 + Math.random()) * 0x10000)
9 | .toString(16)
10 | .substring(1);
11 | }
12 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
13 | s4() + '-' + s4() + s4() + s4();
14 | }
15 |
16 | // keep track of all the calls currently in progress
17 | calls = {};
18 |
19 | return {
20 |
21 | // this is the main function to use: call "api.call(params)" to make
22 | // an AJAX call
23 | call : function(params) {
24 | var reqid = guid();
25 | calls[reqid] = params;
26 | params['_reqid'] = reqid;
27 | Shiny.onInputChange('api', params);
28 | },
29 |
30 | // callback from R when a call is done
31 | callback : function(response) {
32 | // grab the request id and get the original parameters of this call
33 | var reqid = response._reqid;
34 | delete response['_reqid'];
35 | var call = calls[reqid];
36 |
37 | // remove this call from the list of calls in progress
38 | delete calls[reqid];
39 |
40 | // if the user specified a callback, call it
41 | var callback = call._callback;
42 | if (callback) {
43 | callback(response);
44 | }
45 | },
46 |
47 | // callback from R when an error occurs during an api call
48 | failureCallback : function(response) {
49 | console.log("API error!");
50 | console.log(response);
51 |
52 | var reqid = response._reqid;
53 | delete response['_reqid'];
54 | var call = calls[reqid];
55 | delete calls[reqid];
56 | var callback = call._failureCallback;
57 | if (callback) {
58 | callback(response);
59 | }
60 | }
61 | };
62 | }();
63 |
64 | $(function () {
65 | Shiny.addCustomMessageHandler('api.callback', api.callback);
66 | Shiny.addCustomMessageHandler('api.failureCallback', api.failureCallback);
67 | });
--------------------------------------------------------------------------------
/navigate-history/README.md:
--------------------------------------------------------------------------------
1 | # Navigation in a Shiny app (forward/backwards in history)
2 |
3 | Sometimes it's nice to be able to support navigation within a Shiny app, especially when there are multiple tabs or some other form of "multiple pages" in a Shiny app. Since Shiny apps are a single page, the browser nagivation buttons (previous/next page) don't work when "navigating" within a Shiny app. You also can't bookmark a certain "page" in a Shiny app - every time you go to an app, you will be shown the initial state of the app.
4 |
5 | This app shows a basic demonstration of how to implement a way to navigate a Shiny app by manually changing the URL when different tabs are loaded, and using the current URL to decide what to show when a new page is loaded. In this app, every time you click on a new tab, the browser will behave as if you navigated to a new page, and you will be able to go back and forward using the browser navigation buttons. Another nice feature is that after navigating to a certain tab, you can copy the URL or bookmark it, and when you open that page again, the Shiny app will restore the state of the app.
6 |
7 | For example, try this sequence of events to see what this app can do:
8 | - load the app
9 | - type some text into the search box in the Home tab
10 | - click on "Search" (you will be redirected to the Search tab)
11 | - click on the "About" tab
12 | - click the "back" button in your browser navigation
13 | - copy the URL
14 | - click the "next" button in your browser navigation
15 | - in a different window, go to the URL that you copied earlier (it will automatically navigate to the search page and show the search term)
16 |
17 | The code is a bit complex and might take some time to understand how it works. This is certainly not an ideal solution, I'd love to know if someone has a nicer method for navigating a Shiny app. It's possible that the core Shiny team will work on this and that soon this functionality will be native to shiny.
18 |
19 | This example makes use of the [shinyjs](https://github.com/daattali/shinyjs) package to call custom JavaScript functions.
20 |
21 | ---
22 |
23 | [](./navigate-history.gif)
--------------------------------------------------------------------------------
/fb-login/www/app.js:
--------------------------------------------------------------------------------
1 | // Dean Attali, July 2015
2 |
3 | facebook = function() {
4 |
5 | return {
6 |
7 | init : function() {
8 |
9 | // initialize facebook JDK
10 | (function(d, s, id) {
11 | var js, fjs = d.getElementsByTagName(s)[0];
12 | if (d.getElementById(id)) {return;}
13 | js = d.createElement(s);
14 | js.id = id;
15 | js.src = "//connect.facebook.net/en_US/sdk.js";
16 | fjs.parentNode.insertBefore(js, fjs);
17 | }(document, 'script', 'facebook-jssdk'));
18 | window.fbAsyncInit = function() {
19 | FB.init({
20 | appId: '587632811407957', // YOUR APP ID GOES HERE
21 | cookie: true,
22 | version: 'v2.7'
23 | });
24 | FB.getLoginStatus(function(response) {
25 | console.log(response);
26 | facebook.statusChangeCallback(response);
27 | });
28 | };
29 |
30 | // register click handler to login
31 | $('#fbLoginBtn').click(function() {
32 | facebook.fbLogin(function(response) {
33 | var params = {};
34 | params['_method'] = 'fblogin';
35 | params['_callback'] = function(r){console.log(r)};
36 | params['user_id'] = response.authResponse.userID;
37 | params['access_token'] = response.authResponse.accessToken;
38 | api.call(params);
39 | });
40 | });
41 | },
42 |
43 | fbLogin : function(cb) {
44 | FB.login(function(response) {
45 | facebook.statusChangeCallback(response, cb);
46 | });
47 | },
48 |
49 | // This is called with the results from from FB.getLoginStatus().
50 | statusChangeCallback : function(response, cb) {
51 | console.log(response);
52 | if (response.status === 'connected') {
53 | FB.api('/me', function(response) {
54 | $("#fbStatus").html('Hi ' + response.name);
55 | });
56 | if (typeof cb !== "undefined") {
57 | cb(response);
58 | }
59 | } else if (response.status === 'not_authorized') {
60 | $("#fbStatus").html('Please authorize the app.');
61 | } else {
62 | $("#fbStatus").html('Please log into Facebook.');
63 | }
64 | },
65 | }
66 | }();
67 |
68 | $(function () { facebook.init(); });
69 |
--------------------------------------------------------------------------------
/message-javascript-to-r-force/README.md:
--------------------------------------------------------------------------------
1 | # Send a message from JavaScript (client) to R (server) - force repetitive messages to get sent
2 |
3 | Assuming you already know how to [send messages from JavaScript to Shiny](../message-javascript-to-r), you might notice that when you send a message with the exact same value multiple times in a row, only the first time actually gets sent to Shiny. For example, suppose you want to listen to every key that is pressed on the keyboard (in JavaScript) and send the value of every key press to R. If the user presses the keys *a*,*b*,*b*,*c*,*c*,*b* in that order, then Shiny will only receive *a*,*b*,*c*,*b* (notice that any repeating value was not received). This can often be problematic and might be resolved in Shiny in the future (you can follow the bug report [here](https://github.com/rstudio/shiny/issues/928)).
4 |
5 | Why is this happening? To understand the problem, it helps to remember that inputs are reactive values, and reactive values only trigger their dependencies when their value changes. This generally makes sense in Shiny: if you have some code that depends on an input, then you only want the code to re-run whenever the input's value changes. If the value of the input didn't change, then Shiny doesn't get notified. This idea works great for Shiny inputs, but when you want to send custom messages to Shiny from JavaScript, sometimes you might want to notify Shiny that an event occurred even if it's an identical event to the previous one.
6 |
7 | If you want to ensure that every message gets sent to Shiny, even if the value you want to send is the same as before, then a simple solution can be to simply include a random value in the message (along with the real message). By adding some random component to the message, it means that every call will be different, and it will always trigger an update in Shiny. Simply change `Shiny.onInputChange(name, value)` to `Shiny.onInputChange(name, [value, Math.random()])` and in R instead of listening to `input$name` you need to use `input$name[1]`. Essentially what we're doing is pass a 2-element vector, where the first element is the actual value we're interested in and the second element is just some random number.
8 |
9 | To send a message in the other direction (from R to JavaScript), see [Send a message from R to JavaScript](../message-r-to-javascript).
10 |
11 | [See a real shiny app where I used this concept](http://daattali.com/shiny/cfl/)
--------------------------------------------------------------------------------
/forked-task/create_forked_task.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | # Also uses parallel, shinyjs, tools
3 |
4 | # Create a long-running task, executed in a forked process. (Doesn't work on Windows)
5 | #
6 | # The return value is a promise-like object with three
7 | # methods:
8 | # - completed(): FALSE initially, then TRUE if the task succeeds,
9 | # fails, or is cancelled. Reactive, so when the state changes
10 | # any reactive readers will invalidate.
11 | # - result(): Use this to get the return value. While execution is
12 | # in progress, performs a req(FALSE). If task succeeded, returns
13 | # the return value. If failed, throws error. Reactive, so when
14 | # the state changes any reactive readers will invalidate.
15 | # - cancel(): Call this to prematurely terminate the task.
16 | create_forked_task <- function(expr) {
17 | makeReactiveBinding("state")
18 | state <- factor("running",
19 | levels = c("running", "success", "error", "cancel"),
20 | ordered = TRUE
21 | )
22 |
23 | result <- NULL
24 |
25 | # Launch the task in a forked process. This always returns
26 | # immediately, and we get back a handle we can use to monitor
27 | # or kill the job.
28 | task_handle <- parallel::mcparallel({
29 | force(expr)
30 | })
31 |
32 | # Poll every 100 milliseconds until the job completes
33 | o <- observe({
34 | res <- parallel::mccollect(task_handle, wait = FALSE)
35 | if (is.null(res)) {
36 | invalidateLater(100)
37 | } else {
38 | o$destroy()
39 | if (!is.list(res) || length(res) != 1 || !inherits(res[[1]], "try-error")) {
40 | state <<- "success"
41 | result <<- res[[1]]
42 | } else {
43 | state <<- "error"
44 | result <<- attr(res[[1]], "condition", exact = TRUE)
45 | }
46 | }
47 | })
48 |
49 | list(
50 | completed = function() {
51 | state != "running"
52 | },
53 | result = function() {
54 | if (state == "running") {
55 | # If running, abort the current context silently.
56 | # We've taken a reactive dependency on "state" so if
57 | # the state changes the context will invalidate.
58 | req(FALSE)
59 | } else if (state == "success") {
60 | return(result)
61 | } else if (state == "error") {
62 | stop(result)
63 | } else if (state == "cancel") {
64 | validate(need(FALSE, "The operation was cancelled"))
65 | }
66 | },
67 | cancel = function() {
68 | if (state == "running") {
69 | state <<- "cancel"
70 | o$destroy()
71 | tools::pskill(task_handle$pid, tools::SIGTERM)
72 | tools::pskill(-task_handle$pid, tools::SIGTERM)
73 | parallel::mccollect(task_handle, wait = FALSE)
74 | }
75 | }
76 | )
77 | }
--------------------------------------------------------------------------------
/busy-indicator/helpers.R:
--------------------------------------------------------------------------------
1 | # All the code in this file needs to be copied to your Shiny app, and you need
2 | # to call `withBusyIndicatorUI()` and `withBusyIndicatorServer()` in your app.
3 | # You can also include the `appCSS` in your UI, as the example app shows.
4 |
5 | # =============================================
6 |
7 | # Set up a button to have an animated loading indicator and a checkmark
8 | # for better user experience
9 | # Need to use with the corresponding `withBusyIndicator` server function
10 | withBusyIndicatorUI <- function(button) {
11 | id <- button[['attribs']][['id']]
12 | div(
13 | `data-for-btn` = id,
14 | button,
15 | span(
16 | class = "btn-loading-container",
17 | hidden(
18 | img(src = "ajax-loader-bar.gif", class = "btn-loading-indicator"),
19 | icon("check", class = "btn-done-indicator")
20 | )
21 | ),
22 | hidden(
23 | div(class = "btn-err",
24 | div(icon("exclamation-circle"),
25 | tags$b("Error: "),
26 | span(class = "btn-err-msg")
27 | )
28 | )
29 | )
30 | )
31 | }
32 |
33 | # Call this function from the server with the button id that is clicked and the
34 | # expression to run when the button is clicked
35 | withBusyIndicatorServer <- function(buttonId, expr) {
36 | # UX stuff: show the "busy" message, hide the other messages, disable the button
37 | loadingEl <- sprintf("[data-for-btn=%s] .btn-loading-indicator", buttonId)
38 | doneEl <- sprintf("[data-for-btn=%s] .btn-done-indicator", buttonId)
39 | errEl <- sprintf("[data-for-btn=%s] .btn-err", buttonId)
40 | shinyjs::disable(buttonId)
41 | shinyjs::show(selector = loadingEl)
42 | shinyjs::hide(selector = doneEl)
43 | shinyjs::hide(selector = errEl)
44 | on.exit({
45 | shinyjs::enable(buttonId)
46 | shinyjs::hide(selector = loadingEl)
47 | })
48 |
49 | # Try to run the code when the button is clicked and show an error message if
50 | # an error occurs or a success message if it completes
51 | tryCatch({
52 | value <- expr
53 | shinyjs::show(selector = doneEl)
54 | shinyjs::delay(2000, shinyjs::hide(selector = doneEl, anim = TRUE, animType = "fade",
55 | time = 0.5))
56 | value
57 | }, error = function(err) { errorFunc(err, buttonId) })
58 | }
59 |
60 | # When an error happens after a button click, show the error
61 | errorFunc <- function(err, buttonId) {
62 | errEl <- sprintf("[data-for-btn=%s] .btn-err", buttonId)
63 | errElMsg <- sprintf("[data-for-btn=%s] .btn-err-msg", buttonId)
64 | errMessage <- gsub("^ddpcr: (.*)", "\\1", err$message)
65 | shinyjs::html(html = errMessage, selector = errElMsg)
66 | shinyjs::show(selector = errEl, anim = TRUE, animType = "fade")
67 | }
68 |
69 | appCSS <- "
70 | .btn-loading-container {
71 | margin-left: 10px;
72 | font-size: 1.2em;
73 | }
74 | .btn-done-indicator {
75 | color: green;
76 | }
77 | .btn-err {
78 | margin-top: 10px;
79 | color: red;
80 | }
81 | "
82 |
--------------------------------------------------------------------------------
/serve-images-files/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | jscode <- "
5 | // Define a JavaScript function to navigate the page to where the file is served
6 | shinyjs.navigate = function(url) {
7 | window.location.href = window.location.origin + window.location.pathname + url;
8 | }
9 | "
10 |
11 | ui <- fluidPage(
12 | useShinyjs(),
13 | extendShinyjs(text = jscode, functions = c("navigate")),
14 |
15 | # Portion of the app that lets the user cutomize a plot and then see
16 | # a URL to serve that plot
17 | hidden(div(id = "app",
18 | h3("Serve an image of a plot from 1 to n"),
19 | sliderInput("num", "n", 1, 50, 10),
20 | selectInput("col", "Colour",
21 | choices = c("black", "brown", "red", "blue", "green")),
22 | uiOutput("link")
23 | )),
24 |
25 | # Portion of the app shown when a user is on a page that serves a file
26 | hidden(div(id = "creating", h3("Creating the file...")))
27 | )
28 |
29 | server <- function(input, output, session) {
30 |
31 | # Show the user a friendly URL for accessing the plot
32 | output$link <- renderUI({
33 | queryBuild <- sprintf("?num=%s&col=%s", input$num, input$col)
34 | externalUrl <- sprintf(
35 | "%s%s%s",
36 | session$clientData$url_hostname,
37 | session$clientData$url_pathname,
38 | queryBuild
39 | )
40 |
41 | tags$a(externalUrl, href = queryBuild, target = "_blank")
42 | })
43 |
44 | # Logic for associating the URL with the plot
45 | observe({
46 | query <- parseQueryString(session$clientData$url_search)
47 |
48 | # if we see a 'num' GET param, it means this is a request for a plot image
49 | if (!is.null(query$num)) {
50 |
51 | show("creating")
52 |
53 | # create a URL endpoint for the image
54 | myurl <- session$registerDataObj(
55 | name = 'getplot',
56 | data = query,
57 | filter = function(data, req) {
58 |
59 | # create the image (or it could be a text file or any other file)
60 | # the file needs to be saved so that it's possible to read it
61 | image <- tempfile()
62 | col <- data$col
63 | tryCatch({
64 | png(image, width = 600, height = 600)
65 | plot(seq(1, as.numeric(data$num)), main = date(), col = col)
66 | }, finally = dev.off())
67 |
68 | # submit an OK response, sending the file (image) as the content
69 | # if you're serving a different type of file, remember to change
70 | # the "image/png" to the correct MIME type
71 | shiny:::httpResponse(
72 | 200, 'image/png', readBin(image, 'raw', file.info(image)[, 'size'])
73 | )
74 | }
75 | )
76 |
77 | # After creating the requested file and associating it with a URL, go
78 | # to that webpage using JavaScript
79 | js$navigate(myurl)
80 | } else {
81 | # if this is not a request for a file, show the Shiny app
82 | show("app")
83 | }
84 | })
85 | }
86 |
87 | shinyApp(ui, server)
--------------------------------------------------------------------------------
/multiple-scrollspy-advanced/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | ui <- navbarPage(
5 | "Bootstrap scrollspy on multiple tabs",
6 | id = "navbar",
7 | header = div(
8 | useShinyjs(),
9 | extendShinyjs("www/app-shinyjs.js", functions = c("updateScrollspy")),
10 | includeCSS("www/app.css"),
11 | includeScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-scrollTo/1.4.3/jquery.scrollTo.min.js")
12 | ),
13 |
14 | # tab 1 contains 4 sections and a scrollspy on the left with text
15 | tabPanel(
16 | "tab1",
17 | div(id = "tab1-content",
18 | fluidRow(
19 | column(
20 | 4,
21 | div(
22 | id = "tab1-scrollspy",
23 | class = "potential-scrollspy",
24 | tags$ul(
25 | class = "nav nav-pills nav-stacked",
26 | tags$li(tags$a(href = "#section1-1", "Section 1-1")),
27 | tags$li(tags$a(href = "#section1-2", "Section 1-2")),
28 | tags$li(tags$a(href = "#section1-3", "Section 1-3")),
29 | tags$li(tags$a(href = "#section1-4", "Section 1-4"))
30 | )
31 | )
32 | ),
33 | column(
34 | 8,
35 | div(id = "section1-1",
36 | class = "scrollspy-section",
37 | p('Section 1-1')
38 | ),
39 | div(id = "section1-2",
40 | class = "scrollspy-section",
41 | p('Section 1-2')
42 | ),
43 | div(id = "section1-3",
44 | class = "scrollspy-section",
45 | p('Section 1-3')
46 | ),
47 | div(id = "section1-4",
48 | class = "scrollspy-section",
49 | p('Section 1-4')
50 | )
51 | )
52 | )
53 | )
54 | ),
55 |
56 | # tab 2 contains 3 sections and a scrollspy on the right without text
57 | tabPanel(
58 | "tab2",
59 | div(id = "tab2-content",
60 | fluidRow(
61 | column(
62 | 8,
63 | div(id = "section2-1",
64 | class = "scrollspy-section",
65 | p('Section 2-1')
66 | ),
67 | div(id = "section2-2",
68 | class = "scrollspy-section",
69 | p('Section 2-2')
70 | ),
71 | div(id = "section2-3",
72 | class = "scrollspy-section",
73 | p('Section 2-3')
74 | )
75 | ),
76 | column(
77 | 4,
78 | div(
79 | id = "tab2-scrollspy",
80 | class = "potential-scrollspy",
81 | `data-offset` = 50,
82 | tags$ul(
83 | class = "nav nav-pills nav-stacked",
84 | tags$li(tags$a(href = "#section2-1")),
85 | tags$li(tags$a(href = "#section2-2")),
86 | tags$li(tags$a(href = "#section2-3"))
87 | )
88 | )
89 | )
90 | )
91 | )
92 | )
93 | )
94 |
95 | server <- function(input, output, session) {
96 | # when changing tabs, update the scrollspy control
97 | observeEvent(input$navbar, {
98 | js$updateScrollspy(input$navbar)
99 | })
100 | }
101 |
102 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/navigate-history/app.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(shinyjs)
3 |
4 | ui <- navbarPage(
5 | "Navigating in Shiny app",
6 | id = "navbar",
7 | header = tagList(
8 | useShinyjs(),
9 | extendShinyjs("www/app-shinyjs.js", functions = c("updateHistory"))
10 | ),
11 |
12 | tabPanel("Home", value = "home",
13 | h2("Home tab"),
14 | textInput("text", "Enter string to search", "foo"),
15 | actionButton("go", "Search")
16 | ),
17 | tabPanel("Search", value = "search",
18 | h2("Search results:",
19 | textOutput("searchString", inline = TRUE)
20 | )
21 | ),
22 | tabPanel("About", value = "about",
23 | "Basic demo of supporting navigation in a Shiny app by",
24 | tags$a("Dean Attali", href = "http://deanattali.com")
25 | )
26 | )
27 |
28 | server <- function(input, output, session) {
29 |
30 | values <- reactiveValues(
31 |
32 | # variable to keep track of whether or not the tab switching is manual (by the
33 | # user) or automatic (restoring the app's state on initialization or prev/next buttons)
34 | autoNavigating = 0,
35 |
36 | # search string
37 | searchString = ""
38 | )
39 |
40 | # show the search string
41 | output$searchString <- renderText({
42 | values$searchString
43 | })
44 |
45 | # go to the search tab when clicking the button
46 | observeEvent(input$go, {
47 | values$searchString <- input$text
48 | updateTabsetPanel(session, "navbar", "search")
49 | })
50 |
51 | # ----- navigation logic -----
52 |
53 | # when the app initializes, if there is a history in the URL, navigate to it
54 | observeEvent(session$clientData$url_search, {
55 | # if there is a history in the URL, restore the state
56 | if (nchar(session$clientData$url_search) > 1) {
57 | # when the app starts, the input$navbar gets triggered, but we don't
58 | # want to trigger the navigation function because the user didn't actively
59 | # navigate anywhere
60 | values$autoNavigating <- values$autoNavigating + 1
61 |
62 | restore(session$clientData$url_search)
63 | }
64 | })
65 |
66 | # restore the Shiny app's state based on the URL
67 | restore <- function(qs) {
68 | data <- parseQueryString(qs)
69 |
70 | if (!is.null(data[['page']])) {
71 | # we're about to change tabs programatically, so don't trigger the
72 | # navigation function
73 | values$autoNavigating <- values$autoNavigating + 1
74 |
75 | # change to the correct tab
76 | updateTabsetPanel(session, "navbar", data[['page']])
77 |
78 | # if the given tab has some more information we wnat to initialize,
79 | # do it here
80 | if (data[['page']] == "search") {
81 | if (!is.null(data[['query']])) {
82 | values$searchString <- data[['query']]
83 | updateTextInput(session, "text", value = data[['query']])
84 | }
85 | }
86 | }
87 | }
88 |
89 | # when the user changes tabs, save the state in the URL
90 | observeEvent(input$navbar, {
91 | if (values$autoNavigating > 0) {
92 | values$autoNavigating <- values$autoNavigating - 1
93 | return()
94 | }
95 |
96 | if (input$navbar == "search") {
97 | shinyjs::js$updateHistory(page = "search", query = values$searchString)
98 | } else {
99 | shinyjs::js$updateHistory(page = input$navbar)
100 | }
101 | })
102 |
103 | # when the user clicks prev/next buttons in the browser, restore the state
104 | observeEvent(input$navigatedTo, {
105 | restore(input$navigatedTo)
106 | })
107 | }
108 |
109 | shinyApp(ui = ui, server = server)
--------------------------------------------------------------------------------
/fb-share-img/www/app.js:
--------------------------------------------------------------------------------
1 | // Dean Attali, July 2015
2 |
3 | app = function() {
4 |
5 | // the message to share
6 | var payload = {
7 | picture : 'http://deanattali.com/img/deanimg.jpeg',
8 | link : 'http://deanattali.com',
9 | title : 'Dean Attali',
10 | caption : 'Short tagline goes here',
11 | description : 'This is the loooooooooooonger description where you can write more'
12 | };
13 |
14 | // facebook access token if the user is logged in and sharing permissions
15 | var fbToken = null;
16 |
17 | return {
18 |
19 | init : function() {
20 |
21 | // initialize facebook JDK
22 | (function(d, s, id) {
23 | var js, fjs = d.getElementsByTagName(s)[0];
24 | if (d.getElementById(id)) return;
25 | js = d.createElement(s);
26 | js.id = id;
27 | js.src = 'http://connect.facebook.net/en_US/sdk.js';
28 | fjs.parentNode.insertBefore(js, fjs);
29 | }(document, 'script', 'facebook-jssdk'));
30 | window.fbAsyncInit = function() {
31 | FB.init({
32 | appId: '978725282157807', // Facebook app ID (works when app is at URL fuf.me)
33 | cookie: true,
34 | version: 'v2.4'
35 | });
36 |
37 | // on page load, if the user is logged in, check for sharing permissions
38 | // and store the access token
39 | FB.getLoginStatus(function(response) {
40 | if (response.status === 'connected') {
41 | FB.api("me/permissions", function (resp) {
42 | if (resp.data) {
43 | for(var i = 0; i < resp.data.length; i++) {
44 | if (resp.data[i].permission == "publish_actions" &&
45 | resp.data[i].status == "granted")
46 | {
47 | fbToken = response.authResponse.accessToken;
48 | }
49 | }
50 | }
51 | });
52 | }
53 | });
54 | };
55 |
56 | // register click event on facebook share button
57 | $('#fbShareBtn').click(app.facebookShare);
58 |
59 | // register click event on facebook share base64 image button
60 | $('#fbShare64Btn').click(app.facebookShare64Click);
61 | },
62 |
63 | // share image on facebook
64 | facebookShare : function() {
65 | var params = {
66 | method : 'feed',
67 | picture : payload.picture,
68 | link : payload.link,
69 | name : payload.title,
70 | caption : payload.caption,
71 | description : payload.description
72 | }
73 | FB.ui(params, function(response) {
74 | if (response && response.post_id) {
75 | alert('Facebook share success!');
76 | console.log(response);
77 | } else {
78 | alert('Facebook share error')
79 | console.log(response);
80 | }
81 | });
82 | },
83 |
84 | // user clicked on sharing the base64 image
85 | facebookShare64Click : function() {
86 | // if there is an access token, call the function to share the image
87 | if (fbToken !== null) {
88 | app.facebookShare64();
89 | } else {
90 | // if the user isn't logged in or doesn't have sharing permissions,
91 | // prompt for it and then store the access token and attempt to sharethe image
92 | FB.login(function(response) {
93 | if(response.status == "connected") {
94 | var perms = response.authResponse.grantedScopes.split(",");
95 | if ($.inArray("publish_actions", perms) > -1) {
96 | fbToken = response.authResponse.accessToken;
97 | app.facebookShare64();
98 | return;
99 | }
100 | }
101 | alert("Error - app does not have permission to share posts");
102 | }, {
103 | scope : 'publish_actions',
104 | return_scopes : true
105 | });
106 | }
107 | },
108 |
109 | // share a base64 encoded image to facebook
110 | facebookShare64 : function() {
111 | var base64img = $("#plot").find("img").attr('src');
112 | var blob = dataURItoBlob(base64img);
113 | var fd = new FormData();
114 | fd.append("access_token", fbToken);
115 | fd.append("source", blob);
116 | fd.append("message", payload.title);
117 | $.ajax({
118 | url : "https://graph.facebook.com/me/photos?access_token=" + fbToken,
119 | type : "POST",
120 | data : fd,
121 | processData : false,
122 | contentType : false,
123 | cache : false,
124 | success : function(data){
125 | alert('Facebook share success!');
126 | console.log(data);
127 | },
128 | error : function(shr, status, data){
129 | alert('Facebook share error')
130 | console.log(data);
131 | }
132 | });
133 | }
134 | }
135 | }();
136 |
137 | $(function () { app.init(); });
138 |
139 | function dataURItoBlob(dataURI) {
140 | var byteString = atob(dataURI.split(',')[1]);
141 | var ab = new ArrayBuffer(byteString.length);
142 | var ia = new Uint8Array(ab);
143 | for (var i = 0; i < byteString.length; i++) {
144 | ia[i] = byteString.charCodeAt(i);
145 | }
146 | return new Blob([ab], { type: 'image/png' });
147 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shiny tips & tricks for improving your apps and solving common problems
2 |
3 | [](https://www.paypal.me/daattali)
4 |
5 | > *Copyright 2016 [Dean Attali](http://deanattali.com). Licensed under the MIT license.*
6 |
7 | This document contains a collection of various Shiny tricks that I commonly use or that I know many people ask about. Each folder contains a complete functional Shiny app that demonstrates how to perform a non trivial task in Shiny.
8 |
9 | Since I first learned about [Shiny](http://shiny.rstudio.com/) 2 years ago, I was always looking for ways to push Shiny to its limits and I enjoyed finding ways to work around common problems people were having (the harder the problem, the better!). I've built [many Shiny apps](https://daattali.com/shiny/) over these 2 years, both for myself and as a contractor for other people/companies, and throughout this time I developed a handy list of Shiny design patterns and tricks, some of which I present here.
10 |
11 | **Apart from providing these tips for free, I am also a [Shiny consultant](http://deanattali.com/shiny/) - if you need help with anything Shiny or R, feel free to [contact me](http://deanattali.com/contact).**
12 |
13 | ### Table of contents
14 |
15 | - [Prereq: How to hide/show something in Shiny? How to disable an input? How do I reset an input?](#shinyjs)
16 | - Easy
17 | - [Show a spinning "loading" animation while a plot is recalculating](#plot-spinner) ([code](./plot-spinner))
18 | - [Hide a tab](#hide-tab) ([code](./hide-tab))
19 | - [Hide/show shinydashboard sidebar programmatically](#shinydashboard-sidebar-hide) ([code](./shinydashboard-sidebar-hide))
20 | - [Loading screen](#loading-screen) ([code](./loading-screen))
21 | - [Automatically stop a Shiny app when closing the browser tab](#auto-kill-app) ([code](./auto-kill-app))
22 | - [Close the window (and stop the app) with a button click](#close-window) ([code](./close-window))
23 | - [Select input with more breathing room](#select-input-large) ([code](./select-input-large))
24 | - [Select input with groupings of options](#dropdown-groups) ([code](./dropdown-groups))
25 | - [Getting the value of an object in a running Shiny app without access to a debugger](#debug-value) ([code](./debug-value))
26 | - [Adding text (or inputs) to the navigation bar in a navbarPage](#navbar-add-text) ([code](./navbar-add-text))
27 | - Intermediate
28 | - [Pre-populate Shiny inputs when an app loads based on URL parameters](#url-inputs) ([code](./url-inputs))
29 | - [Split app code across multiple files (when codebase is large)](#split-code) ([code](./split-code))
30 | - [Use a variable from the server in a UI `conditionalPanel()`](#server-to-ui-variable) ([code](./server-to-ui-variable))
31 | - [Show user a generic error message when a Shiny error occurs in an output](#error-custom-message) ([code](./error-custom-message))
32 | - [Show a function's messages and warnings to the user](#show-warnings-messages) ([code](./show-warnings-messages))
33 | - [Fix filenames of files uploaded via fileInput()](#upload-file-names) ([code](./upload-file-names))
34 | - [Shiny app with sequence of pages](#multiple-pages) ([code](./multiple-pages))
35 | - [Toggle a UI element (alternate between show/hide) with a button](#simple-toggle) ([code](./simple-toggle))
36 | - [Send a message from R to JavaScript](#message-r-to-javascript) ([code](./message-r-to-javascript))
37 | - [Send a message from JavaScript to R](#message-javascript-r) ([code](./message-javascript-to-r))
38 | - [Send a message from JavaScript to R - force repetitive messages to get sent](#message-javascript-r-force) ([code](./message-javascript-to-r-force))
39 | - [Press the Enter key to simulate a button press](#proxy-click) ([code](./proxy-click))
40 | - [Run arbitrary code live in Shiny - great for testing during development](#run-arbitrary-code) ([code](./run-arbitrary-code))
41 | - Advanced
42 | - [Serve files (images/text files/etc) instead of webpages from a Shiny app](#serve-images-files) ([code](./serve-images-files))
43 | - [Update multiple Shiny inputs without knowing input type](#update-input) ([code](./update-input))
44 | - ["Busy..." / "Done!" / "Error" feedback after pressing a button](#busy-indicator) ([code](./busy-indicator))
45 | - [Simple AJAX system for Shiny apps (JS -> R -> JS communication)](#api-ajax) ([code](./api-ajax))
46 | - [Use a custom function to convert the JavaScript data into an R object](#javascript-to-r-handler) ([code](./javascript-to-r-handler))
47 | - [Navigation in a Shiny app (forward/backwards in history)](#navigate-history) ([code](./navigate-history))
48 | - [Sharing images on Facebook](#fb-share-img) ([code](./fb-share-img))
49 | - [Facebook login through JavaScript in Shiny](#fb-login) ([code](./fb-login))
50 | - [Multiple scrollspy on same page - basic](#multiple-scrollspy-basic) ([code](./multiple-scrollspy-basic))
51 | - [Multiple scrollspy on same page - advanced](#multiple-scrollspy-advanced) ([code](./multiple-scrollspy-advanced))
52 | - Very advanced
53 | - [Create a reactive trigger](#reactive-trigger) ([code](./reactive-trigger))
54 | - [Create a reactive value that only triggers when its value changes](#reactive-dedupe) ([code](./reactive-dedupe))
55 | - [Create a cancellable long-running task](#forked-task) ([code](./forked-task))
56 | - [Updates](#updates)
57 |
58 | Prereq: How to hide/show something in Shiny? How to disable an input? How do I reset an input?
59 |
60 | A few very common questions in Shiny are "how do I hide/show something", "how do I disable an input", and "how do I reset an input". Many of the code samples in this document also rely on being able to do these things, so I wanted to start by saying that I will be using the [shinyjs](https://github.com/daattali/shinyjs) package to do all that. (Yes, I know it looks like I'm shamelessly advertising shinyjs by saying this... but it is going to be useful for many concepts here)
61 |
62 | Show a spinning "loading" animation while a plot is recalculating
63 |
64 | **[Link to code](./plot-spinner)**
65 |
66 | When a Shiny plot is recalculating, the plot gets grayed out. This app shows how you can add a spinner wheel on top of the plot while it is recalculating, to make it clear to the user that the plot is reloading. There can be many different ways to achieve a similar result using different combinations of HTML/CSS, this example is just the simplest one I came up with. **Update**: There is now a package [`shinycssloaders`](https://cran.r-project.org/package=shinycssloaders) to do this easier!
67 |
68 | [](./plot-spinner)
69 |
70 |
71 | Hide a tab
72 |
73 | **[Link to code](./hide-tab)**
74 |
75 | This app demonstrates how `shinyjs` can be used to hide/show a specific tab in a `tabsetPanel`. In order to use this trick, the `tabsetPanel` must have an id. Using this id and the value of the specific tab you want to hide/show, you can call `shinyjs::hide()`/`shinyjs::show()`/`shinyjs::toggle()`.
76 |
77 | [](./hide-tab)
78 |
79 |
80 |
81 | **[Link to code](./shinydashboard-sidebar-hide)**
82 |
83 | A common question regarding `shinydashboard` is how to programmatically hide/show the sidebar. This can very easily be done using the [shinyjs](https://github.com/daattali/shinyjs) package, as demonstrated here.
84 |
85 | [](./shinydashboard-sidebar-hide)
86 |
87 | Loading screen
88 |
89 | **[Link to code](./loading-screen)**
90 |
91 | This simple app shows how to add a "Loading..." screen overlaying the main app while the app's server is initializing. The main idea is to include an overlay element that covers the entire app (using CSS), hide the main app's UI, and at the end of the server function show the UI and hide the overlay.
92 |
93 | [](./loading-screen)
94 |
95 | Automatically stop a Shiny app when closing the browser tab
96 |
97 | **[Link to code](./auto-kill-app)**
98 |
99 | When developing a Shiny app and running the app in the browser (as opposed to inside the RStudio Viewer), it can be annoying that when you close the browser window, the app is still running and you need to manually press "Esc" to kill it. By adding a single line to the server code `session$onSessionEnded(stopApp)`, a Shiny app will automatically stop running whenever the browser tab (or any session) is closed.
100 |
101 | Close the window (and stop the app) with a button click
102 |
103 | **[Link to code](./close-window)**
104 |
105 | This simple example shows how you can have a button that, when clicked, will close the current browser tab and stop the running Shiny app (you can choose to do only one of these two actions).
106 |
107 |
108 |
109 | **[Link to code](./select-input-large)**
110 |
111 | One common CSS question in Shiny is how to make the select input dropdown menu have some more whitespace. It's actually very easy to do with just two CSS rules, as demonstrated in this example.
112 |
113 | [](./select-input-large)
114 |
115 | Select input with groupings of options
116 |
117 | **[Link to code](./dropdown-groups)**
118 |
119 | This isn't really a trick as much as an [undocumented feature](https://github.com/rstudio/shiny/issues/1321) in Shiny that not many people know about. Usually when people write dropdowns in Shiny, all the options are just provided as one long list. But it is possible to have groups of items, and it's very easy to do.
120 |
121 | [](./dropdown-groups)
122 |
123 | Getting the value of an object in a running Shiny app without access to a debugger
124 |
125 | **[Link to code](./debug-value)**
126 |
127 | Sometimes you may need to know the value of some variable/function call in a Shiny app when you don't have easy access to debugging tools. For example, suppose you deploy your shiny app on shinyapps.io and it's running into a weird error there. You're sure that it's because one of the packages on the shinyapps.io server is not the version that you expect, but you want to make sure that your suspicion is correct. It's a bit difficult to debug on shinyapps.io (one thing you could do is try to use the log files), but there's a quick and easy way to see any value in a Shiny app in real-time.
128 |
129 | Adding text (or inputs) to the navigation bar in a navbarPage
130 |
131 | **[Link to code](./navbar-add-text)**
132 |
133 | Traditionally, a `navbarPage()` only accepts tabs and menu items inside of it. Even though the Bootstrap navbar (which is what Shiny uses) supports adding text and input widgets into the navbar, Shiny doesn't have support for that. This app shows you how to very easily achieve that.
134 |
135 |
136 |
137 | **[Link to code](./url-inputs)**
138 |
139 | This simple app demonstrates how you can fill out certain input fields when a Shiny app loads based on URL parameters.
140 |
141 | [](./url-inputs)
142 |
143 | Split app code across multiple files (when codebase is large)
144 |
145 | **[Link to code](./split-code)**
146 |
147 | When creating Shiny apps with a lot of code and a complex UI, it can sometimes get very messy and difficult to maintain your code when it's all in one file. What you can do instead is have one "main" UI and "main" server and split your UI and server code into multiple files. This can make your code much more manageable and easier to develop when it grows large. You can split the code however you want, but I usually like to split it logically: for example, if my app has 4 tabs then the UI for each tab would be in its own file and the server code for each tab would be in its own file. The example code here shows how to separate the code of an app with two tabs into 2 UI files and 2 server files (one for each tab).
148 |
149 |
150 | Use a variable from the server in a UI `conditionalPanel()`
151 |
152 | **[Link to code](./server-to-ui-variable)**
153 |
154 | When using a conditional panel in the UI, the condition is usually an expression that uses an input value. But what happens when you want to use a conditional panel with a more complex condition that is not necessarily directly related to an input field? This example shows how to define an output variable in the server code that you can use in the UI. An alternative approach is to use the `show()` and `hide()` functions from the [shinyjs](https://github.com/daattali/shinyjs) package.
155 |
156 |
157 |
158 | Show user a generic error message when a Shiny error occurs in an output
159 |
160 | **[Link to code](./error-custom-message)**
161 |
162 | When a Shiny output encounters an error, the exact error message will be shown to the user in place of the output. This is generally a good feature because it's easier to debug when you know the exact error. But sometimes this is undesireable if you want to keep the specifics of what happened unknown to the user, and you prefer to just show the user a generic "Some error occurred; please contact us" message. This may sound counter intuitive, but you can actually do this with a tiny bit of CSS, as this example shows.
163 |
164 | Show a function's messages and warnings to the user
165 |
166 | **[Link to code](./show-warnings-messages)**
167 |
168 | Sometimes when you call a function, it may print out some messages and/or warnings to the console. If you want to be able to relay these warnings/messages to your app in real time, you can combine `withCallingHandlers` with the `html` function from [shinyjs](https://github.com/daattali/shinyjs).
169 |
170 | (Originally developed as an [answer on StackOverflow](http://stackoverflow.com/questions/30474538/possible-to-show-console-messages-written-with-message-in-a-shiny-ui))
171 |
172 | Fix filenames of files uploaded via fileInput()
173 |
174 | **[Link to code](./upload-file-names)**
175 |
176 | When selecting files using a `fileInput()`, the filenames of the selected files are not retained. This is not usually a problem because usually you only care about the contents of a file and not its name. But sometimes you may actually need to know the original name of each selected file. This example shows how to write a simple function `fixUploadedFilesNames()` to rename uploaded files back to their original names.
177 |
178 |
179 | Shiny app with sequence of pages
180 |
181 | **[Link to code](./multiple-pages)**
182 |
183 | This app demonstrates how to write a Shiny app that has a sequence of different pages, where the user can navigate to the next/previous page. This can be useful in many scenarios that involve a multi-step process. This behaviour can also be achieved by simply using tabs, but when using tabs the user can freely move from any tab to any other tab, while this approach restricts the user to only move to the previous/next step, and can also control when the user can move on to the next page.
184 |
185 | [](./multiple-pages)
186 |
187 | Toggle a UI element (alternate between show/hide) with a button
188 |
189 | **[Link to code](./simple-toggle)**
190 |
191 | Sometimes you want to toggle a section of the UI every time a button is clicked. This app shows how to achieve very basic toggle functionality using `conditionalPanel()`. If you want anything more advanced, you can use the `toggle()` function from the [shinyjs](https://github.com/daattali/shinyjs) package.
192 |
193 | Send a message from R to JavaScript
194 |
195 | **[Link to code](./message-r-to-javascript)**
196 |
197 | While Shiny is very powerful, there are many things that cannot be achieved in R and must be done in JavaScript. When including JavaScript code in a Shiny app, you sometimes need to send a message or a value from R to the JavaScript layer. This example how this is done.
198 |
199 | Send a message from JavaScript to R
200 |
201 | **[Link to code](./message-javascript-to-r)**
202 |
203 | In some shiny applications you may want to send a value from JavaScript to the R server. This can be useful in a variety of applications, for example if you want to capture a mouse click or a keyboard press of the user and tell R about it. This example shows how this is done.
204 |
205 | Send a message from JavaScript to R - force repetitive messages to get sent
206 |
207 | **[Link to code](./message-javascript-to-r-force)**
208 |
209 | When you send a message from JS to R with the exact same value multiple times in a row, only the first time actually gets sent to Shiny. This can often be problematic, and this example shows a fairly simple workaround.
210 |
211 | Press the Enter key to simulate a button press
212 |
213 | **[Link to code](./proxy-click)**
214 |
215 | This is a simple app with a tiny bit of JavaScript that shows you how to cause an Enter key press inside an input to trigger a click on a button.
216 |
217 | Run arbitrary code live in Shiny - great for testing during development
218 |
219 | **[Link to code](./run-arbitrary-code)**
220 |
221 | **NOTE: This is now part of the shinyjs package -- look at the documentation for `?shinyjs::runcode`**
222 |
223 | When I develop Shiny apps or packages for Shiny, I often find myself wanting to be able to run R code on-demand while the app is running. Outside of Shiny, in regular R programming, we have the R console where we can run any command at any point in time, but in Shiny we don't really have that. So while developing, I often add a text input that lets me type any R code into it and then run it. This is very useful for testing.
224 |
225 | [](./run-arbitrary-code)
226 |
227 | Serve files (images/text files/etc) instead of webpages from a Shiny app
228 |
229 | **[Link to code](./serve-images-files)**
230 |
231 | It is possible to serve an image or another file directly from your Shiny app instead of a webpage. The method shown here is a simple proof-of-concept of how to achieve this functionality. It also supports passing GET parameters to the file-generating logic so that the file can be parameterized.
232 |
233 | [](./serve-images-files)
234 |
235 |
236 |
237 | **[Link to code](./update-input)**
238 |
239 | Shiny allows you to update an input element only if you know the type of input. Furthermore, Shiny only allows you to update input elements one by one. This Shiny app shows how you can update an input only using its ID and without knowing its type, and how to update multiple inputs together.
240 |
241 | "Busy..." / "Done!" / "Error" feedback after pressing a button
242 |
243 | **[Link to code](./busy-indicator)**
244 |
245 | When the user clicks on a button, it usually results in some R code being run. Sometimes the resulting code is short and quick and the user doesn't even notice any delay, but sometimes the button click initialiates some long process or computation that can take more than 1 second to complete. In those cases, it might be a bit confusing to the user if there is no immediate feedback notifying that the action is being performed. For example, if the user clicked a button to load data from a database and it takes 3 seconds to connect to the database, it can be useful to show a "Connecting..." and then a "Done!" (or "Error") message, instead of just letting the user wait without seeing any message. Of course when the wait time is only 2-3 seconds it's not a big deal, but you can imagine that for a 20-second process, the user might think that something went wrong if there is no feedback. This example shows how to add some immediate feedback to the user after a button is clicked, including disabling/enabling the button and showing a success/error message when appropriate.
246 |
247 | [](./busy-indicator)
248 |
249 | Simple AJAX system for Shiny apps (JS -> R -> JS communication)
250 |
251 | **[Link to code](./api-ajax)**
252 |
253 | Sometimes it's useful to be able to call an R function from JavaScript and use the return value from R back in JavaScript. This sort of communication is usually done with AJAX in JavaScript. This app shows how to implement a simple and ultra lightweight AJAX-like system in Shiny, to be able to call functions in R.
254 |
255 | Use a custom function to convert the JavaScript data into an R object
256 |
257 | **[Link to code](./javascript-to-r-handler)**
258 |
259 | When using `Shiny.onInputChange(name, data)` (as described [here](./message-javascript-to-r)), you are passing in a JavaScript object (`data`) and expect it to get converted to an R object (`input$name`). This conversion happens by serializing and deserializing the data to and from JSON. Usually `input$name` will look exactly like you'd expect it to, but it is possible for the conversion process to not do exactly what you want. Alternatively, you may just want to alter the data slightly in R before presenting it to Shiny.
260 |
261 | Navigation in a Shiny app (forward/backwards in history)
262 |
263 | **[Link to code](./navigate-history)**
264 |
265 | Sometimes it's nice to be able to support navigation within a Shiny app, especially when there are multiple tabs or some other form of "multiple pages" in a Shiny app. Since Shiny apps are a single page, the browser nagivation buttons (previous/next page) don't work when "navigating" within a Shiny app. You also can't bookmark a certain "page" in a Shiny app - every time you go to an app, you will be shown the initial state of the app. This app shows how to implement basic navigation in Shiny apps.
266 |
267 | [](./navigate-history)
268 |
269 | Sharing images on Facebook
270 |
271 | **[Link to code](./fb-share-img)**
272 |
273 | There are two ways to share images on Facebook: either using an image URL and a popup dialog, or by programmatically supplying the Facebook API with a base64 encoded image. This example shows both.
274 |
275 | Facebook login through JavaScript in Shiny
276 |
277 | **[Link to code](./fb-login)**
278 |
279 | This app shows how you can use the [AJAX-like system](./api-ajax) in Shiny to authorize a user using Facebook's JavaScript library and pass the user's information to R for processing.
280 |
281 |
282 |
283 | **[Link to code](./multiple-scrollspy-basic)**
284 |
285 | The Bootstrap *scrollspy* plugin does not support multiple scrollspy objects per page. This Shiny app demonstrates how to support scrollspy on multiple tabs by having one common scrollspy control that gets updated via JavaScript whenever a tab is changed to reflect the contents of the new tab.
286 |
287 |
288 |
289 | **[Link to code](./multiple-scrollspy-advanced)**
290 |
291 | The Bootstrap *scrollspy* plugin does not support multiple scrollspy objects per page.
292 | This Shiny app demonstrates how to support scrollspy on multiple tabs by allowing each tab to have its own independent scrollspy control and using JavaScript to ensure only the scrollspy on the current tab is activated.
293 |
294 | Create a reactive trigger
295 |
296 | **[Link to code](./reactive-trigger)**
297 |
298 | A reactive trigger can be used when you want to be able to explicitly trigger a reactive expression. You can think of it as being similar to an action button, except instead of clicking on a button to trigger an expression, you can programatically cause the trigger. This concept and code was created by Joe Cheng (author of shiny).
299 |
300 | Create a reactive value that only triggers when its value changes
301 |
302 | **[Link to code](./reactive-dedupe)**
303 |
304 | If you understand shiny and reactivity well, you will know that a reactive expression gets re-run whenever any of its reactive dependencies are invalidated. This is generally the desired behaviour, but there is one caveat that comes up occassionally: even if the underlying value of the reactive dependency hasn't changed, it can still be considered "invalidated". This can mean that a reactive expression will run again with exactly the same values because its dependencies have not changed, even though they're invalidated. Joe Cheng (author of shiny) has [a solution](https://github.com/rstudio/shiny/issues/1484#issuecomment-262812760) for this.
305 |
306 | Create a cancellable long-running task
307 |
308 | **[Link to code](./forked-task)**
309 |
310 | In shiny (or R in general), when you start running a function, you generally cannot do anything else until that function completes. This means that if the user of a shiny app does something that results in a 2-minute calculation, the entire app becomes unusable and the user has to wait 2 minutes before they can interact with the app again. This has been a problem for some people, and the shiny team is currently looking into providing a solution for this. In the meantime, Joe Cheng (author of Shiny) [came up with a nice workaroud](https://gist.github.com/jcheng5/9504798d93e5c50109f8bbaec5abe372). His solution will likely not work on Windows and it is not a robust fool-proof solution, but itcan get the job done.
311 |
312 | Updates
313 |
314 | This list is slowly growing with time. Here are the new tricks added since the post was originally published:
315 |
316 | - 2016-08-29: [Getting the value of an object in a running Shiny app without access to a debugger](#debug-value)
317 | - 2016-09-03: [Show a function's messages and warnings to the user](#show-warnings-messages)
318 | - 2016-09-16: [Use a custom function to convert the JavaScript data into an R object](#javascript-to-r-handler)
319 | - 2016-10-11: [Run arbitrary code live in Shiny - great for testing during development](#run-arbitrary-code)
320 | - 2016-11-23: [Adding text (or inputs) to the navigation bar in a navbarPage](#navbar-add-text)
321 | - 2017-02-12: Added a 'Very advanced' section with 3 clever reusable shiny constructs
322 |
--------------------------------------------------------------------------------