From c998faebcac70a5f442f21dc9d48562537ad1e5a Mon Sep 17 00:00:00 2001 From: Carson Sievert Date: Wed, 13 Mar 2019 13:19:53 -0500 Subject: [PATCH] Try evaling js code client-side both with and without parentheses (#332) * Try evaling jsHook verbatim. If that fails, try evaling in parentheses. Fixes #329 * throw existing error object rather than creating new ones * tweak documentation of jsCode parameter * if second try at eval is SyntaxError, then throw original error * wrap eval strategy in a reusable function and leverage in evaluateStringMember * update news; better comment; roxygenize --- DESCRIPTION | 2 +- R/htmlwidgets.R | 4 ++-- inst/NEWS | 6 ++++++ inst/www/htmlwidgets.js | 29 +++++++++++++++++++++++++++-- man/onRender.Rd | 4 ++-- man/saveWidget.Rd | 3 ++- man/sizingPolicy.Rd | 7 ++++--- 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 28b6d124..70c489fe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,4 +25,4 @@ Suggests: Enhances: shiny (>= 1.1) URL: https://github.com/ramnathv/htmlwidgets BugReports: https://github.com/ramnathv/htmlwidgets/issues -RoxygenNote: 6.0.1 +RoxygenNote: 6.1.1 diff --git a/R/htmlwidgets.R b/R/htmlwidgets.R index a77d095b..aff81807 100644 --- a/R/htmlwidgets.R +++ b/R/htmlwidgets.R @@ -94,8 +94,8 @@ appendContent <- function(x, ...) { #' multiple objects to pass to the function, use a named list. #' @return The modified widget object #' -#' @details The \code{jsCode} parameter must be a valid JavaScript expression -#' that returns a function. +#' @details The \code{jsCode} parameter must contain valid JavaScript code which +#' when evaluated returns a function. #' #' The function will be invoked with three arguments: the first is the widget's #' main HTML element, and the second is the data to be rendered (the \code{x} diff --git a/inst/NEWS b/inst/NEWS index cc23c1f7..b3cf2296 100644 --- a/inst/NEWS +++ b/inst/NEWS @@ -1,3 +1,9 @@ +htmlwidgets 1.4 +----------------------------------------------------------------------- + +* JavaScript statements can now be passed along to `onRender()` and `JS()` (#329). + + htmlwidgets 1.3 ----------------------------------------------------------------------- diff --git a/inst/www/htmlwidgets.js b/inst/www/htmlwidgets.js index ed9837d9..ffb9e706 100644 --- a/inst/www/htmlwidgets.js +++ b/inst/www/htmlwidgets.js @@ -233,7 +233,7 @@ theseArgs = theseArgs.concat([task.data]); task = task.code; } - var taskFunc = eval("(" + task + ")"); + var taskFunc = tryEval(task); if (typeof(taskFunc) !== "function") { throw new Error("Task must be a function! Source:\n" + task); } @@ -242,6 +242,31 @@ } } + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval(code); + } catch(error) { + if (!error instanceof SyntaxError) { + throw error; + } + try { + result = eval("(" + code + ")"); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + function initSizing(el) { var sizing = sizingPolicy(el); if (!sizing) @@ -732,7 +757,7 @@ if (o !== null && typeof o === "object" && part in o) { if (i == (l - 1)) { // if we are at the end of the line then evalulate if (typeof o[part] === "string") - o[part] = eval("(" + o[part] + ")"); + o[part] = tryEval(o[part]); } else { // otherwise continue to next embedded object o = o[part]; } diff --git a/man/onRender.Rd b/man/onRender.Rd index b5c1f3e7..7a2e5b38 100644 --- a/man/onRender.Rd +++ b/man/onRender.Rd @@ -24,8 +24,8 @@ logic with additional custom JavaScript code, just for this specific widget object. } \details{ -The \code{jsCode} parameter must be a valid JavaScript expression - that returns a function. +The \code{jsCode} parameter must contain valid JavaScript code which + when evaluated returns a function. The function will be invoked with three arguments: the first is the widget's main HTML element, and the second is the data to be rendered (the \code{x} diff --git a/man/saveWidget.Rd b/man/saveWidget.Rd index 8f2ac02c..d5555625 100644 --- a/man/saveWidget.Rd +++ b/man/saveWidget.Rd @@ -5,7 +5,8 @@ \title{Save a widget to an HTML file} \usage{ saveWidget(widget, file, selfcontained = TRUE, libdir = NULL, - background = "white", title = class(widget)[[1]], knitrOptions = list()) + background = "white", title = class(widget)[[1]], + knitrOptions = list()) } \arguments{ \item{widget}{Widget to save} diff --git a/man/sizingPolicy.Rd b/man/sizingPolicy.Rd index 59494646..39ff985e 100644 --- a/man/sizingPolicy.Rd +++ b/man/sizingPolicy.Rd @@ -4,9 +4,10 @@ \alias{sizingPolicy} \title{Create a widget sizing policy} \usage{ -sizingPolicy(defaultWidth = NULL, defaultHeight = NULL, padding = NULL, - viewer.defaultWidth = NULL, viewer.defaultHeight = NULL, - viewer.padding = NULL, viewer.fill = TRUE, viewer.suppress = FALSE, +sizingPolicy(defaultWidth = NULL, defaultHeight = NULL, + padding = NULL, viewer.defaultWidth = NULL, + viewer.defaultHeight = NULL, viewer.padding = NULL, + viewer.fill = TRUE, viewer.suppress = FALSE, viewer.paneHeight = NULL, browser.defaultWidth = NULL, browser.defaultHeight = NULL, browser.padding = NULL, browser.fill = FALSE, browser.external = FALSE,