Skip to content

Commit f03c135

Browse files
Merge pull request #4 from react-R/enhancements
HTMLWidget and Shiny input integration helpers
2 parents c330f9c + c439aa9 commit f03c135

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+8407
-301
lines changed

.Rbuildignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
^node_modules$
12
^.*\.Rproj$
23
^\.Rproj\.user$
34
^\build
45
^CONDUCT\.md$
56
^cran-comments\.md$
67
^docs$
78
^\.travis\.yml$
9+
^\assets
10+
^karma\.conf\.js$
11+
^package\.json$
12+
^yarn\.lock$

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
.RData
44
.Ruserdata
55
inst/doc
6+
node_modules
7+
reactR.Rcheck
8+
reactR_*.tar.gz

.travis.yml

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r
2-
3-
language: R
4-
sudo: required
5-
cache: packages
6-
7-
before_install:
8-
- sudo apt-get install -y libv8-dev
1+
matrix:
2+
include:
3+
# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r
4+
- language: r
5+
r:
6+
- release
7+
sudo: required
8+
cache: packages
9+
before_install:
10+
- sudo apt-get install -y libv8-dev
11+
- language: node_js
12+
node_js:
13+
- "11.4.0"
14+
addons:
15+
- chrome: stable

DESCRIPTION

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Package: reactR
22
Type: Package
33
Title: React Helpers
4-
Version: 0.2.1
5-
Date: 2018-10-23
4+
Version: 0.3.0
5+
Date: 2019-01-11
66
Authors@R: c(
77
person(
88
"Facebook", "Inc"
@@ -14,16 +14,23 @@ Authors@R: c(
1414
, role = c("aut", "cre")
1515
, comment = "R interface"
1616
, email = "[email protected]"
17+
),
18+
person(
19+
"Alan", "Dipert"
20+
, role = c("aut")
21+
, comment = "R interface"
22+
, email = "[email protected]"
1723
)
1824
)
1925
Maintainer: Kent Russell <[email protected]>
20-
Description: Make it easy to use 'React' in R with helper
21-
dependency functions, embedded 'Babel' 'transpiler',
26+
Description: Make it easy to use 'React' in R with 'htmlwidget' scaffolds,
27+
helper dependency functions, an embedded 'Babel' 'transpiler',
2228
and examples.
23-
URL: https://github.com/timelyportfolio/reactR
24-
BugReports: https://github.com/timelyportfolio/reactR/issues
29+
URL: https://github.com/react-R/reactR
30+
BugReports: https://github.com/react-R/reactR/issues
2531
License: MIT + file LICENSE
2632
LazyData: TRUE
33+
Encoding: UTF-8
2734
Imports:
2835
htmltools
2936
Suggests:
@@ -32,5 +39,5 @@ Suggests:
3239
shiny,
3340
V8,
3441
knitr
35-
RoxygenNote: 6.0.1
42+
RoxygenNote: 6.1.1
3643
VignetteBuilder: knitr

NAMESPACE

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
S3method("$",react_component_builder)
4+
S3method("$<-",react_component_builder)
5+
S3method("[[",react_component_builder)
6+
S3method("[[<-",react_component_builder)
7+
export(React)
38
export(babel_transform)
9+
export(component)
410
export(html_dependency_corejs)
511
export(html_dependency_react)
12+
export(html_dependency_reacttools)
13+
export(reactMarkup)
14+
export(scaffoldReactWidget)
615
importFrom(htmltools,htmlDependency)

R/dependencies.R

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,18 @@ html_dependency_corejs <- function() {
7070
script = "shim.min.js"
7171
)
7272
}
73+
74+
#' Adds window.reactR.exposeComponents and window.reactR.hydrate
75+
#'
76+
#' @return \code{\link[htmltools]{htmlDependency}}
77+
#' @importFrom htmltools htmlDependency
78+
#' @export
79+
html_dependency_reacttools <- function(){
80+
htmltools::htmlDependency(
81+
name = "reactwidget",
82+
src = "www/react-tools",
83+
version = "1.0.0",
84+
package = "reactR",
85+
script = c("react-tools.js")
86+
)
87+
}

R/meta.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#'@keywords internal
2-
react_version <- function(){'16.6.0'}
2+
react_version <- function(){'16.7.0'}
33
babel_version <- function(){'6.26.0'}

