3 min read

map(expr) + renderUI -> dynamic shiny

I wrote my first shiny widget. A shiny widget is an html widget which uses shiny functions. In form it resembles a shiny app very closely, only the shinyApp function is wrapped in another function, which can take whatever arguments you like. More background can be found here.

In order for the shiny widget to run, it needs a shiny runtime behind the document, which means it needs a server, just like a shiny app. For that reason, the widget it’s running in this actual document, it’s running on at shinyapps.io. I’ve embedded it below.

Just a bit of explanation about how it works: The key is in mapping checkboxGroupInput (or some other input function) across the names of the data.frame passed to the widget. A slightly simpler version would be

library(shiny)
library(tidyverse, warn.conflicts = FALSE)

# invent some names
c("john", "paul", "ringo") %>%
# map a suitable shiny input across them
map(. %>% { expr(checkboxInput(!!., !!. )) } %>%  eval ) %>% 
tagList 

Let’s break this down one step further. First, why use tagList? Well, because map returns a list, and we don’t want a list, we wants legit html. To see the hmtl in markdown without it rendering need a bit of a workaround, so the next code chunk isn’t actually run, but if you run

checkboxInput("john", "john")

In an R console, what you get is

<div class="form-group shiny-input-container">
  <div class="checkbox">
    <label>
      <input id="john" type="checkbox"/>
      <span>john</span>
    </label>
  </div>
</div>

I know I don’t actually use tagList in the widget, I use renderUI, but they’re performing the same role there. In the shinywidget it needs renderUI, but in markdown, outside of a widget you could tagList.

So, mapping checkboxInput across a list of names gives you a list of chunks of html. tagList just combines a list of chunks of html into one big chunk, without it, you just get the html from the last element of the list (I’m not sure why).

list(checkboxInput("john", "john"), 
        checkboxInput("paul", "paul"),
        checkboxInput("ringo", "ringo"))
## [[1]]
## <div class="form-group shiny-input-container">
##   <div class="checkbox">
##     <label>
##       <input id="john" type="checkbox"/>
##       <span>john</span>
##     </label>
##   </div>
## </div>
## 
## [[2]]
## <div class="form-group shiny-input-container">
##   <div class="checkbox">
##     <label>
##       <input id="paul" type="checkbox"/>
##       <span>paul</span>
##     </label>
##   </div>
## </div>
## 
## [[3]]
## <div class="form-group shiny-input-container">
##   <div class="checkbox">
##     <label>
##       <input id="ringo" type="checkbox"/>
##       <span>ringo</span>
##     </label>
##   </div>
## </div>

v.s.

tagList(checkboxInput("john", "john"), 
        checkboxInput("paul", "paul"),
        checkboxInput("ringo", "ringo"))

And finally, good ol’ map(expr), which makes it easy to create any list of expressions you want.

letters[1:5] %>% map(~ expr(selectInput(!!., !!., choices = c(TRUE, FALSE))))
## [[1]]
## selectInput("a", "a", choices = c(TRUE, FALSE))
## 
## [[2]]
## selectInput("b", "b", choices = c(TRUE, FALSE))
## 
## [[3]]
## selectInput("c", "c", choices = c(TRUE, FALSE))
## 
## [[4]]
## selectInput("d", "d", choices = c(TRUE, FALSE))
## 
## [[5]]
## selectInput("e", "e", choices = c(TRUE, FALSE))

And that’s it!