Skip to content

Commit

Permalink
Add ability for onRender to take arbitrary R data
Browse files Browse the repository at this point in the history
  • Loading branch information
jcheng5 committed Apr 16, 2016
1 parent 34d9cd0 commit 63e3752
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 13 deletions.
50 changes: 43 additions & 7 deletions R/htmlwidgets.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,23 @@ appendContent <- function(x, ...) {
#'
#' @param x An HTML Widget object
#' @param jsCode Character vector containing JavaScript code (see Details)
#' @param data An additional argument to pass to the \code{jsCode} function.
#' This can be any R object that can be serialized to JSON. If you have
#' 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.
#'
#' The function will be invoked with two arguments: the first is the widget's
#' 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}
#' parameter in \code{createWidget}). When the function is invoked, the
#' \code{this} will be the widget instance object.
#' parameter in \code{createWidget}). The third argument is the JavaScript
#' equivalent of the R object passed into \code{onRender} as the \code{data}
#' argument; this is an easy way to transfer e.g. data frames without having
#' to manually do the JSON encoding.
#'
#' When the function is invoked, the \code{this} keyword will refer to the
#' widget instance object.
#'
#' @seealso \code{\link{onStaticRenderComplete}}, for writing custom JavaScript
#' that involves multiple widgets.
Expand All @@ -93,28 +101,56 @@ appendContent <- function(x, ...) {
#' \dontrun{
#' library(leaflet)
#'
#' # This example uses browser geolocation. RStudio users:
#' # this won't work in the Viewer pane; try popping it
#' # out into your system web browser.
#' leaflet() %>% addTiles() %>%
#' onRender("
#' function(el, x) {
#' // Navigate the map to the user's location
#' this.locate({setView: true});
#' }
#' ")
#'
#'
#' # This example shows how you can make an R data frame available
#' # to your JavaScript code.
#'
#' meh <- "&#x1F610;";
#' yikes <- "&#x1F628;";
#'
#' df <- data.frame(
#' lng = quakes$long,
#' lat = quakes$lat,
#' html = ifelse(quakes$mag < 5.5, meh, yikes),
#' stringsAsFactors = FALSE
#' )
#'
#' leaflet() %>% addTiles() %>%
#' fitBounds(min(df$lng), min(df$lat), max(df$lng), max(df$lat)) %>%
#' onRender("
#' function(el, x, data) {
#' for (var i = 0; i < data.lng.length; i++) {
#' var icon = L.divIcon({className: '', html: data.html[i]});
#' L.marker([data.lat[i], data.lng[i]], {icon: icon}).addTo(this);
#' }
#' }
#' ", data = df)
#' }
#'
#' @export
onRender <- function(x, jsCode) {
addHook(x, "render", jsCode)
onRender <- function(x, jsCode, data = NULL) {
addHook(x, "render", jsCode, data)
}

addHook <- function(x, hookName, jsCode) {
addHook <- function(x, hookName, jsCode, data = NULL) {
if (length(jsCode) == 0)
return(x)

if (length(jsCode) > 1)
jsCode <- paste(jsCode, collapse = "\n")

x$jsHooks[[hookName]] <- c(x$jsHooks[[hookName]], list(jsCode))
x$jsHooks[[hookName]] <- c(x$jsHooks[[hookName]], list(list(code = jsCode, data = data)))
x
}

Expand Down
13 changes: 11 additions & 2 deletions inst/www/htmlwidgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,19 +216,28 @@

// @param tasks Array of strings (or falsy value, in which case no-op).
// Each element must be a valid JavaScript expression that yields a
// function.
// function. Or, can be an array of objects with "code" and "data"
// properties; in this case, the "code" property should be a string
// of JS that's an expr that yields a function, and "data" should be
// an object that will be added as an additional argument when that
// function is called.
// @param target The object that will be "this" for each function
// execution.
// @param args Array of arguments to be passed to the functions. (The
// same arguments will be passed to all functions.)
function evalAndRun(tasks, target, args) {
if (tasks) {
forEach(tasks, function(task) {
var theseArgs = args;
if (typeof(task) === "object") {
theseArgs = theseArgs.concat([task.data]);
task = task.code;
}
var taskFunc = eval("(" + task + ")");
if (typeof(taskFunc) !== "function") {
throw new Error("Task must be a function! Source:\n" + task);
}
taskFunc.apply(target, args);
taskFunc.apply(target, theseArgs);
});
}
}
Expand Down
45 changes: 41 additions & 4 deletions man/onRender.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 63e3752

Please sign in to comment.