Skip to content

Commit e955fbc

Browse files
Merge branch 'enhancements' of https://github.com/react-R/reactR into enhancements
2 parents 8b39bae + 32b0bea commit e955fbc

29 files changed

+347
-7061
lines changed

DESCRIPTION

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ Authors@R: c(
1414
, role = c("aut", "cre")
1515
, comment = "R interface"
1616
, email = "kent.russell@timelyportfolio.com"
17+
),
18+
person(
19+
"Alan", "Dipert"
20+
, role = c("aut")
21+
, comment = "R interface"
22+
, email = "alan@rstudio.com"
1723
)
1824
)
1925
Maintainer: Kent Russell <kent.russell@timelyportfolio.com>

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export(html_dependency_corejs)
1111
export(html_dependency_react)
1212
export(html_dependency_reacttools)
1313
export(reactData)
14+
export(scaffoldReactWidget)
1415
importFrom(htmltools,htmlDependency)

R/reacttools.R

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,37 @@ isUpper <- function(s) {
22
grepl("^[[:upper:]]+$", s)
33
}
44

5+
#' React component builder functions
6+
#'
7+
#' Functions for creating React component, HTML, and SVG trees to send to the
8+
#' browser for rendering.
9+
#'
10+
#' The \code{\link{component}} function creates a representation of a React
11+
#' component instance to send to the browser for rendering. It is analagous to
12+
#' \code{\link[htmltools]{tag}}.
13+
#'
14+
#' The \code{\link{React}} list is a special object that supports
15+
#' \link[=InternalMethods]{extraction} syntax for creating React components.
16+
#'
17+
#' Once a component or tag has been created in R, it must be passed to
18+
#' \code\{\link{reactData}} before being sent to the browser. In the case of
19+
#' htmlwidgets, the return value of \code{reactData} should be passed as the
20+
#' \code{x} argument of \code{\link{htmlwidgets::createWidget}}.
21+
#'
22+
#' Any React components named by \code{component} or \code{React} must have been
23+
#' installed on the client using \code{reactR.reactWidget}. Alternatively, the
24+
#' JSON representing the tag can be converted to a React component tree with
25+
#' \code{reactR.hydrate}.
26+
#'
27+
#' @name builder
28+
NULL
29+
530
#' Create a React component
631
#'
7-
#' @param name Name of the React component, which must start with an upper-case character.
8-
#' @param ... Attributes and children of the element to pass along to \code{\link[htmltools]{tag}} as varArgs.
32+
#' @param name Name of the React component, which must start with an upper-case
33+
#' character.
34+
#' @param varArgs Attributes and children of the element to pass along to
35+
#' \code{\link[htmltools]{tag}} as \code{varArgs}.
936
#'
1037
#' @return An htmltools \code{\link[htmltools]{tag}} object
1138
#' @export
@@ -26,8 +53,32 @@ component <- function(name, varArgs = list()) {
2653
htmltools::tag(name, varArgs)
2754
}
2855

56+
#' React component builder.
57+
#'
58+
#' \code{React} is a syntactically-convenient way to create instances of React
59+
#' components that can be sent to the browser for display. It is a list for
60+
#' which \link[=InternalMethods]{extract methods} are defined, allowing
61+
#' object creation syntax like \code{React$MyComponent(x = 1)} where
62+
#' \code{MyComponent} is a React component you have exposed to Shiny in
63+
#' JavaScript.
64+
#'
65+
#' Internally, the \code{\link{component}} function is used to create the
66+
#' component instance.
67+
#'
68+
#' @examples
69+
#' # Create an instance of ParentComponent with two children,
70+
#' # ChildComponent and OtherChildComponent.
71+
#' React$ParentComponent(
72+
#' x = 1,
73+
#' y = 2,
74+
#' React$ChildComponent(),
75+
#' React$OtherChildComponent()
76+
#' )
2977
#' @export
30-
React <- structure(list(), class = "react_component_builder")
78+
React <- structure(
79+
list(),
80+
class = "react_component_builder"
81+
)
3182

