Advanced topics (original) (raw)
Overview
This article covers several aspects of creating widgets that are not required by all widgets, but are an essential part of getting bindings to certain types of JavaScript libraries to work properly. Topics covered include:
- Transforming JSON representations of R objects into representations required by JavaScript libraries (e.g. an R data frame to a d3 dataset).
- Tracking instance-specific widget data within JavaScript bindings.
- Passing JavaScript functions from R to JavaScript (e.g. a user provided formatting or drawing function)
- Generating custom HTML to enclose a widget (the default is a
<div>
but some libraries require a different element e.g. a<span>
).
Data transformation
R objects passed as part of the x
parameter to the createWidget()
function are transformed to JSON using the internal function htmlwidgets:::toJSON()
1, which is basically a wrapper function of jsonlite::toJSON()
by default. However, sometimes this representation is not what is required by the JavaScript library you are interfacing with. There are two JavaScript functions that you can use to transform the JSON data.
Custom JSON serializer
You may find it necessary to customize the JSON serialization of widget data when the default serializer in htmlwidgets does not work in the way you have expected. For widget package authors, there are two levels of customization for the JSON serialization: you can either customize the default values of arguments for jsonlite::toJSON()
, or just customize the whole function.
jsonlite::toJSON()
has a lot of arguments, and we have already changed some of its default values. Below is the JSON serializer we use in htmlwidgets at the moment:
function (x, ..., dataframe = "columns", null = "null", na = "null",
auto_unbox = TRUE, digits = getOption("shiny.json.digits",
16), use_signif = TRUE, force = TRUE, POSIXt = "ISO8601",
UTC = TRUE, rownames = FALSE, keep_vec_names = TRUE, strict_atomic = TRUE)
{
if (strict_atomic)
x <- I(x)
jsonlite::toJSON(x, dataframe = dataframe, null = null, na = na,
auto_unbox = auto_unbox, digits = digits, use_signif = use_signif,
force = force, POSIXt = POSIXt, UTC = UTC, rownames = rownames,
keep_vec_names = keep_vec_names, json_verbatim = TRUE,
...)
}
<bytecode: 0x559bc7acd648>
For example, we convert data frames to JSON by columns instead of rows (the latter is jsonlite::toJSON
’s default). If you want to change the default values of any arguments, you can attach an attribute TOJSON_ARGS
to the widget data to be passed to createWidget()
, e.g.
fooWidget <- function(data, name, ...) {
# ... process the data ...
params <- list(foo = data, bar = TRUE)
# customize toJSON() argument values
attr(params, 'TOJSON_ARGS') <- list(digits = 7, na = 'string')
htmlwidgets::createWidget(name, x = params, ...)
}
We changed the default value of digits
from 16 to 7, and na
from null
to string
in the above example. It is up to you, the package author, whether you want to expose such customization to users. For example, you can leave an extra argument in your widget function so that users can customize the behavior of the JSON serializer:
fooWidget <- function(data, name, ..., JSONArgs = list(digits = 7)) {
# ... process the data ...
params <- list(foo = data, bar = TRUE)
# customize toJSON() argument values
attr(params, 'TOJSON_ARGS') <- JSONArgs
htmlwidgets::createWidget(name, x = params, ...)
}
You can also use a global option htmlwidgets.TOJSON_ARGS
to customize the JSON serializer arguments for all widgets in the current R session, e.g.
options(htmlwidgets.TOJSON_ARGS = list(digits = 7, pretty = TRUE))
- If you do not want to use jsonlite, you can completely override the serializer function by attaching an attribute
TOJSON_FUNC
to the widget data, e.g.
fooWidget <- function(data, name, ...) {
# ... process the data ...
params <- list(foo = data, bar = TRUE)
# customize the JSON serializer
attr(params, 'TOJSON_FUNC') <- MY_OWN_JSON_FUNCTION
htmlwidgets::createWidget(name, x = params, ...)
}
Here MY_OWN_JSON_FUNCTION
can be an arbitrary R function that converts R objects to JSON. If you have also specified the TOJSON_ARGS
attribute, it will be passed to your custom JSON function as well.
Note these features about custom JSON serializers require the shiny version to be greater than 0.11.1 if you render the widgets in Shiny apps.
Passing JavaScript functions
As you would expect, character vectors passed from R to JavaScript are converted to JavaScript strings. However, what if you want to allow users to provide custom JavaScript functions for formatting, drawing, or event handling? For this case, the htmlwidgets package includes a JS()
function that allows you to request that a character value is evaluated as JavaScript when it is received on the client.
For example, the dygraphs widget includes a dyCallbacks
function that allows the user to provide callback functions for a variety of contexts. These callbacks are “marked” as containing JavaScript so that they can be converted to actual JavaScript functions on the client:
callbacks <- list(
clickCallback = JS(clickCallback)
drawCallback = JS(drawCallback)
highlightCallback = JS(highlightCallback)
pointClickCallback = JS(pointClickCallback)
underlayCallback = JS(underlayCallback)
)
Another example is in the DT (DataTables) widget, where users can specify an initCallback
with JavaScript to execute after the table is loaded and initialized:
datatable(head(iris, 20), options = list(
initComplete = JS(
"function(settings, json) {",
"$(this.api().table().header()).css({'background-color': '#000', 'color': '#fff'});",
"}")
))
If multiple arguments are passed to JS()
(as in the above example), they will be concatenated into a single string separated by \n
.