Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/gadenbuie/shinyComponents

:memo::sparkles: Shiny Components in R Markdown
https://github.com/gadenbuie/shinyComponents

Last synced: 3 months ago
JSON representation

:memo::sparkles: Shiny Components in R Markdown

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
message = FALSE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```

# shinyComponents

[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental)

The goal of shinyComponents is to turn R Markdown documents in self-contained
Shiny components.
Like [web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
meets [Shiny modules](https://shiny.rstudio.com/articles/modules.html)
(without that _ShadowDOM_ business).

## Installation

You can install the released version of shinyComponents from Github.

``` r
# install.packages("remotes")
remotes::install_github("gadenbuie/shinyComponents")
```

## Example

Writing a Shiny component often requires wiring together a number of different languages:

- R for the Shiny UI and Server pieces
- CSS or SASS for styling
- JavaScript for interactivity
- HTML dependencies for external libraries
- ...and more R for data and other things.

Imagine you've written out all of those pieces in a R Markdown file,
like the one in the details section below (click to expand).

Example R Markdown: example-component.Rmd

````markdown
`r paste(readLines(system.file("example-component.Rmd", package = "shinyComponents")), collapse = "\n")`
````

To read the component from the R Markdown,
load `shinyComponents` and create a new `ShinyComponent`.

```{r example}
library(shinyComponents)

ex_rmd <- system.file("example-component.Rmd", package = "shinyComponents")

ex <- ShinyComponent$new(ex_rmd)
```

Component documents contain `{ui}` and `{server}` chunks.
That means you can write a chunk like this

````markdown
```{ui .tagList = TRUE, ... = list(message = "MESSAGE")}`r ''`
h3(initial_header)
p(message)
sliderInput("number", "Pick a number", min = 0, max = 10, value = 1)
verbatimTextOutput("debug")
```
````

and, because there is only one `{ui}` chunk,
this chunk will become available in the `$ui()` method.

```{r eval=FALSE}
ex$ui()
```

```{r echo=FALSE, comment=""}
print(ex$ui())
```

Similarly, the `server` chunk is available in the `$server()` method,
for you to be call anywhere in your app's server function.
Any SASS, CSS or JavaScript chunks are concatenated and included in appropriate
`` and `<script>` tags when you call the `$assets()` method.

The example above hints at a number of additional features:

* First, for simplicity,
if you set `.tagList = TRUE` in your `{ui}` chunk's options,
each expression in the chunk is added to an `htmltools::tagList()`.

* Second, any plain R code chunks are treated as global code shared between the UI and Server components.
In this case, `initial_header` was set to `"Just a simple demo"` in a plain `{r}` chunk.

* Third, `{ui}` and `{server}` components may accept inputs whose default values
are set with the list provided to the `...` chunk option.
in this case, by setting `... = list(message = "MESSAGE")`,
we declared that the `$ui()` method will have a `message` argument.

```{r eval=FALSE}
ex$ui(message = "Hello, world!")
```

```{r echo=FALSE, comment=""}
print(ex$ui(message = "Hello, World!")[[1]])
```

### Using in a Shiny app

Here's a bare bone Shiny app incorporating all of the pieces.

```{r eval=FALSE}
library(shiny)

ex <- ShinyComponent$new(ex_rmd)

ui <- fluidPage(
ex$ui()
)

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

shinyApp(ui, server)
```

Or you can run a demo app that pulls the components together
into an app just like the one above.

``` r
ex$app()
```

![Demonstration of Shiny app from R Markdown component document.](https://raw.githubusercontent.com/gadenbuie/shinyComponents/assets/demo-component.gif)

### Complex, composable components

Finally, for more complex components,
you may want to break your component into
smaller pieces so that you can have more control over how and where they are used.
In this case, give each `{ui}` or `{server}` chunk a name.

````
# example-increment.Rmd

```{ui button}`r ''`
actionButton(ns("increment"), "Plus One")
```

```{ui number}`r ''`
uiOutput(ns("number"))
```

```{ui, .tagList = TRUE}`r ''`
self$button()
self$number()
```

```{server}`r ''`
output$number <- renderUi(span(input$number))
```
````

In this case,
instead of a `$ui()` method,
the Shiny Component will contain a `$ui` property.
That property will contain a list of three functions:

1. `$ui$button()` from the `ui button` chunk
1. `$ui$number()` from the `ui number` chunk
1. and `$ui$ui()` from the unnamed `ui` chunk (or the `ui` chunk named `ui`).

By convention, the `ui` chunk can be seen as the "default" UI arrangement.
You can use this chunk to coordinate a default view of the various pieces of the component's UI.
In situations where you want more control over where and how the components are laid out,
you can reference each component by name, e.g. `inc$ui$button()` and `inc$ui$number()`.

### Shiny Modules

Shiny Components can be [Shiny Modules](https://shiny.rstudio.com/articles/modules.html)!
You only need to do two (or maybe three) things:

1. Wrap input IDs in `ns()` in your `ui` chunks.
It's a good idea to always do this, since it won't interfere with a component when used not as a module.

2. If you want your server module to return a value,
add a return value at the end of your `server` chunk (this step is optional).

````
```{server}`r ''`
# ... component server logic ...

reactive(input$number)
```
````

3. Set the `id` argument when you call your `$ui`, `$server` or `$app()` methods.

That's it!
Your components will automatically be modularized!

```{r eval=FALSE}
library(shiny)

inc <- ShinyComponent$new("example_increment.Rmd")

ui <- fluidPage(
inc$ui$ui(id = "first"),
inc$ui$ui(id = "second"),
verbatimTextOutput("debug")
)

server <- function(input, output, session) {
first <- inc$server(id = "first")
second <- inc$server(id = "second")

output$debug <- renderPrint(str(list(first = first(), second = second())))
}

shinyApp(ui, server)
```

### Dependencies

You can also declare dependencies for the component using the YAML header in the R markdown document.
A dependency can either be the name of a function in a package that returns and HTML dependency —
like the `rmarkdown::html_dependency_font_awesome` entry below —
or it can be a list with named entries that match the arguments of `htmltool::htmlDependency()`.

```
---
dependencies:
- rmarkdown::html_dependency_font_awesome
- name: animate.css
version: 4.1.1
src:
href: https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/
stylesheet: animate.min.css
---
```

These dependencies are available from the component using the `$dependencies` property,
and will have the name of the dependency.

By default, all declared dependencies are included with the `ui()` method.
Or you can include individual dependencies in the components that require them
by referencing `self$dependencies$<dep name>`.
For example, the button component in our previous example
could rely on the Font Awesome dependency

````
```{ui button, .tagList = TRUE}`r ''`
actionButton(ns("increment"), "Plus One")
self$dependencies$`font-awesome`
```
````

or the UI returned by the server function could depend on animate.css.

````
```{server}`r ''`
output$number <- renderUi(span(input$number, self$dependencies$animate.css))
```
````

### Assets

Shiny Components supports turning `css`, `sass` and `js` chunks into CSS or JavaScript assets.
`sass` chunks are compiled to CSS and
all assets are in-lined using `<style>` or `<script>` tags
via the `$assets()` method.

Like dependencies, CSS and JavaScript assets are included by default in the `ui()` method.
Alternatively, you may include them manually with `self$assets()`
or with the `$assets()` method of your Shiny Component.

Currently, all CSS and JavaScript assets are returned together,
but in the future we may add the ability to separate these by component piece as well.