-
Notifications
You must be signed in to change notification settings - Fork 37
HTMLWidget and Shiny input integration helpers #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 66 commits
961e696
438ea90
0a1ebf3
b8bb5b9
43facab
42e112e
7fcd97c
70d45a2
a519540
b49a1f4
4f416b0
8738c9b
2d8c765
eada87b
61095b2
6c22800
e177aa0
3855049
657ac05
32a9467
947ea50
f9fba29
3232e87
9922ad0
32b0bea
8b39bae
e955fbc
184e728
2b96174
8c26d12
0f4c9e6
305cbc7
5f22d0d
b5bf7d7
1525747
7b85242
2155d4b
79ca0d1
2305746
be51515
eb51153
c129486
434eecb
7a68a37
1a0e685
167779a
ad8244b
cb9eea0
b40b356
1bd4f09
2c95b6f
2fe2f84
b6d2d24
9b8236e
300823c
1812397
0102c8f
aa0c195
9cc3310
af330c9
5a80783
68c1f4d
c37c588
ab9a66d
ca7b313
c31d721
e0f62a6
1808e9d
ad28dc9
9983058
582bb4a
8174f4d
9e94c1a
5c2e639
9659531
a513900
f5fff43
e66b71d
e519b6d
ed5b725
b7f83dd
25a2d9c
02cac39
c439aa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,12 @@ | ||
| ^node_modules$ | ||
| ^.*\.Rproj$ | ||
| ^\.Rproj\.user$ | ||
| ^\build | ||
| ^CONDUCT\.md$ | ||
| ^cran-comments\.md$ | ||
| ^docs$ | ||
| ^\.travis\.yml$ | ||
| ^\assets | ||
| ^karma\.conf\.js$ | ||
| ^package\.json$ | ||
| ^yarn\.lock$ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,6 @@ | |
| .RData | ||
| .Ruserdata | ||
| inst/doc | ||
| node_modules | ||
| reactR.Rcheck | ||
| reactR_*.tar.gz | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,15 @@ | ||
| # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r | ||
|
|
||
| language: R | ||
| sudo: required | ||
| cache: packages | ||
|
|
||
| before_install: | ||
| - sudo apt-get install -y libv8-dev | ||
| matrix: | ||
| include: | ||
| # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r | ||
| - language: r | ||
| r: | ||
| - release | ||
| sudo: required | ||
| cache: packages | ||
| before_install: | ||
| - sudo apt-get install -y libv8-dev | ||
| - language: node_js | ||
| node_js: | ||
| - "11.4.0" | ||
| addons: | ||
| - chrome: stable |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| Package: reactR | ||
| Type: Package | ||
| Title: React Helpers | ||
| Version: 0.2.1 | ||
| Date: 2018-10-23 | ||
| Version: 0.3.0 | ||
| Date: 2019-01-03 | ||
| Authors@R: c( | ||
| person( | ||
| "Facebook", "Inc" | ||
|
|
@@ -14,16 +14,23 @@ Authors@R: c( | |
| , role = c("aut", "cre") | ||
| , comment = "R interface" | ||
| , email = "[email protected]" | ||
| ), | ||
| person( | ||
| "Alan", "Dipert" | ||
| , role = c("aut") | ||
| , comment = "R interface" | ||
| , email = "[email protected]" | ||
| ) | ||
| ) | ||
| Maintainer: Kent Russell <[email protected]> | ||
| Description: Make it easy to use 'React' in R with helper | ||
| dependency functions, embedded 'Babel' 'transpiler', | ||
| and examples. | ||
| URL: https://github.com/timelyportfolio/reactR | ||
| BugReports: https://github.com/timelyportfolio/reactR/issues | ||
| URL: https://github.com/react-R/reactR | ||
| BugReports: https://github.com/react-R/reactR/issues | ||
| License: MIT + file LICENSE | ||
| LazyData: TRUE | ||
| Encoding: UTF-8 | ||
| Imports: | ||
| htmltools | ||
| Suggests: | ||
|
|
@@ -32,5 +39,5 @@ Suggests: | |
| shiny, | ||
| V8, | ||
| knitr | ||
| RoxygenNote: 6.0.1 | ||
| RoxygenNote: 6.1.1 | ||
| VignetteBuilder: knitr | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,15 @@ | ||
| # Generated by roxygen2: do not edit by hand | ||
|
|
||
| S3method("$",react_component_builder) | ||
| S3method("$<-",react_component_builder) | ||
| S3method("[[",react_component_builder) | ||
| S3method("[[<-",react_component_builder) | ||
| export(React) | ||
| export(babel_transform) | ||
| export(component) | ||
| export(html_dependency_corejs) | ||
| export(html_dependency_react) | ||
| export(html_dependency_reacttools) | ||
| export(reactMarkup) | ||
| export(scaffoldReactWidget) | ||
| importFrom(htmltools,htmlDependency) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| #'@keywords internal | ||
| react_version <- function(){'16.6.0'} | ||
| react_version <- function(){'16.7.0'} | ||
| babel_version <- function(){'6.26.0'} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| isUpper <- function(s) { | ||
| grepl("^[[:upper:]]+$", s) | ||
| } | ||
|
|
||
| #' Create a React component | ||
| #' | ||
| #' @param name Name of the React component, which must start with an upper-case | ||
| #' character. | ||
| #' @param varArgs Attributes and children of the element to pass along to | ||
| #' \code{\link[htmltools]{tag}} as \code{varArgs}. | ||
| #' | ||
| #' @return An htmltools \code{\link[htmltools]{tag}} object | ||
| #' @export | ||
| #' | ||
| #' @examples | ||
| #' component("ParentComponent", | ||
| #' list( | ||
| #' x = 1, | ||
| #' y = 2, | ||
| #' component("ChildComponent"), | ||
| #' component("OtherChildComponent") | ||
| #' ) | ||
| #' ) | ||
| component <- function(name, varArgs = list()) { | ||
| if (length(name) == 0 || !isUpper(substring(name, 1, 1))) { | ||
| stop("Component name must be specified and start with an upper case character") | ||
| } | ||
| htmltools::tag(name, varArgs) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to add a class or a subclass here? As a package author, I might want to know if a user is supplying a component versus a tag |
||
|
|
||
| #' React component builder. | ||
| #' | ||
| #' \code{React} is a syntactically-convenient way to create instances of React | ||
| #' components that can be sent to the browser for display. It is a list for | ||
| #' which \link[=InternalMethods]{extract methods} are defined, allowing | ||
| #' object creation syntax like \code{React$MyComponent(x = 1)} where | ||
| #' \code{MyComponent} is a React component you have exposed to Shiny in | ||
| #' JavaScript. | ||
| #' | ||
| #' Internally, the \code{\link{component}} function is used to create the | ||
| #' component instance. | ||
| #' | ||
| #' @examples | ||
| #' # Create an instance of ParentComponent with two children, | ||
| #' # ChildComponent and OtherChildComponent. | ||
| #' React$ParentComponent( | ||
| #' x = 1, | ||
| #' y = 2, | ||
| #' React$ChildComponent(), | ||
| #' React$OtherChildComponent() | ||
| #' ) | ||
| #' @export | ||
| React <- structure( | ||
| list(), | ||
| class = "react_component_builder" | ||
| ) | ||
|
|
||
| #' @export | ||
| `$.react_component_builder` <- function(x, name) { | ||
| function(...) { | ||
| component(name, list(...)) | ||
| } | ||
| } | ||
|
|
||
| #' @export | ||
| `[[.react_component_builder` <- `$.react_component_builder` | ||
|
|
||
| #' @export | ||
| `$<-.react_component_builder` <- function(x, name, value) { | ||
| stop("Assigning to a component constructor is not allowed") | ||
| } | ||
|
|
||
| #' @export | ||
| `[[<-.react_component_builder` <- `$<-.react_component_builder` | ||
|
|
||
| #' Prepare data that represents a single-element character vector, a React | ||
| #' component, or an htmltools tag for sending to the client. | ||
| #' | ||
| #' @param tag character vector or React component or | ||
| #' \code{\link[htmltools]{tag}} | ||
| #' | ||
| #' @return | ||
| #' @export | ||
| #' | ||
| #' @examples | ||
| reactMarkup <- function(tag) { | ||
| stopifnot(class(tag) == "shiny.tag" | ||
| || (is.character(tag) && length(tag) == 1)) | ||
| list(tag = tag, class = "reactR.markup") | ||
|
||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| #' Create implementation scaffolding for a React.js-based HTML widget | ||
| #' | ||
| #' Add the minimal code required to implement a React.js-based HTML widget to an | ||
| #' R package. | ||
| #' | ||
| #' @param name Name of widget | ||
| #' @param npmPkg Optional \href{https://npmjs.com/}{NPM} package upon which this | ||
| #' widget is based, as a two-element character vector of name and | ||
| #' \href{https://docs.npmjs.com/files/package.json#dependencies}{version | ||
| #' range}. If you specify this parameter the package will be added to the | ||
| #' \code{dependency} section of the generated \code{package.json}. | ||
| #' @param edit Automatically open the widget's JavaScript source file after | ||
| #' creating the scaffolding. | ||
| #' | ||
| #' @note This function must be executed from the root directory of the package | ||
| #' you wish to add the widget to. | ||
| #' | ||
| #' @export | ||
| scaffoldReactWidget <- function(name, npmPkg = NULL, edit = interactive()){ | ||
| if (!file.exists('DESCRIPTION')){ | ||
| stop( | ||
| "You need to create a package to house your widget first!", | ||
| call. = F | ||
| ) | ||
| } | ||
| if (!file.exists('inst')){ | ||
| dir.create('inst') | ||
| } | ||
| package <- read.dcf('DESCRIPTION')[[1,"Package"]] | ||
| addWidgetConstructor(name, package, edit) | ||
| addWidgetYAML(name, edit) | ||
| addPackageJSON(toDepJSON(npmPkg)) | ||
| addWebpackConfig(name) | ||
| addWidgetJS(name, edit) | ||
| addExampleApp(name) | ||
| message("To install dependencies from npm run: yarn install") | ||
| message("To build JavaScript run: yarn run webpack --mode=development") | ||
| } | ||
|
|
||
| toDepJSON <- function(npmPkg) { | ||
| if (is.null(npmPkg)) { | ||
| "" | ||
| } else { | ||
| do.call(sprintf, as.list(c('"%s": "%s"', npmPkg))) | ||
| } | ||
| } | ||
|
|
||
| slurp <- function(file) { | ||
| paste(readLines( | ||
| system.file(file, package = 'reactR') | ||
| ), collapse = "\n") | ||
| } | ||
|
|
||
| # Perform a series of pattern replacements on str. | ||
| # Example: renderTemplate("foo ${x} bar ${y} baz ${x}", list(x = 1, y = 2)) | ||
| # Produces: "foo 1 bar 2 baz 1" | ||
| renderTemplate <- function(str, substitutions) { | ||
| Reduce(function(str, name) { | ||
| gsub(paste0("\\$\\{", name, "\\}"), substitutions[[name]], str) | ||
| }, names(substitutions), str) | ||
| } | ||
|
|
||
| capName = function(name){ | ||
| paste0(toupper(substring(name, 1, 1)), substring(name, 2)) | ||
| } | ||
|
|
||
| addWidgetConstructor <- function(name, package, edit){ | ||
| tpl <- slurp('templates/widget_r.txt') | ||
| if (!file.exists(file_ <- sprintf("R/%s.R", name))){ | ||
| cat( | ||
| renderTemplate(tpl, list(name = name, package = package, capName = capName(name))), | ||
| file = file_ | ||
| ) | ||
| message('Created boilerplate for widget constructor ', file_) | ||
| } else { | ||
| message(file_, " already exists") | ||
| } | ||
| if (edit) fileEdit(file_) | ||
| } | ||
|
|
||
| addWidgetYAML <- function(name, edit){ | ||
| tpl <- "# (uncomment to add a dependency) | ||
| # dependencies: | ||
| # - name: | ||
| # version: | ||
| # src: | ||
| # script: | ||
| # stylesheet: | ||
| " | ||
| if (!file.exists('inst/htmlwidgets')){ | ||
| dir.create('inst/htmlwidgets') | ||
| } | ||
| if (!file.exists(file_ <- sprintf('inst/htmlwidgets/%s.yaml', name))){ | ||
| cat(tpl, file = file_) | ||
| message('Created boilerplate for widget dependencies at ', | ||
| sprintf('inst/htmlwidgets/%s.yaml', name) | ||
| ) | ||
| } else { | ||
| message(file_, " already exists") | ||
| } | ||
| if (edit) fileEdit(file_) | ||
| } | ||
|
|
||
| addPackageJSON <- function(npmPkg) { | ||
| tpl <- renderTemplate(slurp('templates/widget_package.json.txt'), list(npmPkg = npmPkg)) | ||
| if (!file.exists('package.json')) { | ||
| cat(tpl, file = 'package.json') | ||
| message('Created package.json') | ||
| } else { | ||
| message("package.json already exists") | ||
| } | ||
| } | ||
|
|
||
| addWebpackConfig <- function(name) { | ||
| tpl <- renderTemplate(slurp('templates/widget_webpack.config.js.txt'), list(name = name)) | ||
| if (!file.exists('webpack.config.js')) { | ||
| cat(tpl, file = 'webpack.config.js') | ||
| message('Created webpack.config.js') | ||
| } else { | ||
| message("webpack.config.js already exists") | ||
| } | ||
| } | ||
|
|
||
| addWidgetJS <- function(name, edit){ | ||
| tpl <- paste(readLines( | ||
| system.file('templates/widget_js.txt', package = 'reactR') | ||
| ), collapse = "\n") | ||
| if (!file.exists('srcjs')){ | ||
| dir.create('srcjs') | ||
| } | ||
| if (!file.exists(file_ <- sprintf('srcjs/%s.js', name))){ | ||
| cat(renderTemplate(tpl, list(name = name)), file = file_) | ||
| message('Created boilerplate for widget javascript bindings at ', | ||
| sprintf('srcjs/%s.js', name) | ||
| ) | ||
| } else { | ||
| message(file_, " already exists") | ||
| } | ||
| if (edit) fileEdit(file_) | ||
| } | ||
|
|
||
| addExampleApp <- function(name) { | ||
| tpl <- renderTemplate(slurp('templates/widget_app.R.txt'), list(name = name, capName = capName(name))) | ||
| if (!file.exists('app.R')) { | ||
| cat(tpl, file = 'app.R') | ||
| message('Created example app.R') | ||
| } else { | ||
| message("app.R already exists") | ||
| } | ||
| } | ||
|
|
||
| # invoke file.edit in a way that will bind to the RStudio editor | ||
| # when running inside RStudio | ||
| fileEdit <- function(file) { | ||
| fileEditFunc <- eval(parse(text = "file.edit"), envir = globalenv()) | ||
| fileEditFunc(file) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.