R/reacttools.R

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
isUpper <- function(s) {
2+
grepl("^[[:upper:]]+$", s)
3+
}
4+
5+
#' Create a React component
6+
#'
7+
#' @param name Name of the React component, which must start with an upper-case
8+
#' character.
9+
#' @param varArgs Attributes and children of the element to pass along to
10+
#' \code{\link[htmltools]{tag}} as \code{varArgs}.
11+
#'
12+
#' @return An htmltools \code{\link[htmltools]{tag}} object
13+
#' @export
14+
#'
15+
#' @examples
16+
#' component("ParentComponent",
17+
#' list(
18+
#' x = 1,
19+
#' y = 2,
20+
#' component("ChildComponent"),
21+
#' component("OtherChildComponent")
22+
#' )
23+
#' )
24+
component <- function(name, varArgs = list()) {
25+
if (length(name) == 0 || !isUpper(substring(name, 1, 1))) {
26+
stop("Component name must be specified and start with an upper case character")
27+
}
28+
component <- htmltools::tag(name, varArgs)
29+
structure(component, class = c("reactR_component", oldClass(component)))
30+
}
31+
32+
#' React component builder.
33+
#'
34+
#' \code{React} is a syntactically-convenient way to create instances of React
35+
#' components that can be sent to the browser for display. It is a list for
36+
#' which \link[=InternalMethods]{extract methods} are defined, allowing
37+
#' object creation syntax like \code{React$MyComponent(x = 1)} where
38+
#' \code{MyComponent} is a React component you have exposed to Shiny in
39+
#' JavaScript.
40+
#'
41+
#' Internally, the \code{\link{component}} function is used to create the
42+
#' component instance.
43+
#'
44+
#' @examples
45+
#' # Create an instance of ParentComponent with two children,
46+
#' # ChildComponent and OtherChildComponent.
47+
#' React$ParentComponent(
48+
#' x = 1,
49+
#' y = 2,
50+
#' React$ChildComponent(),
51+
#' React$OtherChildComponent()
52+
#' )
53+
#' @export
54+
React <- structure(
55+
list(),
56+
class = "react_component_builder"
57+
)
58+
59+
#' @export
60+
`$.react_component_builder` <- function(x, name) {
61+
function(...) {
62+
component(name, list(...))
63+
}
64+
}
65+
66+
#' @export
67+
`[[.react_component_builder` <- `$.react_component_builder`
68+
69+
#' @export
70+
`$<-.react_component_builder` <- function(x, name, value) {
71+
stop("Assigning to a component constructor is not allowed")
72+
}
73+
74+
#' @export
75+
`[[<-.react_component_builder` <- `$<-.react_component_builder`
76+
77+
#' Prepare data that represents a single-element character vector, a React
78+
#' component, or an htmltools tag for sending to the client.
79+
#'
80+
#' Tag lists as returned by \code{\link[htmltools]{tagList}} are not currently
81+
#' supported.
82+
#'
83+
#' @param tag character vector or React component or
84+
#' \code{\link[htmltools]{tag}}
85+
#'
86+
#' @return A reactR markup object suitable for being passed to
87+
#' \code{\link[htmlwidgets]{createWidget}} as widget instance data.
88+
#' @export
89+
reactMarkup <- function(tag) {
90+
stopifnot(inherits(tag, "shiny.tag")
91+
|| (is.character(tag) && length(tag) == 1))
92+
list(tag = tag, class = "reactR_markup")
93+
}
94+

