diff --git a/.Rbuildignore b/.Rbuildignore
index 0b98076..59f2ef8 100644
--- a/.Rbuildignore
+++ b/.Rbuildignore
@@ -4,16 +4,16 @@
^pkgdown$
^assets$
^.*\.Rproj$
-^\.Rproj\.user$
-^\build
^CONDUCT\.md$
-^cran-comments\.md$
-^docs$
+^README\.Rmd$
+^\.Rproj\.user$
^\.travis\.yml$
-^\assets
+^build$
+^docs$
+^cran-comments\.md$
^karma\.conf\.js$
+^logo.svg$
^package\.json$
+^webpack\.config\.js$
^yarn\.lock$
-^README\.Rmd$
-logo.svg
-webpack.config.js
+^logo\.svg$
diff --git a/.gitignore b/.gitignore
index 63e58fe..569985b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+Meta
+doc
.Rproj.user
.Rhistory
.RData
@@ -7,3 +9,4 @@ node_modules
reactR.Rcheck
reactR_*.tar.gz
*.swp
+.DS_Store
diff --git a/DESCRIPTION b/DESCRIPTION
index 2672e03..9417f93 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,8 +1,8 @@
Package: reactR
Type: Package
Title: React Helpers
-Version: 0.3.1
-Date: 2019-01-26
+Version: 0.4.0
+Date: 2019-04-10
Authors@R: c(
person(
"Facebook", "Inc"
@@ -39,6 +39,7 @@ Suggests:
shiny,
V8,
knitr,
- usethis
+ usethis,
+ jsonlite
RoxygenNote: 6.1.1
VignetteBuilder: knitr
diff --git a/NAMESPACE b/NAMESPACE
index 14a93f4..a996be6 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -7,9 +7,11 @@ S3method("[[<-",react_component_builder)
export(React)
export(babel_transform)
export(component)
+export(createReactShinyInput)
export(html_dependency_corejs)
export(html_dependency_react)
export(html_dependency_reacttools)
export(reactMarkup)
+export(scaffoldReactShinyInput)
export(scaffoldReactWidget)
importFrom(htmltools,htmlDependency)
diff --git a/R/reacttools.R b/R/reacttools.R
index b75a94c..b6dfbaa 100644
--- a/R/reacttools.R
+++ b/R/reacttools.R
@@ -1,3 +1,9 @@
+# A robust name string is a valid
+# - CSS class
+# - JavaScript variable name
+# - R variable name
+robustName <- "^[[:alpha:]_][[:alnum:]_]*$"
+
isUpper <- function(s) {
grepl("^[[:upper:]]+$", s)
}
@@ -92,3 +98,57 @@ reactMarkup <- function(tag) {
list(tag = tag, class = "reactR_markup")
}
+#' Create a React-based input
+#'
+#' @param inputId The \code{input} slot that will be used to access the value.
+#' @param class Space-delimited list of CSS class names that should identify
+#' this input type in the browser.
+#' @param dependencies HTML dependencies to include in addition to those
+#' supporting React. Must contain at least one dependency, that of the input's
+#' implementation.
+#' @param default Initial value.
+#' @param configuration Static configuration data.
+#' @param container Function to generate an HTML element to contain the input.
+#'
+#' @return Shiny input suitable for inclusion in a UI.
+#' @export
+#'
+#' @examples
+#' myInput <- function(inputId, default = "") {
+#' # The value of createReactShinyInput should be returned from input constructor functions.
+#' createReactShinyInput(
+#' inputId,
+#' "myinput",
+#' # At least one htmlDependency must be provided -- the JavaScript implementation of the input.
+#' htmlDependency(
+#' name = "my-input",
+#' version = "1.0.0",
+#' src = "www/mypackage/myinput",
+#' package = "mypackage",
+#' script = "myinput.js"
+#' ),
+#' default
+#' )
+#' }
+createReactShinyInput <- function(inputId,
+ class,
+ dependencies,
+ default = NULL,
+ configuration = list(),
+ container = htmltools::tags$div) {
+ if(length(dependencies) < 1) stop("Must include at least one HTML dependency.")
+ value <- shiny::restoreInput(id = inputId, default = default)
+ htmltools::tagList(
+ html_dependency_corejs(),
+ html_dependency_react(),
+ html_dependency_reacttools(),
+ container(id = inputId, class = class),
+ htmltools::tags$script(id = sprintf("%s_value", inputId),
+ type = "application/json",
+ jsonlite::toJSON(value, auto_unbox = TRUE)),
+ htmltools::tags$script(id = sprintf("%s_configuration", inputId),
+ type = "application/json",
+ jsonlite::toJSON(configuration, auto_unbox = TRUE)),
+ dependencies
+ )
+}
diff --git a/R/scaffold.R b/R/scaffold.R
deleted file mode 100644
index 1a98bcf..0000000
--- a/R/scaffold.R
+++ /dev/null
@@ -1,163 +0,0 @@
-#' 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 npmPkgs Optional \href{https://npmjs.com/}{NPM} packages upon which
-#' this widget is based which will be used to populate \code{package.json}.
-#' Should be a named list of names to
-#' \href{https://docs.npmjs.com/files/package.json#dependencies}{versions}.
-#' @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, npmPkgs = 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(npmPkgs))
- addWebpackConfig(name)
- addWidgetJS(name, edit)
- addExampleApp(name)
-
- usethis::use_build_ignore(c("node_modules", "srcjs"))
- usethis::use_git_ignore(c("node_modules"))
- lapply(c("htmltools", "htmlwidgets", "reactR"), usethis::use_package)
-
- message("To install dependencies from npm run: yarn install")
- message("To build JavaScript run: yarn run webpack --mode=development")
-}
-
-toDepJSON <- function(npmPkgs) {
- if (is.null(npmPkgs)) {
- ""
- } else if (!length(names(npmPkgs))) {
- stop("Must specify npm package names in the names attributes of npmPkgs")
- } else {
- paste0(sprintf('"%s": "%s"', names(npmPkgs), npmPkgs), collapse = ",\n")
- }
-}
-
-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(npmPkgs) {
- tpl <- renderTemplate(slurp('templates/widget_package.json.txt'), list(npmPkgs = npmPkgs))
- 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)
-}
diff --git a/R/scaffold_input.R b/R/scaffold_input.R
new file mode 100644
index 0000000..fc9df6a
--- /dev/null
+++ b/R/scaffold_input.R
@@ -0,0 +1,78 @@
+#' Create implementation scaffolding for a React.js-based Shiny input.
+#'
+#' Add the minimal code required to implement a React.js-based Shiny input to an
+#' R package.
+#'
+#' @param name Name of input
+#' @param npmPkgs Optional \href{https://npmjs.com/}{NPM} packages upon which
+#' this input is based which will be used to populate \code{package.json}.
+#' Should be a named list of names to
+#' \href{https://docs.npmjs.com/files/package.json#dependencies}{versions}.
+#' @param edit Automatically open the input's source files after creating the
+#' scaffolding.
+#'
+#' @note This function must be executed from the root directory of the package
+#' you wish to add the input to.
+#'
+#' @export
+scaffoldReactShinyInput <- function(name, npmPkgs = NULL, edit = interactive()) {
+ assertNameValid(name)
+ package <- getPackage()
+
+ file <- renderFile(
+ sprintf("R/%s.R", name),
+ "templates/input_r.txt",
+ "boilerplate for input constructor",
+ list(
+ name = name,
+ capName = capitalize(name),
+ package = package
+ )
+ )
+ if (edit) fileEdit(file)
+
+ renderFile(
+ 'package.json',
+ 'templates/package.json.txt',
+ 'project metadata',
+ list(npmPkgs = toDepJSON(npmPkgs))
+ )
+
+ renderFile(
+ 'webpack.config.js',
+ 'templates/webpack.config.js.txt',
+ 'webpack configuration',
+ list(
+ name = name,
+ outputPath = sprintf("inst/www/%s/%s", package, name)
+ )
+ )
+
+ renderFile(
+ sprintf('srcjs/%s.jsx', name),
+ 'templates/input_js.txt',
+ 'JavaScript implementation',
+ list(
+ name = name,
+ package = package
+ )
+ )
+
+ renderFile(
+ 'app.R',
+ 'templates/input_app.R.txt',
+ 'example app',
+ list(
+ name = name,
+ package = package
+ )
+ )
+
+ usethis::use_build_ignore(c("node_modules", "srcjs", "app.R", "package.json", "webpack.config.js", "yarn.lock"))
+ usethis::use_git_ignore(c("node_modules"))
+ lapply(c("htmltools", "shiny", "reactR"), usethis::use_package)
+
+ message("To install dependencies from npm run: yarn install")
+ message("To build JavaScript run: yarn run webpack --mode=development")
+}
+
diff --git a/R/scaffold_utils.R b/R/scaffold_utils.R
new file mode 100644
index 0000000..3cfc628
--- /dev/null
+++ b/R/scaffold_utils.R
@@ -0,0 +1,63 @@
+slurp <- function(file) {
+ paste(readLines(
+ system.file(file, package = 'reactR')
+ ), collapse = "\n")
+}
+
+# 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)
+}
+
+# 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)
+}
+
+capitalize <- function(s) {
+ gsub("^(.)", perl = TRUE, replacement = '\\U\\1', s)
+}
+
+toDepJSON <- function(npmPkgs) {
+ if (is.null(npmPkgs)) {
+ ""
+ } else if (!length(names(npmPkgs))) {
+ stop("Must specify npm package names in the names attributes of npmPkgs")
+ } else {
+ paste0(sprintf('"%s": "%s"', names(npmPkgs), npmPkgs), collapse = ",\n")
+ }
+}
+
+# Wraps renderTemplate for convenient use from scaffold functions.
+renderFile <- function(outputFile, templateFile, description = '', substitutions = list()) {
+ if (!file.exists(outputFile)) {
+ dir.create(dirname(outputFile), recursive = TRUE, showWarnings = FALSE)
+ cat(renderTemplate(slurp(templateFile), substitutions), file = outputFile)
+ message("Created ", description, " ", outputFile)
+ } else {
+ message(outputFile, " already exists")
+ }
+ outputFile
+}
+
+getPackage <- function() {
+ if (!file.exists('DESCRIPTION')) {
+ stop("The current directory doesn't contain a package. You're either in the wrong directory, or need to create a package to house your widget.", call. = FALSE)
+ }
+ read.dcf('DESCRIPTION')[[1,"Package"]]
+}
+
+# Constraining names prevents the user from encountering obscure CSS problems
+# and JavaScript errors after scaffolding.
+assertNameValid <- function(name) {
+ if (!grepl(robustName, name)) {
+ msg <- sprintf("Name '%s' is invalid, names must begin with an alphabetic character and must contain only alphabetic and numeric characters", name)
+ stop(msg, call. = FALSE)
+ }
+}
diff --git a/R/scaffold_widget.R b/R/scaffold_widget.R
new file mode 100644
index 0000000..4240f1b
--- /dev/null
+++ b/R/scaffold_widget.R
@@ -0,0 +1,101 @@
+#' 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 npmPkgs Optional \href{https://npmjs.com/}{NPM} packages upon which
+#' this widget is based which will be used to populate \code{package.json}.
+#' Should be a named list of names to
+#' \href{https://docs.npmjs.com/files/package.json#dependencies}{versions}.
+#' @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, npmPkgs = NULL, edit = interactive()){
+ assertNameValid(name)
+ package <- getPackage()
+
+ addWidgetConstructor(name, package, edit)
+ addWidgetYAML(name, edit)
+ addPackageJSON(toDepJSON(npmPkgs))
+ addWebpackConfig(name)
+ addWidgetJS(name, edit)
+ addExampleApp(name)
+
+ usethis::use_build_ignore(c("node_modules", "srcjs", "app.R", "package.json", "webpack.config.js", "yarn.lock"))
+ usethis::use_git_ignore(c("node_modules"))
+ lapply(c("htmltools", "htmlwidgets", "reactR"), usethis::use_package)
+
+ message("To install dependencies from npm run: yarn install")
+ message("To build JavaScript run: yarn run webpack --mode=development")
+}
+
+addWidgetConstructor <- function(name, package, edit){
+ file <- renderFile(
+ sprintf("R/%s.R", name),
+ "templates/widget_r.txt",
+ "boilerplate for widget constructor",
+ list(
+ name = name,
+ package = package,
+ capName = capitalize(name)
+ )
+ )
+ if (edit) fileEdit(file)
+}
+
+addWidgetYAML <- function(name, edit){
+ file <- renderFile(
+ sprintf('inst/htmlwidgets/%s.yaml', name),
+ "templates/widget_yaml.txt",
+ "boilerplate for widget dependencies"
+ )
+ if (edit) fileEdit(file)
+}
+
+addPackageJSON <- function(npmPkgs) {
+ renderFile(
+ 'package.json',
+ 'templates/package.json.txt',
+ 'project metadata',
+ list(npmPkgs = npmPkgs)
+ )
+}
+
+addWebpackConfig <- function(name) {
+ renderFile(
+ 'webpack.config.js',
+ 'templates/webpack.config.js.txt',
+ 'webpack configuration',
+ list(
+ name = name,
+ outputPath = 'inst/htmlwidgets'
+ )
+ )
+}
+
+addWidgetJS <- function(name, edit){
+ file <- renderFile(
+ sprintf('srcjs/%s.jsx', name),
+ 'templates/widget_js.txt',
+ 'boilerplate for widget JavaScript bindings',
+ list(name = name)
+ )
+ if (edit) fileEdit(file)
+}
+
+addExampleApp <- function(name) {
+ renderFile(
+ 'app.R',
+ 'templates/widget_app.R.txt',
+ 'example app',
+ list(
+ name = name,
+ capName = capitalize(name)
+ )
+ )
+}
diff --git a/inst/templates/input_app.R.txt b/inst/templates/input_app.R.txt
new file mode 100644
index 0000000..151ac55
--- /dev/null
+++ b/inst/templates/input_app.R.txt
@@ -0,0 +1,16 @@
+library(shiny)
+library(${package})
+
+ui <- fluidPage(
+ titlePanel("reactR Input Example"),
+ ${name}Input("textInput"),
+ textOutput("textOutput")
+)
+
+server <- function(input, output, session) {
+ output$textOutput <- renderText({
+ sprintf("You entered: %s", input$textInput)
+ })
+}
+
+shinyApp(ui, server)
diff --git a/inst/templates/input_js.txt b/inst/templates/input_js.txt
new file mode 100644
index 0000000..14781a1
--- /dev/null
+++ b/inst/templates/input_js.txt
@@ -0,0 +1,7 @@
+import { reactShinyInput } from 'reactR';
+
+const TextInput = ({ configuration, value, setValue }) => {
+ return setValue(e.target.value)}/>;
+};
+
+reactShinyInput('.${name}', '${package}.${name}', TextInput);
diff --git a/inst/templates/input_r.txt b/inst/templates/input_r.txt
new file mode 100644
index 0000000..037ffa9
--- /dev/null
+++ b/inst/templates/input_r.txt
@@ -0,0 +1,36 @@
+#'
+#'
+#'
+#'
+#' @importFrom shiny restoreInput
+#' @importFrom reactR createReactShinyInput
+#' @importFrom htmltools htmlDependency tags
+#'
+#' @export
+${name}Input <- function(inputId, default = "") {
+ reactR::createReactShinyInput(
+ inputId,
+ "${name}",
+ htmltools::htmlDependency(
+ name = "${name}-input",
+ version = "1.0.0",
+ src = "www/${package}/${name}",
+ package = "${package}",
+ script = "${name}.js"
+ ),
+ default,
+ list(),
+ htmltools::tags$span
+ )
+}
+
+#'
+#'
+#'
+#'
+#' @export
+update${capName}Input <- function(session, inputId, value, configuration = NULL) {
+ message <- list(value = value)
+ if (!is.null(configuration)) message$configuration <- configuration
+ session$sendInputMessage(inputId, message);
+}
diff --git a/inst/templates/widget_package.json.txt b/inst/templates/package.json.txt
similarity index 100%
rename from inst/templates/widget_package.json.txt
rename to inst/templates/package.json.txt
diff --git a/inst/templates/widget_webpack.config.js.txt b/inst/templates/webpack.config.js.txt
similarity index 68%
rename from inst/templates/widget_webpack.config.js.txt
rename to inst/templates/webpack.config.js.txt
index 4161d75..bcfbb58 100644
--- a/inst/templates/widget_webpack.config.js.txt
+++ b/inst/templates/webpack.config.js.txt
@@ -1,15 +1,17 @@
var path = require('path');
module.exports = {
- entry: path.join(__dirname, 'srcjs', '${name}.js'),
+ mode: 'development',
+ entry: path.join(__dirname, 'srcjs', '${name}.jsx'),
output: {
- path: path.join(__dirname, 'inst', 'htmlwidgets'),
+ path: path.join(__dirname, 'inst', 'www', '${package}', '${name}'),
+ path: path.join(__dirname, '${outputPath}'),
filename: '${name}.js'
},
module: {
rules: [
{
- test: /\.js$/,
+ test: /\.jsx?$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
diff --git a/inst/templates/widget_yaml.txt b/inst/templates/widget_yaml.txt
new file mode 100644
index 0000000..82bc4da
--- /dev/null
+++ b/inst/templates/widget_yaml.txt
@@ -0,0 +1,8 @@
+# (uncomment to add a dependency)
+# dependencies:
+# - name:
+# version:
+# src:
+# script:
+# stylesheet:
+
diff --git a/inst/www/react-tools/react-tools.js b/inst/www/react-tools/react-tools.js
index 514b484..c785223 100644
--- a/inst/www/react-tools/react-tools.js
+++ b/inst/www/react-tools/react-tools.js
@@ -86,145 +86,395 @@
/************************************************************************/
/******/ ({
+/***/ "./srcjs/input.js":
+/*!************************!*\
+ !*** ./srcjs/input.js ***!
+ \************************/
+/*! exports provided: reactShinyInput */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reactShinyInput", function() { return reactShinyInput; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "react-dom");
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
+/* harmony import */ var shiny__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! shiny */ "shiny");
+/* harmony import */ var shiny__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(shiny__WEBPACK_IMPORTED_MODULE_2__);
+/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! jquery */ "jquery");
+/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_3__);
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
+
+function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
+
+function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
+
+function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
+
+
+
+
+
+/*
+ * This default receiveMessage implementation expects data to contain whole
+ * configuration and value properties. If either is present, it will be set and
+ * the component will be re-rendered. Because receiveMessage is typically used
+ * by input authors to perform incremental updates, this default implementation
+ * can be overriden by the user with the receiveMessage arguments to
+ * reactShinyInput.
+ */
+
+function defaultReceiveMessage(el, _ref) {
+ var configuration = _ref.configuration,
+ value = _ref.value;
+ var dirty = false;
+
+ if (configuration !== undefined) {
+ this.setInputConfiguration(el, configuration);
+ dirty = true;
+ }
+
+ if (value !== undefined) {
+ this.setInputValue(el, value);
+ dirty = true;
+ }
+
+ if (dirty) {
+ this.getCallback(el)();
+ this.render(el);
+ }
+}
+
+var defaultOptions = {
+ receiveMessage: defaultReceiveMessage
+};
+/**
+ * Installs a new Shiny input binding based on a React component.
+ *
+ * @param {string} selector - jQuery selector that should identify the set of
+ * container elements within the scope argument of Shiny.InputBinding.find.
+ * @param {string} name - A name such as 'acme.FooInput' that should uniquely
+ * identify the component.
+ * @param {Object} component - React Component, either class or function.
+ * @param {Object} options - Additional configuration options. Supported
+ * options are:
+ * - receiveMessage: Implementation of Shiny.InputBinding to use in place of
+ * the default. Typically overridden as an optimization to perform
+ * incremental value updates.
+ */
+
+function reactShinyInput(selector, name, component, options) {
+ options = Object.assign({}, defaultOptions, options);
+ shiny__WEBPACK_IMPORTED_MODULE_2___default.a.inputBindings.register(new (
+ /*#__PURE__*/
+ function (_Shiny$InputBinding) {
+ _inherits(_class, _Shiny$InputBinding);
+
+ function _class() {
+ _classCallCheck(this, _class);
+
+ return _possibleConstructorReturn(this, _getPrototypeOf(_class).apply(this, arguments));
+ }
+
+ _createClass(_class, [{
+ key: "find",
+
+ /*
+ * Methods override those in Shiny.InputBinding
+ */
+ value: function find(scope) {
+ return jquery__WEBPACK_IMPORTED_MODULE_3___default()(scope).find(selector);
+ }
+ }, {
+ key: "getValue",
+ value: function getValue(el) {
+ return this.getInputValue(el);
+ }
+ }, {
+ key: "setValue",
+ value: function setValue(el, value) {
+ this.setInputValue(el, value);
+ this.getCallback(el)();
+ this.render(el);
+ }
+ }, {
+ key: "initialize",
+ value: function initialize(el) {
+ jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('value', JSON.parse(jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).next().text()));
+ jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('configuration', JSON.parse(jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).next().next().text()));
+ }
+ }, {
+ key: "subscribe",
+ value: function subscribe(el, callback) {
+ jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('callback', callback);
+ this.render(el);
+ }
+ }, {
+ key: "unsubscribe",
+ value: function unsubscribe(el, callback) {
+ jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).removeData('callback');
+ react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(null, el);
+ }
+ }, {
+ key: "receiveMessage",
+ value: function receiveMessage(el, data) {
+ options.receiveMessage.call(this, el, data);
+ }
+ /*
+ * Methods not present in Shiny.InputBinding but accessible to users
+ * through `this` in receiveMessage
+ * */
+
+ }, {
+ key: "getInputValue",
+ value: function getInputValue(el) {
+ return jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('value');
+ }
+ }, {
+ key: "setInputValue",
+ value: function setInputValue(el, value) {
+ jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('value', value);
+ }
+ }, {
+ key: "getInputConfiguration",
+ value: function getInputConfiguration(el) {
+ return jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('configuration');
+ }
+ }, {
+ key: "setInputConfiguration",
+ value: function setInputConfiguration(el, configuration) {
+ jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('configuration', configuration);
+ }
+ }, {
+ key: "getCallback",
+ value: function getCallback(el) {
+ return jquery__WEBPACK_IMPORTED_MODULE_3___default()(el).data('callback');
+ }
+ }, {
+ key: "render",
+ value: function render(el) {
+ var element = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(component, {
+ configuration: this.getInputConfiguration(el),
+ value: this.getValue(el),
+ setValue: this.setValue.bind(this, el)
+ });
+ react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(element, el);
+ }
+ }]);
+
+ return _class;
+ }(shiny__WEBPACK_IMPORTED_MODULE_2___default.a.InputBinding))(), name);
+}
+
+/***/ }),
+
/***/ "./srcjs/react-tools.js":
/*!******************************!*\
!*** ./srcjs/react-tools.js ***!
\******************************/
-/*! no static exports found */
-/***/ (function(module, exports) {
+/*! no exports provided */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
-function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _widget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./widget */ "./srcjs/widget.js");
+/* harmony import */ var _input__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./input */ "./srcjs/input.js");
-window.reactR = function () {
- /**
- * Recursively transforms tag, a JSON representation of an instance of a
- * React component and its children, into a React element suitable for
- * passing to ReactDOM.render.
- * @param {Object} components
- * @param {Object} tag
- */
- function hydrate(components, tag) {
- if (typeof tag === 'string') return tag;
-
- if (tag.name[0] === tag.name[0].toUpperCase() && !components.hasOwnProperty(tag.name)) {
- throw new Error("Unknown component: " + tag.name);
- }
- var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,
- args = [elem, tag.attribs];
+window.reactR = {
+ reactShinyInput: _input__WEBPACK_IMPORTED_MODULE_1__["reactShinyInput"],
+ reactWidget: _widget__WEBPACK_IMPORTED_MODULE_0__["reactWidget"],
+ hydrate: _widget__WEBPACK_IMPORTED_MODULE_0__["hydrate"]
+};
- for (var i = 0; i < tag.children.length; i++) {
- args.push(hydrate(components, tag.children[i]));
- }
+/***/ }),
- return React.createElement.apply(React, args);
+/***/ "./srcjs/widget.js":
+/*!*************************!*\
+ !*** ./srcjs/widget.js ***!
+ \*************************/
+/*! exports provided: hydrate, defaultOptions, mergeOptions, formatDimension, isTag, reactWidget */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hydrate", function() { return hydrate; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultOptions", function() { return defaultOptions; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeOptions", function() { return mergeOptions; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "formatDimension", function() { return formatDimension; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isTag", function() { return isTag; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reactWidget", function() { return reactWidget; });
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+/**
+ * Recursively transforms tag, a JSON representation of an instance of a
+ * React component and its children, into a React element suitable for
+ * passing to ReactDOM.render.
+ * @param {Object} components
+ * @param {Object} tag
+ */
+function hydrate(components, tag) {
+ if (typeof tag === 'string') return tag;
+
+ if (tag.name[0] === tag.name[0].toUpperCase() && !components.hasOwnProperty(tag.name)) {
+ throw new Error("Unknown component: " + tag.name);
}
- var defaultOptions = {
- // The name of the property on the root tag to use for the width, if
- // it's updated.
- widthProperty: "width",
- // The name of the property on the root tag to use for the height, if
- // it's updated.
- heightProperty: "height",
- // Whether or not to append the string 'px' to the width and height
- // properties when they change.
- appendPx: false,
- // Whether or not to dynamically update the width and height properties
- // of the last known tag when the computed width and height change in
- // the browser.
- renderOnResize: false
- };
-
- function mergeOptions(options) {
- var merged = {};
-
- for (var k in defaultOptions) {
- merged[k] = defaultOptions[k];
- }
+ var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,
+ args = [elem, tag.attribs];
- for (var k in options) {
- if (!defaultOptions.hasOwnProperty(k)) {
- throw new Error("Unrecognized option: " + k);
- }
+ for (var i = 0; i < tag.children.length; i++) {
+ args.push(hydrate(components, tag.children[i]));
+ }
- merged[k] = options[k];
- }
+ return React.createElement.apply(React, args);
+}
+var defaultOptions = {
+ // The name of the property on the root tag to use for the width, if
+ // it's updated.
+ widthProperty: "width",
+ // The name of the property on the root tag to use for the height, if
+ // it's updated.
+ heightProperty: "height",
+ // Whether or not to append the string 'px' to the width and height
+ // properties when they change.
+ appendPx: false,
+ // Whether or not to dynamically update the width and height properties
+ // of the last known tag when the computed width and height change in
+ // the browser.
+ renderOnResize: false
+};
+function mergeOptions(options) {
+ var merged = {};
- return merged;
+ for (var k in defaultOptions) {
+ merged[k] = defaultOptions[k];
}
- function formatDimension(dim, options) {
- if (options.appendPx) {
- return dim + 'px';
- } else {
- return dim;
+ for (var k in options) {
+ if (!defaultOptions.hasOwnProperty(k)) {
+ throw new Error("Unrecognized option: " + k);
}
+
+ merged[k] = options[k];
}
- function isTag(value) {
- return _typeof(value) === 'object' && value.hasOwnProperty('name') && value.hasOwnProperty('attribs') && value.hasOwnProperty('children');
+ return merged;
+}
+function formatDimension(dim, options) {
+ if (options.appendPx) {
+ return dim + 'px';
+ } else {
+ return dim;
}
- /**
- * Creates an HTMLWidget that is updated by rendering a React component.
- * React component constructors are made available by specifying them by
- * name in the components object.
- * @param {string} name
- * @param {string} type
- * @param {Object} components
- * @param {Object} options
- */
-
-
- function reactWidget(name, type, components, options) {
- var actualOptions = mergeOptions(options);
- HTMLWidgets.widget({
- name: name,
- type: type,
- factory: function factory(el, width, height) {
- var lastValue,
- instance = {},
- renderValue = function renderValue(value) {
- if (actualOptions.renderOnResize) {
- // value.tag might be a primitive string, in which
- // case there is no attribs property.
- if (_typeof(value.tag) === 'object') {
- value.tag.attribs[actualOptions["widthProperty"]] = formatDimension(width);
- value.tag.attribs[actualOptions["heightProperty"]] = formatDimension(height);
- }
-
- lastValue = value;
- }
+}
+function isTag(value) {
+ return _typeof(value) === 'object' && value.hasOwnProperty('name') && value.hasOwnProperty('attribs') && value.hasOwnProperty('children');
+}
+/**
+ * Creates an HTMLWidget that is updated by rendering a React component.
+ * React component constructors are made available by specifying them by
+ * name in the components object.
+ * @param {string} name
+ * @param {string} type
+ * @param {Object} components
+ * @param {Object} options
+ */
- this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);
- };
-
- return {
- instance: instance,
- renderValue: renderValue,
- resize: function resize(newWidth, newHeight) {
- if (actualOptions.renderOnResize) {
- width = newWidth;
- height = newHeight;
- renderValue(lastValue);
- }
+function reactWidget(name, type, components, options) {
+ var actualOptions = mergeOptions(options);
+ window.HTMLWidgets.widget({
+ name: name,
+ type: type,
+ factory: function factory(el, width, height) {
+ var lastValue,
+ instance = {},
+ renderValue = function renderValue(value) {
+ if (actualOptions.renderOnResize) {
+ // value.tag might be a primitive string, in which
+ // case there is no attribs property.
+ if (_typeof(value.tag) === 'object') {
+ value.tag.attribs[actualOptions["widthProperty"]] = formatDimension(width);
+ value.tag.attribs[actualOptions["heightProperty"]] = formatDimension(height);
}
- };
- }
- });
- }
- return {
- reactWidget: reactWidget,
- hydrate: hydrate,
- __internal: {
- defaultOptions: defaultOptions,
- mergeOptions: mergeOptions,
- formatDimension: formatDimension,
- isTag: isTag
+ lastValue = value;
+ } // with functional stateless components this will be null
+ // see https://reactjs.org/docs/react-dom.html#render for more details
+
+
+ this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);
+ };
+
+ return {
+ instance: instance,
+ renderValue: renderValue,
+ resize: function resize(newWidth, newHeight) {
+ if (actualOptions.renderOnResize) {
+ width = newWidth;
+ height = newHeight;
+ renderValue(lastValue);
+ }
+ }
+ };
}
- };
-}();
+ });
+}
+
+/***/ }),
+
+/***/ "jquery":
+/*!********************************!*\
+ !*** external "window.jQuery" ***!
+ \********************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = window.jQuery;
+
+/***/ }),
+
+/***/ "react":
+/*!*******************************!*\
+ !*** external "window.React" ***!
+ \*******************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = window.React;
+
+/***/ }),
+
+/***/ "react-dom":
+/*!**********************************!*\
+ !*** external "window.ReactDOM" ***!
+ \**********************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = window.ReactDOM;
+
+/***/ }),
+
+/***/ "shiny":
+/*!*******************************!*\
+ !*** external "window.Shiny" ***!
+ \*******************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+module.exports = window.Shiny;
/***/ })
diff --git a/inst/www/react-tools/react-tools.js.map b/inst/www/react-tools/react-tools.js.map
index 6766dba..b4e27bf 100644
--- a/inst/www/react-tools/react-tools.js.map
+++ b/inst/www/react-tools/react-tools.js.map
@@ -1 +1 @@
-{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./srcjs/react-tools.js"],"names":["window","reactR","hydrate","components","tag","name","toUpperCase","hasOwnProperty","Error","elem","args","attribs","i","children","length","push","React","createElement","apply","defaultOptions","widthProperty","heightProperty","appendPx","renderOnResize","mergeOptions","options","merged","k","formatDimension","dim","isTag","value","reactWidget","type","actualOptions","HTMLWidgets","widget","factory","el","width","height","lastValue","instance","renderValue","component","ReactDOM","render","resize","newWidth","newHeight","__internal"],"mappings":"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","file":"react-tools.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./srcjs/react-tools.js\");\n","window.reactR = (function () {\r\n /**\r\n * Recursively transforms tag, a JSON representation of an instance of a\r\n * React component and its children, into a React element suitable for\r\n * passing to ReactDOM.render.\r\n * @param {Object} components\r\n * @param {Object} tag\r\n */\r\n function hydrate(components, tag) {\r\n if (typeof tag === 'string') return tag;\r\n if (tag.name[0] === tag.name[0].toUpperCase()\r\n && !components.hasOwnProperty(tag.name)) {\r\n throw new Error(\"Unknown component: \" + tag.name);\r\n }\r\n var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,\r\n args = [elem, tag.attribs];\r\n for (var i = 0; i < tag.children.length; i++) {\r\n args.push(hydrate(components, tag.children[i]));\r\n }\r\n return React.createElement.apply(React, args);\r\n }\r\n\r\n var defaultOptions = {\r\n // The name of the property on the root tag to use for the width, if\r\n // it's updated.\r\n widthProperty: \"width\",\r\n // The name of the property on the root tag to use for the height, if\r\n // it's updated.\r\n heightProperty: \"height\",\r\n // Whether or not to append the string 'px' to the width and height\r\n // properties when they change.\r\n appendPx: false,\r\n // Whether or not to dynamically update the width and height properties\r\n // of the last known tag when the computed width and height change in\r\n // the browser.\r\n renderOnResize: false\r\n };\r\n\r\n function mergeOptions(options) {\r\n var merged = {};\r\n for (var k in defaultOptions) {\r\n merged[k] = defaultOptions[k];\r\n }\r\n for (var k in options) {\r\n if (!defaultOptions.hasOwnProperty(k)) {\r\n throw new Error(\"Unrecognized option: \" + k);\r\n }\r\n merged[k] = options[k];\r\n }\r\n return merged;\r\n }\r\n\r\n function formatDimension(dim, options) {\r\n if (options.appendPx) {\r\n return dim + 'px';\r\n } else {\r\n return dim;\r\n }\r\n }\r\n\r\n function isTag(value) {\r\n return (typeof value === 'object')\r\n && value.hasOwnProperty('name')\r\n && value.hasOwnProperty('attribs')\r\n && value.hasOwnProperty('children');\r\n }\r\n\r\n /**\r\n * Creates an HTMLWidget that is updated by rendering a React component.\r\n * React component constructors are made available by specifying them by\r\n * name in the components object.\r\n * @param {string} name\r\n * @param {string} type\r\n * @param {Object} components\r\n * @param {Object} options\r\n */\r\n function reactWidget(name, type, components, options) {\r\n var actualOptions = mergeOptions(options);\r\n HTMLWidgets.widget({\r\n name: name,\r\n type: type,\r\n factory: function (el, width, height) {\r\n var lastValue,\r\n instance = {},\r\n renderValue = (function (value) {\r\n if (actualOptions.renderOnResize) {\r\n // value.tag might be a primitive string, in which\r\n // case there is no attribs property.\r\n if (typeof value.tag === 'object') {\r\n value.tag.attribs[actualOptions[\"widthProperty\"]] = formatDimension(width);\r\n value.tag.attribs[actualOptions[\"heightProperty\"]] = formatDimension(height);\r\n }\r\n lastValue = value;\r\n }\r\n this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);\r\n });\r\n return {\r\n instance: instance,\r\n renderValue: renderValue,\r\n resize: function (newWidth, newHeight) {\r\n if (actualOptions.renderOnResize) {\r\n width = newWidth;\r\n height = newHeight;\r\n renderValue(lastValue);\r\n }\r\n }\r\n };\r\n }\r\n })\r\n }\r\n\r\n return {\r\n reactWidget: reactWidget,\r\n hydrate: hydrate,\r\n __internal: {\r\n defaultOptions: defaultOptions,\r\n mergeOptions: mergeOptions,\r\n formatDimension: formatDimension,\r\n isTag: isTag\r\n }\r\n };\r\n})()\r\n"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./srcjs/input.js","webpack:///./srcjs/react-tools.js","webpack:///./srcjs/widget.js","webpack:///external \"window.jQuery\"","webpack:///external \"window.React\"","webpack:///external \"window.ReactDOM\"","webpack:///external \"window.Shiny\""],"names":["defaultReceiveMessage","el","configuration","value","dirty","undefined","setInputConfiguration","setInputValue","getCallback","render","defaultOptions","receiveMessage","reactShinyInput","selector","name","component","options","Object","assign","Shiny","inputBindings","register","scope","$","find","getInputValue","data","JSON","parse","next","text","callback","removeData","ReactDOM","call","element","React","createElement","getInputConfiguration","getValue","setValue","bind","InputBinding","window","reactR","reactWidget","hydrate","components","tag","toUpperCase","hasOwnProperty","Error","elem","args","attribs","i","children","length","push","apply","widthProperty","heightProperty","appendPx","renderOnResize","mergeOptions","merged","k","formatDimension","dim","isTag","type","actualOptions","HTMLWidgets","widget","factory","width","height","lastValue","instance","renderValue","resize","newWidth","newHeight"],"mappings":"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","file":"react-tools.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./srcjs/react-tools.js\");\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport Shiny from 'shiny';\nimport $ from 'jquery';\n\n/*\n * This default receiveMessage implementation expects data to contain whole\n * configuration and value properties. If either is present, it will be set and\n * the component will be re-rendered. Because receiveMessage is typically used\n * by input authors to perform incremental updates, this default implementation\n * can be overriden by the user with the receiveMessage arguments to\n * reactShinyInput.\n */\nfunction defaultReceiveMessage(el, { configuration, value }) {\n let dirty = false;\n if (configuration !== undefined) {\n this.setInputConfiguration(el, configuration);\n dirty = true;\n }\n if (value !== undefined) {\n this.setInputValue(el, value);\n dirty = true;\n }\n if (dirty) {\n this.getCallback(el)();\n this.render(el);\n }\n}\n\nconst defaultOptions = {\n receiveMessage: defaultReceiveMessage\n};\n\n/**\n * Installs a new Shiny input binding based on a React component.\n *\n * @param {string} selector - jQuery selector that should identify the set of\n * container elements within the scope argument of Shiny.InputBinding.find.\n * @param {string} name - A name such as 'acme.FooInput' that should uniquely\n * identify the component.\n * @param {Object} component - React Component, either class or function.\n * @param {Object} options - Additional configuration options. Supported\n * options are:\n * - receiveMessage: Implementation of Shiny.InputBinding to use in place of\n * the default. Typically overridden as an optimization to perform\n * incremental value updates.\n */\nexport function reactShinyInput(selector,\n name,\n component,\n options) {\n options = Object.assign({}, defaultOptions, options);\n Shiny.inputBindings.register(new class extends Shiny.InputBinding {\n\n /*\n * Methods override those in Shiny.InputBinding\n */\n\n find(scope) {\n return $(scope).find(selector);\n }\n getValue(el) {\n return this.getInputValue(el);\n }\n setValue(el, value) {\n this.setInputValue(el, value);\n this.getCallback(el)();\n this.render(el);\n }\n initialize(el) {\n $(el).data('value', JSON.parse($(el).next().text()));\n $(el).data('configuration', JSON.parse($(el).next().next().text()));\n }\n subscribe(el, callback) {\n $(el).data('callback', callback);\n this.render(el);\n }\n unsubscribe(el, callback) {\n $(el).removeData('callback');\n ReactDOM.render(null, el);\n }\n receiveMessage(el, data) {\n options.receiveMessage.call(this, el, data);\n }\n\n /*\n * Methods not present in Shiny.InputBinding but accessible to users\n * through `this` in receiveMessage\n * */\n\n getInputValue(el) {\n return $(el).data('value');\n }\n setInputValue(el, value) {\n $(el).data('value', value);\n }\n getInputConfiguration(el) {\n return $(el).data('configuration');\n }\n setInputConfiguration(el, configuration) {\n $(el).data('configuration', configuration);\n }\n getCallback(el) {\n return $(el).data('callback');\n }\n render(el) {\n const element = React.createElement(component, {\n configuration: this.getInputConfiguration(el),\n value: this.getValue(el),\n setValue: this.setValue.bind(this, el)\n });\n ReactDOM.render(element, el);\n }\n }, name);\n}\n\n","import { reactWidget, hydrate } from './widget';\nimport { reactShinyInput } from './input';\n\nwindow.reactR = {\n reactShinyInput: reactShinyInput,\n reactWidget: reactWidget,\n hydrate: hydrate\n};\n","/**\n * Recursively transforms tag, a JSON representation of an instance of a\n * React component and its children, into a React element suitable for\n * passing to ReactDOM.render.\n * @param {Object} components\n * @param {Object} tag\n */\nexport function hydrate(components, tag) {\n if (typeof tag === 'string') return tag;\n if (tag.name[0] === tag.name[0].toUpperCase()\n && !components.hasOwnProperty(tag.name)) {\n throw new Error(\"Unknown component: \" + tag.name);\n }\n var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,\n args = [elem, tag.attribs];\n for (var i = 0; i < tag.children.length; i++) {\n args.push(hydrate(components, tag.children[i]));\n }\n return React.createElement.apply(React, args);\n}\n\nexport const defaultOptions = {\n // The name of the property on the root tag to use for the width, if\n // it's updated.\n widthProperty: \"width\",\n // The name of the property on the root tag to use for the height, if\n // it's updated.\n heightProperty: \"height\",\n // Whether or not to append the string 'px' to the width and height\n // properties when they change.\n appendPx: false,\n // Whether or not to dynamically update the width and height properties\n // of the last known tag when the computed width and height change in\n // the browser.\n renderOnResize: false\n};\n\nexport function mergeOptions(options) {\n var merged = {};\n for (var k in defaultOptions) {\n merged[k] = defaultOptions[k];\n }\n for (var k in options) {\n if (!defaultOptions.hasOwnProperty(k)) {\n throw new Error(\"Unrecognized option: \" + k);\n }\n merged[k] = options[k];\n }\n return merged;\n}\n\nexport function formatDimension(dim, options) {\n if (options.appendPx) {\n return dim + 'px';\n } else {\n return dim;\n }\n}\n\nexport function isTag(value) {\n return (typeof value === 'object')\n && value.hasOwnProperty('name')\n && value.hasOwnProperty('attribs')\n && value.hasOwnProperty('children');\n}\n\n/**\n * Creates an HTMLWidget that is updated by rendering a React component.\n * React component constructors are made available by specifying them by\n * name in the components object.\n * @param {string} name\n * @param {string} type\n * @param {Object} components\n * @param {Object} options\n */\nexport function reactWidget(name, type, components, options) {\n var actualOptions = mergeOptions(options);\n window.HTMLWidgets.widget({\n name: name,\n type: type,\n factory: function (el, width, height) {\n var lastValue,\n instance = {},\n renderValue = (function (value) {\n if (actualOptions.renderOnResize) {\n // value.tag might be a primitive string, in which\n // case there is no attribs property.\n if (typeof value.tag === 'object') {\n value.tag.attribs[actualOptions[\"widthProperty\"]] = formatDimension(width);\n value.tag.attribs[actualOptions[\"heightProperty\"]] = formatDimension(height);\n }\n lastValue = value;\n }\n // with functional stateless components this will be null\n // see https://reactjs.org/docs/react-dom.html#render for more details\n this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);\n });\n return {\n instance: instance,\n renderValue: renderValue,\n resize: function (newWidth, newHeight) {\n if (actualOptions.renderOnResize) {\n width = newWidth;\n height = newHeight;\n renderValue(lastValue);\n }\n }\n };\n }\n })\n}\n\n","module.exports = window.jQuery;","module.exports = window.React;","module.exports = window.ReactDOM;","module.exports = window.Shiny;"],"sourceRoot":""}
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
index 6994c7a..c727f38 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -3,13 +3,24 @@ const webpackConfig = require('./webpack.config.js');
module.exports = function (config) {
config.set({
frameworks: ['mocha', 'chai', 'source-map-support'],
- files: ['inst/www/react-tools/react-tools.js', 'js-tests/js-tests.jsx'],
+ files: ['srcjs/react-tools.js', 'js-tests/js-tests.jsx'],
preprocessors: {
'js-tests/*.js': ['webpack'],
- 'js-tests/*.jsx': ['webpack']
+ 'js-tests/*.jsx': ['webpack'],
+ 'srcjs/*.js': ['webpack']
},
webpack: {
- module: webpackConfig.module
+ module: webpackConfig.module,
+ externals: {
+ /**
+ * In tests, react and react-dom are provided internally.
+ * The following libraries are not part of the testing environment,
+ * but are provided here as external so that webpack builds.
+ **/
+ 'jquery': 'window.jQuery',
+ 'shiny': 'window.Shiny',
+ 'htmlwidgets': 'window.HTMLWidgets'
+ }
},
webpackMiddleware: {
stats: 'errors-only'
@@ -23,4 +34,4 @@ module.exports = function (config) {
// singleRun: false, // Karma captures browsers, runs the tests and exits
concurrency: Infinity
})
-}
+}
diff --git a/man/createReactShinyInput.Rd b/man/createReactShinyInput.Rd
new file mode 100644
index 0000000..5bfdd52
--- /dev/null
+++ b/man/createReactShinyInput.Rd
@@ -0,0 +1,49 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/reacttools.R
+\name{createReactShinyInput}
+\alias{createReactShinyInput}
+\title{Create a React-based input}
+\usage{
+createReactShinyInput(inputId, class, dependencies, default = NULL,
+ configuration = list(), container = htmltools::tags$div)
+}
+\arguments{
+\item{inputId}{The \code{input} slot that will be used to access the value.}
+
+\item{class}{Space-delimited list of CSS class names that should identify
+this input type in the browser.}
+
+\item{dependencies}{HTML dependencies to include in addition to those
+supporting React. Must contain at least one dependency, that of the input's
+implementation.}
+
+\item{default}{Initial value.}
+
+\item{configuration}{Static configuration data.}
+
+\item{container}{Function to generate an HTML element to contain the input.}
+}
+\value{
+Shiny input suitable for inclusion in a UI.
+}
+\description{
+Create a React-based input
+}
+\examples{
+myInput <- function(inputId, default = "") {
+ # The value of createReactShinyInput should be returned from input constructor functions.
+ createReactShinyInput(
+ inputId,
+ "myinput",
+ # At least one htmlDependency must be provided -- the JavaScript implementation of the input.
+ htmlDependency(
+ name = "my-input",
+ version = "1.0.0",
+ src = "www/mypackage/myinput",
+ package = "mypackage",
+ script = "myinput.js"
+ ),
+ default
+ )
+}
+}
diff --git a/man/scaffoldReactShinyInput.Rd b/man/scaffoldReactShinyInput.Rd
new file mode 100644
index 0000000..b0e9490
--- /dev/null
+++ b/man/scaffoldReactShinyInput.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/scaffold_input.R
+\name{scaffoldReactShinyInput}
+\alias{scaffoldReactShinyInput}
+\title{Create implementation scaffolding for a React.js-based Shiny input.}
+\usage{
+scaffoldReactShinyInput(name, npmPkgs = NULL, edit = interactive())
+}
+\arguments{
+\item{name}{Name of input}
+
+\item{npmPkgs}{Optional \href{https://npmjs.com/}{NPM} packages upon which
+this input is based which will be used to populate \code{package.json}.
+Should be a named list of names to
+\href{https://docs.npmjs.com/files/package.json#dependencies}{versions}.}
+
+\item{edit}{Automatically open the input's source files after creating the
+scaffolding.}
+}
+\description{
+Add the minimal code required to implement a React.js-based Shiny input to an
+R package.
+}
+\note{
+This function must be executed from the root directory of the package
+ you wish to add the input to.
+}
diff --git a/man/scaffoldReactWidget.Rd b/man/scaffoldReactWidget.Rd
index 4b8fb1c..0ccd69b 100644
--- a/man/scaffoldReactWidget.Rd
+++ b/man/scaffoldReactWidget.Rd
@@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/scaffold.R
+% Please edit documentation in R/scaffold_widget.R
\name{scaffoldReactWidget}
\alias{scaffoldReactWidget}
\title{Create implementation scaffolding for a React.js-based HTML widget}
diff --git a/srcjs/input.js b/srcjs/input.js
new file mode 100644
index 0000000..4b02cc9
--- /dev/null
+++ b/srcjs/input.js
@@ -0,0 +1,116 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Shiny from 'shiny';
+import $ from 'jquery';
+
+/*
+ * This default receiveMessage implementation expects data to contain whole
+ * configuration and value properties. If either is present, it will be set and
+ * the component will be re-rendered. Because receiveMessage is typically used
+ * by input authors to perform incremental updates, this default implementation
+ * can be overriden by the user with the receiveMessage arguments to
+ * reactShinyInput.
+ */
+function defaultReceiveMessage(el, { configuration, value }) {
+ let dirty = false;
+ if (configuration !== undefined) {
+ this.setInputConfiguration(el, configuration);
+ dirty = true;
+ }
+ if (value !== undefined) {
+ this.setInputValue(el, value);
+ dirty = true;
+ }
+ if (dirty) {
+ this.getCallback(el)();
+ this.render(el);
+ }
+}
+
+const defaultOptions = {
+ receiveMessage: defaultReceiveMessage
+};
+
+/**
+ * Installs a new Shiny input binding based on a React component.
+ *
+ * @param {string} selector - jQuery selector that should identify the set of
+ * container elements within the scope argument of Shiny.InputBinding.find.
+ * @param {string} name - A name such as 'acme.FooInput' that should uniquely
+ * identify the component.
+ * @param {Object} component - React Component, either class or function.
+ * @param {Object} options - Additional configuration options. Supported
+ * options are:
+ * - receiveMessage: Implementation of Shiny.InputBinding to use in place of
+ * the default. Typically overridden as an optimization to perform
+ * incremental value updates.
+ */
+export function reactShinyInput(selector,
+ name,
+ component,
+ options) {
+ options = Object.assign({}, defaultOptions, options);
+ Shiny.inputBindings.register(new class extends Shiny.InputBinding {
+
+ /*
+ * Methods override those in Shiny.InputBinding
+ */
+
+ find(scope) {
+ return $(scope).find(selector);
+ }
+ getValue(el) {
+ return this.getInputValue(el);
+ }
+ setValue(el, value) {
+ this.setInputValue(el, value);
+ this.getCallback(el)();
+ this.render(el);
+ }
+ initialize(el) {
+ $(el).data('value', JSON.parse($(el).next().text()));
+ $(el).data('configuration', JSON.parse($(el).next().next().text()));
+ }
+ subscribe(el, callback) {
+ $(el).data('callback', callback);
+ this.render(el);
+ }
+ unsubscribe(el, callback) {
+ $(el).removeData('callback');
+ ReactDOM.render(null, el);
+ }
+ receiveMessage(el, data) {
+ options.receiveMessage.call(this, el, data);
+ }
+
+ /*
+ * Methods not present in Shiny.InputBinding but accessible to users
+ * through `this` in receiveMessage
+ * */
+
+ getInputValue(el) {
+ return $(el).data('value');
+ }
+ setInputValue(el, value) {
+ $(el).data('value', value);
+ }
+ getInputConfiguration(el) {
+ return $(el).data('configuration');
+ }
+ setInputConfiguration(el, configuration) {
+ $(el).data('configuration', configuration);
+ }
+ getCallback(el) {
+ return $(el).data('callback');
+ }
+ render(el) {
+ const element = React.createElement(component, {
+ configuration: this.getInputConfiguration(el),
+ value: this.getValue(el),
+ setValue: this.setValue.bind(this, el)
+ });
+ ReactDOM.render(element, el);
+ }
+ }, name);
+}
+
diff --git a/srcjs/react-tools.js b/srcjs/react-tools.js
index 015a9d5..0c48508 100644
--- a/srcjs/react-tools.js
+++ b/srcjs/react-tools.js
@@ -1,122 +1,8 @@
-window.reactR = (function () {
- /**
- * Recursively transforms tag, a JSON representation of an instance of a
- * React component and its children, into a React element suitable for
- * passing to ReactDOM.render.
- * @param {Object} components
- * @param {Object} tag
- */
- function hydrate(components, tag) {
- if (typeof tag === 'string') return tag;
- if (tag.name[0] === tag.name[0].toUpperCase()
- && !components.hasOwnProperty(tag.name)) {
- throw new Error("Unknown component: " + tag.name);
- }
- var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,
- args = [elem, tag.attribs];
- for (var i = 0; i < tag.children.length; i++) {
- args.push(hydrate(components, tag.children[i]));
- }
- return React.createElement.apply(React, args);
- }
-
- var defaultOptions = {
- // The name of the property on the root tag to use for the width, if
- // it's updated.
- widthProperty: "width",
- // The name of the property on the root tag to use for the height, if
- // it's updated.
- heightProperty: "height",
- // Whether or not to append the string 'px' to the width and height
- // properties when they change.
- appendPx: false,
- // Whether or not to dynamically update the width and height properties
- // of the last known tag when the computed width and height change in
- // the browser.
- renderOnResize: false
- };
-
- function mergeOptions(options) {
- var merged = {};
- for (var k in defaultOptions) {
- merged[k] = defaultOptions[k];
- }
- for (var k in options) {
- if (!defaultOptions.hasOwnProperty(k)) {
- throw new Error("Unrecognized option: " + k);
- }
- merged[k] = options[k];
- }
- return merged;
- }
-
- function formatDimension(dim, options) {
- if (options.appendPx) {
- return dim + 'px';
- } else {
- return dim;
- }
- }
-
- function isTag(value) {
- return (typeof value === 'object')
- && value.hasOwnProperty('name')
- && value.hasOwnProperty('attribs')
- && value.hasOwnProperty('children');
- }
-
- /**
- * Creates an HTMLWidget that is updated by rendering a React component.
- * React component constructors are made available by specifying them by
- * name in the components object.
- * @param {string} name
- * @param {string} type
- * @param {Object} components
- * @param {Object} options
- */
- function reactWidget(name, type, components, options) {
- var actualOptions = mergeOptions(options);
- HTMLWidgets.widget({
- name: name,
- type: type,
- factory: function (el, width, height) {
- var lastValue,
- instance = {},
- renderValue = (function (value) {
- if (actualOptions.renderOnResize) {
- // value.tag might be a primitive string, in which
- // case there is no attribs property.
- if (typeof value.tag === 'object') {
- value.tag.attribs[actualOptions["widthProperty"]] = formatDimension(width);
- value.tag.attribs[actualOptions["heightProperty"]] = formatDimension(height);
- }
- lastValue = value;
- }
- this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);
- });
- return {
- instance: instance,
- renderValue: renderValue,
- resize: function (newWidth, newHeight) {
- if (actualOptions.renderOnResize) {
- width = newWidth;
- height = newHeight;
- renderValue(lastValue);
- }
- }
- };
- }
- })
- }
-
- return {
- reactWidget: reactWidget,
- hydrate: hydrate,
- __internal: {
- defaultOptions: defaultOptions,
- mergeOptions: mergeOptions,
- formatDimension: formatDimension,
- isTag: isTag
- }
- };
-})()
+import { reactWidget, hydrate } from './widget';
+import { reactShinyInput } from './input';
+
+window.reactR = {
+ reactShinyInput: reactShinyInput,
+ reactWidget: reactWidget,
+ hydrate: hydrate
+};
diff --git a/srcjs/widget.js b/srcjs/widget.js
new file mode 100644
index 0000000..4f8df3d
--- /dev/null
+++ b/srcjs/widget.js
@@ -0,0 +1,112 @@
+/**
+ * Recursively transforms tag, a JSON representation of an instance of a
+ * React component and its children, into a React element suitable for
+ * passing to ReactDOM.render.
+ * @param {Object} components
+ * @param {Object} tag
+ */
+export function hydrate(components, tag) {
+ if (typeof tag === 'string') return tag;
+ if (tag.name[0] === tag.name[0].toUpperCase()
+ && !components.hasOwnProperty(tag.name)) {
+ throw new Error("Unknown component: " + tag.name);
+ }
+ var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,
+ args = [elem, tag.attribs];
+ for (var i = 0; i < tag.children.length; i++) {
+ args.push(hydrate(components, tag.children[i]));
+ }
+ return React.createElement.apply(React, args);
+}
+
+export const defaultOptions = {
+ // The name of the property on the root tag to use for the width, if
+ // it's updated.
+ widthProperty: "width",
+ // The name of the property on the root tag to use for the height, if
+ // it's updated.
+ heightProperty: "height",
+ // Whether or not to append the string 'px' to the width and height
+ // properties when they change.
+ appendPx: false,
+ // Whether or not to dynamically update the width and height properties
+ // of the last known tag when the computed width and height change in
+ // the browser.
+ renderOnResize: false
+};
+
+export function mergeOptions(options) {
+ var merged = {};
+ for (var k in defaultOptions) {
+ merged[k] = defaultOptions[k];
+ }
+ for (var k in options) {
+ if (!defaultOptions.hasOwnProperty(k)) {
+ throw new Error("Unrecognized option: " + k);
+ }
+ merged[k] = options[k];
+ }
+ return merged;
+}
+
+export function formatDimension(dim, options) {
+ if (options.appendPx) {
+ return dim + 'px';
+ } else {
+ return dim;
+ }
+}
+
+export function isTag(value) {
+ return (typeof value === 'object')
+ && value.hasOwnProperty('name')
+ && value.hasOwnProperty('attribs')
+ && value.hasOwnProperty('children');
+}
+
+/**
+ * Creates an HTMLWidget that is updated by rendering a React component.
+ * React component constructors are made available by specifying them by
+ * name in the components object.
+ * @param {string} name
+ * @param {string} type
+ * @param {Object} components
+ * @param {Object} options
+ */
+export function reactWidget(name, type, components, options) {
+ var actualOptions = mergeOptions(options);
+ window.HTMLWidgets.widget({
+ name: name,
+ type: type,
+ factory: function (el, width, height) {
+ var lastValue,
+ instance = {},
+ renderValue = (function (value) {
+ if (actualOptions.renderOnResize) {
+ // value.tag might be a primitive string, in which
+ // case there is no attribs property.
+ if (typeof value.tag === 'object') {
+ value.tag.attribs[actualOptions["widthProperty"]] = formatDimension(width);
+ value.tag.attribs[actualOptions["heightProperty"]] = formatDimension(height);
+ }
+ lastValue = value;
+ }
+ // with functional stateless components this will be null
+ // see https://reactjs.org/docs/react-dom.html#render for more details
+ this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);
+ });
+ return {
+ instance: instance,
+ renderValue: renderValue,
+ resize: function (newWidth, newHeight) {
+ if (actualOptions.renderOnResize) {
+ width = newWidth;
+ height = newHeight;
+ renderValue(lastValue);
+ }
+ }
+ };
+ }
+ })
+}
+
diff --git a/vignettes/input_app.jpg b/vignettes/input_app.jpg
new file mode 100644
index 0000000..91398ab
Binary files /dev/null and b/vignettes/input_app.jpg differ
diff --git a/vignettes/input_sketchpicker.jpg b/vignettes/input_sketchpicker.jpg
new file mode 100644
index 0000000..6fb2af4
Binary files /dev/null and b/vignettes/input_sketchpicker.jpg differ
diff --git a/vignettes/input_sketchpicker.mp4 b/vignettes/input_sketchpicker.mp4
new file mode 100644
index 0000000..97b06dd
Binary files /dev/null and b/vignettes/input_sketchpicker.mp4 differ
diff --git a/vignettes/intro_htmlwidgets.Rmd b/vignettes/intro_htmlwidgets.Rmd
index 107033f..aab0dda 100644
--- a/vignettes/intro_htmlwidgets.Rmd
+++ b/vignettes/intro_htmlwidgets.Rmd
@@ -49,7 +49,7 @@ usethis::create_package("~/sparklines")
# Inject the widget templating
withr::with_dir(
"~/sparklines",
- reactR::scaffoldReactWidget("sparklines", list("react-sparklines" = "^1.7.0"))
+ reactR::scaffoldReactWidget("sparklines", list("react-sparklines" = "^1.7.0"), edit = FALSE)
)
```
@@ -91,9 +91,7 @@ shiny::runApp()
Alternatively, in RStudio, you can open `app.R` and press `Ctrl-Shift-Enter` (`Cmd-Shift-Enter` on macOS). You should see something like the following appear in the Viewer pane:
-```{r echo=FALSE}
-knitr::include_graphics('./widget_app.jpg')
-```
+![](./widget_app.jpg)
## Authoring a React binding
@@ -101,6 +99,8 @@ At this point, we've built some scaffolding for an htmlwidget powered by React.
### First, outline an interface
+> Note that the examples in this section are just to demonstrate API possibilities and need not be pasted into any file.
+
Consider the following example taken from the [react-sparklines documentation](http://borisyankov.github.io/react-sparklines/).
```js
@@ -182,7 +182,7 @@ sparklines <- function(data, ..., width = NULL, height = NULL) {
}
```
-At this point, we define functions that make it easy for the user to create the other components by adding these to `R/reactSparklines.R`
+At this point, we define functions that make it easy for the user to create the other components by adding these to `R/sparklines.R`
```{r}
#' @export
@@ -270,6 +270,8 @@ shinyApp(ui, server)
Now, when you run `shiny::runApp()`, you should see your react-based htmlwidget rendering in **shiny** app!
+![](./widget_app_improved.jpg)
+
## Further learning
This tutorial walked you through the steps taken you create an R interface to the react-sparklines library. The full example package is accessible at . Our intention is keep creating example packages under the organization, so head there if you'd like to see other examples of interfacing with React.
diff --git a/vignettes/intro_inputs.Rmd b/vignettes/intro_inputs.Rmd
new file mode 100644
index 0000000..6062cbb
--- /dev/null
+++ b/vignettes/intro_inputs.Rmd
@@ -0,0 +1,259 @@
+---
+title: "Authoring inputs powered by React with reactR"
+author:
+ - Alan Dipert
+date: "`r Sys.Date()`"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Shiny inputs with reactR}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+```{r, echo=FALSE, include=FALSE}
+knitr::opts_chunk$set(eval = FALSE)
+```
+
+[Shiny](http://shiny.rstudio.com/) comes with a large library of input
+[widgets](https://shiny.rstudio.com/gallery/widget-gallery.html) for collecting
+input from the user and conveying input data to R.
+
+If you want a kind of input *not* provided by Shiny — like a color picker,
+or a different kind of slider — you've always been able to build your own.
+Shiny's input system is
+[extensible](https://shiny.rstudio.com/articles/building-inputs.html). All
+that's required is an understanding of certain conventions and a little custom
+JavaScript.
+
+reactR provides additional tools to ease the creation of new Shiny inputs
+implemented using React. In the following tutorial, we will demonstrate these
+tools by implementing a new Shiny color picker input that wraps the
+[react-color](https://github.com/casesandberg/react-color) library.
+
+## Software pre-requisites
+
+In order to develop a **reactR** Shiny input, you'll need to install R and
+optionally RStudio. If you're on Windows, you should also install
+[Rtools](https://cran.r-project.org/bin/windows/Rtools/).
+
+> For an excellent general introduction to R package concepts, check out the [R
+> packages](http://r-pkgs.had.co.nz/) online book.
+
+In addition, you'll need to install the following JavaScript tools on your
+machine:
+
+* [Node.js](https://nodejs.org): JavaScript engine and runtime for development
+ outside of browsers. Provides the `node` and `npm` commands.
+* [Yarn](https://yarnpkg.com/en/): Command-line dependency management tool,
+ provides the `yarn` command.
+
+To follow along in this vignette, you'll also need the following R packages:
+
+```{r}
+install.packages(c("shiny", "devtools", "usethis", "reactR"))
+```
+
+## Scaffolding
+
+To create a new widget you can call `scaffoldReactShinyInput` to generate the basic
+structure and build configuration. This function will:
+
+* Create the .R, .js, and .json files required by your input;
+* If provided, take an [npm](https://www.npmjs.com/) package name and version as
+ a named list with `name` and `version` elements. For example, the npm package
+ `foo` at version `^1.2.0` would be expressed as `list(name = "foo", version =
+ "^1.2.0")`. The package, if provided, will be added to the new widget's
+ `package.json` as a build dependency.
+
+The following R code will create an R package named **colorpicker**, then
+provide the templating for creating an input powered by the
+`react-color` library on npm:
+
+```{r}
+# Create the R package
+usethis::create_package("~/colorpicker")
+# Scaffold initial input implementation files
+withr::with_dir(
+ "~/colorpicker",
+ reactR::scaffoldReactShinyInput("colorpicker", list("react-color" = "^2.17.0"), edit = FALSE)
+)
+```
+
+## Building and installing
+
+### Building the JavaScript
+
+The next step is to navigate to the newly-created `colorpicker` project and run
+the following commands in the terminal:
+
+```
+yarn install
+yarn run webpack
+```
+
+* `yarn install` downloads all of the dependencies listed in `package.json` and
+ creates a new file, `yarn.lock`. You should add this file to revision control.
+ It will be updated whenever you change dependencies and run `yarn install`.
+ **Note: you only need to run it after modifying package.json**. For further
+ documentation on `yarn install`, see the [yarn
+ documentation](https://yarnpkg.com/lang/en/docs/cli/install/).
+
+* `yarn run webpack` compiles the [modern JavaScript](https://babeljs.io/docs/en/babel-preset-env)
+ with [JSX](https://babeljs.io/docs/en/babel-preset-react) source file at `srcjs/colorpicker.jsx` into
+ `www/colorpicker/colorpicker/colorpicker.js`. The latter file is the one
+ actually used by the R package and includes all the relevant JavaScript
+ dependencies in a dialect of JavaScript that most browsers understand.
+
+`yarn run webpack` is not strictly a `yarn` command. In fact, `yarn run` simply
+delegates to the [webpack](https://webpack.js.org/) program. Webpack's
+configuration is generated by `scaffoldReactShinyInput` in the file
+`webpack.config.js`, but you can always change this configuration and/or modify
+the `yarn run webpack` command to suit your needs.
+
+### Installing the R package
+
+Now that the input's JavaScript is compiled, go ahead and install the R
+package:
+
+```{r}
+devtools::document()
+devtools::install(quick = TRUE)
+```
+
+In RStudio, you can use the keyboard shortcuts `Ctrl-Shift-D` and
+`Ctrl-Shift-B` to document and build the package. (On macOS, the shortcuts are
+`Cmd-Shift-D` and `Cmd-Shift-B`)
+
+## Run the included demo
+
+Now that the input's JavaScript is compiled, and the R package is installed,
+run `app.R` to see a demo in action:
+
+```{r}
+shiny::runApp()
+```
+
+In RStudio, you can open `app.R` and press `Ctrl-Shift-Enter`
+(`Cmd-Shift-Enter` on macOS). You should see something like the following appear
+in the Viewer pane:
+
+![](./input_app.jpg)
+
+## Authoring a React input
+
+At this point, we have a working (if simple) React-powered text input.
+Let's modify it to create an interface to the `react-color` library.
+
+### Connecting Shiny with React
+
+Consider the following example taken from the [react-color
+documentation](http://casesandberg.github.io/react-color/).
+
+```js
+import React from 'react';
+import { SketchPicker } from 'react-color';
+
+class Component extends React.Component {
+
+ render() {
+ return ;
+ }
+}
+```
+
+That JavaScript code produces a `SketchPicker`-type interface that looks like
+this:
+
+![](./input_sketchpicker.jpg)
+
+However, that example doesn't demonstrate a way to default to a particular
+color, or a way to cause something to happen when the color changes. To
+accomplish these, `react-color` components can [optionally
+take](http://casesandberg.github.io/react-color/#api) the following
+[props](https://reactjs.org/docs/components-and-props.html):
+
+* `color`: accepts a string of a hex color like `'#333'`
+* `onChangeComplete`: accepts a function taking a single argument, the new
+ color, that will be called when the new color is selected
+
+These operations are conceptually similar enough to the API expected of Shiny
+inputs that `reactR` can assist with integrating React components into Shiny
+as inputs.
+
+It does so by introducing a convention for wrapping components like those
+provided by `react-color` with an intermediate component that accepts these
+props:
+
+* `configuration`: A configuration object containing data from R used to
+ parameterize the input's behavior
+* `value`: The input's values over time, beginning with the default
+* `setValue`: A function to call with the input's new value when one is created
+
+The `configuration` and `value` props are initially populated on the R side, as
+arguments to the `createReactShinyInput` function inside the input's constructor
+function. In the case of our newly-scaffolded input, that happens in
+`R/colorpicker.R`.
+
+The `setValue` function is what causes new values to be sent to R, and also what
+triggers the "intermediate" component to repaint itself.
+
+So, in order to make the components delivered by `react-color` accessible on the
+R side, we must create our own intermediate component that wraps one of
+`react-color`'s pickers.
+
+### Create intermediate component
+
+Open `srcjs/colorpicker.jsx` and paste the following in:
+
+```js
+import { reactShinyInput } from 'reactR';
+import { SketchPicker } from 'react-color';
+
+const PickerInput = ({ configuration, value, setValue }) => {
+ return (
+ setValue(color.hex) }
+ />
+ );
+};
+
+reactShinyInput('.colorpicker', 'colorpicker', PickerInput);
+```
+
+The above code creates a new [function
+component](https://reactjs.org/docs/components-and-props.html#function-and-class-components)
+called `PickerInput` that expects the props supplied by reactR and renders a
+parameterized `SketchPicker` from `react-color`. The `configuration` value is
+not yet used.
+
+After saving the file, run `yarn run webpack` in the terminal and rebuild the
+package.
+
+## Trying it out
+
+After rebuilding the JavaScript and the package, try running `app.R` again. You
+should see something like this:
+
+
+
+When you select new colors, you should see the `textOutput` update accordingly.
+
+You might have noticed that the input showed up initially without a color
+selected. That's because in `app.R` we didn't supply a `default` argument to the
+`colorpickerInput` function inside our `ui`.
+
+Try replacing the call to `colorpickerInput` with this:
+`colorpickerInput("textInput", default = "#a76161")`
+
+Now when you run the app, the color should start as a shade of red.
+
+## Further learning
+
+This tutorial walked you through the steps taken to wrap the `react-color`
+library in a Shiny input. The full example package is accessible at
+. Our intention is keep creating
+example packages under the organization, so head
+there if you'd like to see other examples of interfacing with React.
diff --git a/yarn.lock b/yarn.lock
index 324bee2..bba0957 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3758,15 +3758,15 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-dom@^16.7.0:
- version "16.8.1"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.1.tgz#ec860f98853d09d39bafd3a6f1e12389d283dbb4"
- integrity sha512-N74IZUrPt6UiDjXaO7UbDDFXeUXnVhZzeRLy/6iqqN1ipfjrhR60Bp5NuBK+rv3GMdqdIuwIl22u1SYwf330bg==
+react-dom@^16.8.1:
+ version "16.8.3"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.3.tgz#ae236029e66210783ac81999d3015dfc475b9c32"
+ integrity sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
- scheduler "^0.13.1"
+ scheduler "^0.13.3"
react-html-parser@^2.0.2:
version "2.0.2"
@@ -3780,15 +3780,15 @@ react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb"
integrity sha512-ioMCzVDWvCvKD8eeT+iukyWrBGrA3DiFYkXfBsVYIRdaREZuBjENG+KjrikavCLasozqRWTwFUagU/O4vPpRMA==
-react@^16.7.0:
- version "16.8.1"
- resolved "https://registry.yarnpkg.com/react/-/react-16.8.1.tgz#ae11831f6cb2a05d58603a976afc8a558e852c4a"
- integrity sha512-wLw5CFGPdo7p/AgteFz7GblI2JPOos0+biSoxf1FPsGxWQZdN/pj6oToJs1crn61DL3Ln7mN86uZ4j74p31ELQ==
+react@^16.8.1:
+ version "16.8.3"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.8.3.tgz#c6f988a2ce895375de216edcfaedd6b9a76451d9"
+ integrity sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
- scheduler "^0.13.1"
+ scheduler "^0.13.3"
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6"
@@ -4007,10 +4007,10 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-scheduler@^0.13.1:
- version "0.13.1"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.1.tgz#1a217df1bfaabaf4f1b92a9127d5d732d85a9591"
- integrity sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==
+scheduler@^0.13.3:
+ version "0.13.3"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.3.tgz#bed3c5850f62ea9c716a4d781f9daeb9b2a58896"
+ integrity sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"