3283
#' @export
3384
`$.react_component_builder` <- function(x, name) {
@@ -47,9 +98,9 @@ React <- structure(list(), class = "react_component_builder")
4798
#' @export
4899
`[[<-.react_component_builder` <- `$<-.react_component_builder`
49100

50-
#' Create a data object for transporting a React component to the client.
101+
#' Create a data object for sending a React component to the client.
51102
#'
52-
#' @param tag
103+
#' @param tag React component or \code{\link[htmltools]{tag}}
53104
#'
54105
#' @return
55106
#' @export

R/scaffold.R

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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, as a two-element character vector of name and
9+
#' \href{https://docs.npmjs.com/files/package.json#dependencies}{version
10+
#' range}. If 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+
}
36+
37+
toDepJSON <- function(npmPkg) {
38+
if (is.null(npmPkg)) {
39+
""
40+
} else {
41+
do.call(sprintf, as.list(c('"%s": "%s"', npmPkg)))
42+
}
43+
}
44+
45+
slurp <- function(file) {
46+
paste(readLines(
47+
system.file(file, package = 'reactR')
48+
), collapse = "\n")
49+
}
50+
51+
addWidgetConstructor <- function(name, package, edit){
52+
tpl <- slurp('templates/widget_r.txt')
53+
54+
capName = function(name){
55+
paste0(toupper(substring(name, 1, 1)), substring(name, 2))
56+
}
57+
if (!file.exists(file_ <- sprintf("R/%s.R", name))){
58+
cat(
59+
sprintf(tpl, name, name, package, name, name, name, name, name, name, package, name, capName(name), name, name, name),
60+
file = file_
61+
)
62+
message('Created boilerplate for widget constructor ', file_)
63+
} else {
64+
message(file_, " already exists")
65+
}
66+
if (edit) fileEdit(file_)
67+
}
68+
69+
addWidgetYAML <- function(name, edit){
70+
tpl <- "# (uncomment to add a dependency)
71+
# dependencies:
72+
# - name:
73+
# version:
74+
# src:
75+
# script:
76+
# stylesheet:
77+
"
78+
if (!file.exists('inst/htmlwidgets')){
79+
dir.create('inst/htmlwidgets')
80+
}
81+
if (!file.exists(file_ <- sprintf('inst/htmlwidgets/%s.yaml', name))){
82+
cat(tpl, file = file_)
83+
message('Created boilerplate for widget dependencies at ',
84+
sprintf('inst/htmlwidgets/%s.yaml', name)
85+
)
86+
} else {
87+
message(file_, " already exists")
88+
}
89+
if (edit) fileEdit(file_)
90+
}
91+
92+
addPackageJSON <- function(npmPkg) {
93+
tpl <- sprintf(slurp('templates/widget_package.json.txt'), npmPkg)
94+
if (!file.exists('package.json')) {
95+
cat(tpl, file = 'package.json')
96+
} else {
97+
message("package.json already exists")
98+
}
99+
}
100+
101+
addWebpackConfig <- function(name) {
102+
tpl <- sprintf(slurp('templates/widget_webpack.config.js.txt'), name, name)
103+
if (!file.exists('webpack.config.js')) {
104+
cat(tpl, file = 'webpack.config.js')
105+
} else {
106+
message("webpack.config.js already exists")
107+
}
108+
}
109+
110+
addWidgetJS <- function(name, edit){
111+
tpl <- paste(readLines(
112+
system.file('templates/widget_js.txt', package = 'reactR')
113+
), collapse = "\n")
114+
if (!file.exists('srcjs')){
115+
dir.create('srcjs')
116+
}
117+
if (!file.exists(file_ <- sprintf('srcjs/%s.js', name))){
118+
cat(sprintf(tpl, name), file = file_)
119+
message('Created boilerplate for widget javascript bindings at ',
120+
sprintf('srcjs/%s.js', name)
121+
)
122+
} else {
123+
message(file_, " already exists")
124+
}
125+
if (edit) fileEdit(file_)
126+
}
127+
128+
# invoke file.edit in a way that will bind to the RStudio editor
129+
# when running inside RStudio
130+
fileEdit <- function(file) {
131+
fileEditFunc <- eval(parse(text = "file.edit"), envir = globalenv())
132+
fileEditFunc(file)
133+
}

examples/sparklineswidget/.Rbuildignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

examples/sparklineswidget/.gitignore

Lines changed: 0 additions & 5 deletions
This file was deleted.

examples/sparklineswidget/.vscode/tasks.json

Lines changed: 0 additions & 16 deletions
This file was deleted.

examples/sparklineswidget/DESCRIPTION

Lines changed: 0 additions & 12 deletions
This file was deleted.

examples/sparklineswidget/NAMESPACE

Lines changed: 0 additions & 9 deletions
This file was deleted.

examples/sparklineswidget/R/sparklineswidget.R

Lines changed: 0 additions & 70 deletions
This file was deleted.

0 commit comments

Comments
 (0)