R/scaffold.R

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#' Create implementation scaffolding for a React.js-based HTML widget
2+
#'
3+
#' Add the minimal code required to implement a React.js-based HTML widget to an
4+
#' R package.
5+
#'
6+
#' @param name Name of widget
7+
#' @param npmPkg Optional \href{https://npmjs.com/}{NPM} package upon which this
8+
#' widget is based, a named list with two elements: \code{name} and
9+
#' \href{https://docs.npmjs.com/files/package.json#dependencies}{version}. If
10+
#' you specify this parameter the package will be added to the
11+
#' \code{dependency} section of the generated \code{package.json}.
12+
#' @param edit Automatically open the widget's JavaScript source file after
13+
#' creating the scaffolding.
14+
#'
15+
#' @note This function must be executed from the root directory of the package
16+
#' you wish to add the widget to.
17+
#'
18+
#' @export
19+
scaffoldReactWidget <- function(name, npmPkg = NULL, edit = interactive()){
20+
if (!file.exists('DESCRIPTION')){
21+
stop(
22+
"You need to create a package to house your widget first!",
23+
call. = F
24+
)
25+
}
26+
if (!file.exists('inst')){
27+
dir.create('inst')
28+
}
29+
package <- read.dcf('DESCRIPTION')[[1,"Package"]]
30+
addWidgetConstructor(name, package, edit)
31+
addWidgetYAML(name, edit)
32+
addPackageJSON(toDepJSON(npmPkg))
33+
addWebpackConfig(name)
34+
addWidgetJS(name, edit)
35+
addExampleApp(name)
36+
message("To install dependencies from npm run: yarn install")
37+
message("To build JavaScript run: yarn run webpack --mode=development")
38+
}
39+
40+
toDepJSON <- function(npmPkg) {
41+
if (is.null(npmPkg)) {
42+
""
43+
} else {
44+
sprintf('"%s": "%s"', npmPkg$name, npmPkg$version)
45+
}
46+
}
47+
48+
slurp <- function(file) {
49+
paste(readLines(
50+
system.file(file, package = 'reactR')
51+
), collapse = "\n")
52+
}
53+
54+
# Perform a series of pattern replacements on str.
55+
# Example: renderTemplate("foo ${x} bar ${y} baz ${x}", list(x = 1, y = 2))
56+
# Produces: "foo 1 bar 2 baz 1"
57+
renderTemplate <- function(str, substitutions) {
58+
Reduce(function(str, name) {
59+
gsub(paste0("\\$\\{", name, "\\}"), substitutions[[name]], str)
60+
}, names(substitutions), str)
61+
}
62+
63+
capName = function(name){
64+
paste0(toupper(substring(name, 1, 1)), substring(name, 2))
65+
}
66+
67+
addWidgetConstructor <- function(name, package, edit){
68+
tpl <- slurp('templates/widget_r.txt')
69+
if (!file.exists(file_ <- sprintf("R/%s.R", name))){
70+
cat(
71+
renderTemplate(tpl, list(name = name, package = package, capName = capName(name))),
72+
file = file_
73+
)
74+
message('Created boilerplate for widget constructor ', file_)
75+
} else {
76+
message(file_, " already exists")
77+
}
78+
if (edit) fileEdit(file_)
79+
}
80+
81+
addWidgetYAML <- function(name, edit){
82+
tpl <- "# (uncomment to add a dependency)
83+
# dependencies:
84+
# - name:
85+
# version:
86+
# src:
87+
# script:
88+
# stylesheet:
89+
"
90+
if (!file.exists('inst/htmlwidgets')){
91+
dir.create('inst/htmlwidgets')
92+
}
93+
if (!file.exists(file_ <- sprintf('inst/htmlwidgets/%s.yaml', name))){
94+
cat(tpl, file = file_)
95+
message('Created boilerplate for widget dependencies at ',
96+
sprintf('inst/htmlwidgets/%s.yaml', name)
97+
)
98+
} else {
99+
message(file_, " already exists")
100+
}
101+
if (edit) fileEdit(file_)
102+
}
103+
104+
addPackageJSON <- function(npmPkg) {
105+
tpl <- renderTemplate(slurp('templates/widget_package.json.txt'), list(npmPkg = npmPkg))
106+
if (!file.exists('package.json')) {
107+
cat(tpl, file = 'package.json')
108+
message('Created package.json')
109+
} else {
110+
message("package.json already exists")
111+
}
112+
}
113+
114+
addWebpackConfig <- function(name) {
115+
tpl <- renderTemplate(slurp('templates/widget_webpack.config.js.txt'), list(name = name))
116+
if (!file.exists('webpack.config.js')) {
117+
cat(tpl, file = 'webpack.config.js')
118+
message('Created webpack.config.js')
119+
} else {
120+
message("webpack.config.js already exists")
121+
}
122+
}
123+
124+
addWidgetJS <- function(name, edit){
125+
tpl <- paste(readLines(
126+
system.file('templates/widget_js.txt', package = 'reactR')
127+
), collapse = "\n")
128+
if (!file.exists('srcjs')){
129+
dir.create('srcjs')
130+
}
131+
if (!file.exists(file_ <- sprintf('srcjs/%s.js', name))){
132+
cat(renderTemplate(tpl, list(name = name)), file = file_)
133+
message('Created boilerplate for widget javascript bindings at ',
134+
sprintf('srcjs/%s.js', name)
135+
)
136+
} else {
137+
message(file_, " already exists")
138+
}
139+
if (edit) fileEdit(file_)
140+
}
141+
142+
addExampleApp <- function(name) {
143+
tpl <- renderTemplate(slurp('templates/widget_app.R.txt'), list(name = name, capName = capName(name)))
144+
if (!file.exists('app.R')) {
145+
cat(tpl, file = 'app.R')
146+
message('Created example app.R')
147+
} else {
148+
message("app.R already exists")
149+
}
150+
}
151+
152+
# invoke file.edit in a way that will bind to the RStudio editor
153+
# when running inside RStudio
154+
fileEdit <- function(file) {
155+
fileEditFunc <- eval(parse(text = "file.edit"), envir = globalenv())
156+
fileEditFunc(file)
157+
}

0 commit comments

Comments
 (0)