Skip to content

Commit 0102c8f

Browse files
committed
Complete tutorial
1 parent 1812397 commit 0102c8f

File tree

2 files changed

+150
-2
lines changed

2 files changed

+150
-2
lines changed

vignettes/intro_htmlwidgets.Rmd

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,18 +135,166 @@ Then, there's a call to `reactWidget`, and we pass it four arguments:
135135
1. The React components that should be exposed to the widget (here, `{}`: none)
136136
1. Additional configuration options (here, `{}`: none)
137137

138-
In order to implement additional functionality, we must change the third argument by adding React components provided by `react-sparklines`. We can do it like this:
138+
In order to make our widget do something interesting, we must change the third argument by passing React components provided by `react-sparklines`. We can do it like this:
139139

140140

141141
```{js eval = FALSE}
142142
import * as SparklinesComponents from 'react-sparklines';
143143
import { reactWidget } from 'reactR';
144144
145-
reactWidget('sparklines', 'output', SparklinesComponents, {});
145+
reactWidget('reactSparklines', 'output', SparklinesComponents, {});
146146
```
147147

148148
Instead of passing an empty object as the React components, we pass an object populated with all of the components exported by the `'react-sparklines'` module as `SparklinesComponents`.
149149

150+
We could also have exposed only a subset of the components exported by `react-sparklines` with code like the following:
151+
152+
```{js eval = FALSE}
153+
import { Sparklines, SparklinesLine } from 'react-sparklines';
154+
import { reactWidget } from 'reactR';
155+
156+
reactWidget('reactSparklines', 'output', SparklinesComponents, {
157+
Sparklines: Sparklines,
158+
SparklinesLine: SparklinesLine
159+
});
160+
```
161+
162+
The primary difference between the two is the `import` syntax we chose to use.
163+
150164
After updating your `app.R`, run `yarn run webpack --mode=development` to rebuild the JavaScript.
151165

