---
output: github_document
---
# Building an HTML Widget: Frappe Charts
[htmlwidget]: https://www.htmlwidgets.org/
[frappe-charts]: https://frappe.io/charts
[rmd-book-htmlwidget]: https://bookdown.org/yihui/rmarkdown/html-widgets.html
This repository walks through the building of an [htmlwidget]
around the JavaScript library [Frappe Charts][frappe-charts].
Because there are many files and moving pieces involved in this process,
I've created a git repository that walks through the changes
at each step of the process.
With my notes about each step,
I've included the SHA linked to the updates made during the step.
If you're viewing this on GitHub,
those SHA hashes should be converted to links
that will take you to a summary of which files changed at each step.
This introduction focuses on the mechanics of htmlwidgets.
For a much more detailed summary,
the [HTML Widgets][rmd-book-htmlwidget] chapter of the R Markdown book
is an excellent introduction.
```{r setup, include=FALSE}
knitr::opts_chunk$set(eval = FALSE)
github_sha_link <- function(sha) {
glue::glue("[changelog: {substr(sha, 1, 6)}](https://github.com/gadenbuie/js4shiny-frappeCharts/commit/{sha})")
}
```
## Setup R Package
* `r github_sha_link("a3f6fdd986d2b98323b5be43e323df4f6a19f1f3")`
Create a package for this HTML widget.
We're not going to publish this, so you can call it whatever you want
```{r create-package}
usethis::create_package("frappeCharts")
```
Add a dev script for notes
```{r dev}
dir.create("dev")
file.create("dev/dev.R")
rstudioapi::navigateToFile("dev/dev.R")
```
### Add the R package dependencies for an htmlwidget package
```{r r-deps}
usethis::use_package("htmlwidgets")
usethis::use_package("htmltools")
usethis::use_package("jsonlite")
usethis::use_package("shiny")
usethis::use_package("yaml")
```
## Setup npm package
* `r github_sha_link("256f0ca112b2685608f9a17a4fb4e35d279c9830")`
Same process again, but this time for npm.
```bash
npm init
# or
npm init -y
```
Open `package.json` and take a look
```json
{
"name": "frappecharts",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}
```
From Frappe Charts [docs#installation](https://frappe.io/charts/docs#installation):
```bash
npm install frappe-charts
```
We now have a dependency in `package.json` and there's a `package-lock.json` file.
```json
"dependencies": {
"frappe-charts": "^1.3.0"
}
```
## Ignore node_modules but add package-lock
There's also a `node_modules/` folder with `frappe-charts/` inside.
Add `node_modules` to `.Rbuildignore` and `.gitignore`.
(BTW, you can and are supposed to commit `package-lock.json`.)
```{r ignore-node-module}
usethis::use_build_ignore("node_modules")
usethis::use_build_ignore("package.json")
usethis::use_build_ignore("package-lock.json")
usethis::use_git_ignore("node_modules")
```
## Scaffold the HTML widget
* `r github_sha_link("38bac2c65cf54816525076690310008e62ab99a1")`
```{r htmlwidgets-scaffold}
htmlwidgets::scaffoldWidget("frappeChart")
```
This adds files in `inst/htmlwidgets`
```
inst
└── htmlwidgets
├── frappeChart.js #<< R <-> JS code
└── frappeChart.yaml #<< list of dependencies
```
and creates a file `R/frappeChart.R` with the functions
- `frappeChart()`
- `frappeChartOutput()` (for shiny)
- `renderFrappeChart()` (for shiny)
## Use `npm` to get our dependencies in the right place
* `r github_sha_link("7abf0224345a67217c4a476f04eafe581f0ecec0")`
`htmlwidgets` load dependencies in a way that's exactly the same as using a
`
```
We need to get our dependecy into a subfolder of `inst/htmlwidgets`.
Convention is `inst/htmlwidgets/lib/`.
Rather than creating the directoy and copying over, etc.,
we can have an `npm` build script do this for us.
To avoid issues with mac/windows,
we'll add a dev dependency on [`cpy-cli`](https://github.com/sindresorhus/cpy-cli).
Dev dependencies are node modules
that are used to build a package,
rather than required for the package to work.
```bash
npm install cpy-cli --save-dev
```
Then we create the folder `frappe-charts` under `inst/htmlwidgets/lib`
that will hold the Frappe Charts JavaScript dependency.
(If the library included other required files,
we would move these too.)
```{r create-lib-dir}
dir.create("inst/htmlwidets/lib/frappe-charts", recursive = TRUE)
```
And then edit `package.json` to add a copy task.
You can define scripts that are runnable with `npm run `.
For small build tasks, this is an easy to implement build solution.
```
"scripts": {
"copy-js": "cpy 'node_modules/frappe-charts/dist/frappe-charts.min.iife*' inst/htmlwidgets/lib/frappe-charts/",
"build": "npm run copy-js"
}
```
Notice that running `npm run build` will also call `npm run copy-js`.
If we had more build tasks related to our JavaScript dependencies,
like linting or testing,
we could add them as separate scripts
and have them run in the build process with
`npm run && npm run `, etc.
## Create a demo html_document_plain()
* `r github_sha_link("036d454f80d6036fc1ba35db92161fd19c053635")`
```{r create-demo-html}
dir.create("dev/demo")
js4shiny::js4shiny_rmd(path = "dev/demo/demo.Rmd")
```
Use the example in the [Frappe Charts Docs](https://frappe.io/charts/docs).
```{r}
tagList(
div(id = "chart"),
htmltools::htmlDependency(
name = "frappe-charts",
version = "1.3.0",
package = "frappeCharts",
src = "htmlwidgets/lib/frappe-charts",
script = "frappe-charts.min.iife.js",
all_files = TRUE
)
)
```
And copy the JS into a javascript chunk.
`r emo::ji("warning")` The dependencies won't be found until you build/install.
```{r build-install}
devtools::document()
devtools::install()
```
If you get a path not found error
```
Error: path for html_dependency not found: inst/htmlwidgets/lib/frappe-charts
```
it's most likely because
```
src = "inst/htmlwidgets/lib/frappe-charts"
```
should be relative to `inst`.
## Replace the example data with another data set and example
* `r github_sha_link("8fd703a08b021b8466171b83506f5fb0bf92f2ac")`
The first demo mixes chart types and we don't want to do that.
Use the example from
[Basic Chart](https://frappe.io/charts/docs/basic/basic_chart#adding-more-datasets).
```js
const data = {
labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
datasets: [
{ name: "R", values: [18, 40, 30, 35, 8, 52, 17, -4] },
{ name: "Python", values: [30, 50, -10, 15, 18, 32, 27, 14] }
]
}
```
Then re-create this data in an R chunk (`r github_sha_link("881c12ffdbdaa017863c918f61fa6208400d6130")`:
```{r data-in-r}
data <- list(
labels = c("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"),
datasets = list(
list(name = "R", values = c(18, 40, 30, 35, 8, 52, 17, -4)),
list(name = "Python", values = c(30, 50, -10, 15, 18, 32, 27, 14))
)
)
```
To get the data out of R and make it available in the document,
`htmlwidgets` embeds the data in a ``
element in the page.
Embed the data from the R chunk in a `