152166
### R changes
167+
168+
The next code we'll need to modify to make `react-sparklines` work is on the R side, at the top of the file `R/reactSparklines.R`:
169+
170+
```{r eval = FALSE}
171+
reactSparklines <- function(message, width = NULL, height = NULL, elementId = NULL) {
172+
173+
# describe a React component to send to the browser for rendering.
174+
component <- reactR::reactMarkup(htmltools::tag("div", list(message)))
175+
176+
# create widget
177+
htmlwidgets::createWidget(
178+
name = 'reactSparklines',
179+
component,
180+
width = width,
181+
height = height,
182+
package = 'reactSparklines',
183+
elementId = elementId
184+
)
185+
}
186+
```
187+
188+
This function is the one responsible for creating an instance of our widget. In the `server` function of the generated `app.R`, we use it like this:
189+
190+
```{R eval = FALSE}
191+
server <- function(input, output, session) {
192+
output$widgetOutput <- renderReactSparklines(
193+
reactSparklines("Hello world!")
194+
)
195+
}
196+
```
197+
198+
In the code above, `renderReactSparklines` expects to receive the return value of `reactSparklines` as its argument.
199+
200+
`reactSparklines` is a thin wrapper around `htmlwidgets::createWidget`. Its most important argument, `message`, is the one we must change the handling of. In the current implementation, the following happens to `message`:
201+
202+
```{R eval = FALSE}
203+
component <- reactR::reactMarkup(htmltools::tag("div", list(message)))
204+
```
205+
206+
1. We wrap `message` in a list
207+
1. We call `htmltools::tag` to create a `div` with the list we created as its children
208+
1. We call `reactR::reactMarkup` to prepare the tag to be rendered in the browser
209+
1. We assign to `component`
210+
211+
Then, we pass `component` as the second argument of `htmlwidgets::createWidget`.
212+
213+
Widgets created using reactR expect for this argument to be a representation of `markup`, or either an `htmltools::tag` or a `reactR::component`. When we called `reactR.reactWidget` in our JavaScript, we created and installed the browser-side implementation of a widget that expects to receive markup and then renders that markup in the widget's HTML container.
214+
215+
Let's modify the `reactSparklines` function so that it handles `message` differently. In fact, let's change the meaning of that argument completely. Instead of a `message`, it should take an argument `data`, which is the vector of numbers to display as a sparkline graph:
216+
217+
```{r eval = FALSE}
218+
reactSparklines <- function(data, width = NULL, height = NULL, elementId = NULL) {
219+
220+
# describe a React component to send to the browser for rendering.
221+
component <- reactR::reactMarkup(reactR::component(
222+
"Sparklines",
223+
list(data = data, reactR::component("SparklinesLine"))
224+
))
225+
226+
# create widget
227+
htmlwidgets::createWidget(
228+
name = 'reactSparklines',
229+
component,
230+
width = width,
231+
height = height,
232+
package = 'reactSparklines',
233+
elementId = elementId
234+
)
235+
}
236+
```
237+
238+
Now, we're using the `data` argument in a more sophisticated way. We're assigning it to the attribute `data` of the `Sparklines` component, as per the first example in the [react-sparklines documentation](http://borisyankov.github.io/react-sparklines/). Then, we create a `SparklinesLine` component to tell `react-sparklines` the format to display the data.
239+
240+
Essentially, we're using R code that corresponds directly to the [JSX](https://reactjs.org/docs/introducing-jsx.html) syntax in the `react-sparklines` examples. This is a major benefit of using reactR: examples in JSX are generally easy to adapt.
241+
242+
Our widget now assumes that `data` is a numeric vector, and relies on the fact that this vector becomes a JavaScript array once it's sent to the browser.
243+
244+
After you've edited `R/reactSparklines.R`, run `devtools::install(quick = TRUE)` or hit `Ctrl-Shift-B` (macOS: `Cmd-Shift-B) to rebuild the package.
245+
246+
## Trying it out
247+
248+
It's finally time to modify `app.R` and test our widget modifications out. Open `app.R`, it should look like this:
249+
250+
```{r eval = FALSE}
251+
library(shiny)
252+
library(reactSparklines)
253+
254+
ui <- fluidPage(
255+
titlePanel("reactR HTMLWidget Example"),
256+
reactSparklinesOutput('widgetOutput')
257+
)
258+
259+
server <- function(input, output, session) {
260+
output$widgetOutput <- renderReactSparklines(
261+
reactSparklines("Hello world!")
262+
)
263+
}
264+
265+
shinyApp(ui, server)
266+
```
267+
268+
The thing we need to change is the argument to `reactSparklines`. It should no longer be a string, but a numeric vector to display as a sparkline graph. Here is the amended code:
269+
270+
```{r eval = FALSE}
271+
library(shiny)
272+
library(reactSparklines)
273+
274+
ui <- fluidPage(
275+
titlePanel("reactR HTMLWidget Example"),
276+
reactSparklinesOutput('widgetOutput')
277+
)
278+
279+
server <- function(input, output, session) {
280+
output$widgetOutput <- renderReactSparklines(
281+
reactSparklines(sample.int(10, 10))
282+
)
283+
}
284+
285+
shinyApp(ui, server)
286+
```
287+
288+
Now, when you run `app.R`, you should see something like the following:
289+
290+
```{r echo=FALSE}
291+
knitr::include_graphics('./widget_app_improved.jpg')
292+
```
293+
294+
Congratulations, you just wrote your first React-based widget!
295+
296+
Another thing you can try is running `reactSparklines(data = sample.int(10, 10))` directly in the RStudio Console. You should see a sparkline graph appear in the Viewer.
297+
298+
## Next steps
299+
300+
We've reached the end of this tutorial, but if you'd like to see how a sparklines library could be further improved, you might be interested in our [sparklines-example](https://github.com/react-R/sparklines-example) project. It picks up where we leave off, by creating a kind of DSL that makes the other sparkline types easily accessible from R.

vignettes/widget_app_improved.jpg

26 KB
Loading

0 commit comments

Comments
 (0)