#' Extend shinyjs by calling your own JavaScript functions
#'
#' Add your own JavaScript functions that can be called from R as if they were
#' regular R functions. This is a more advanced technique and can only
#' be used if you know JavaScript. See 'Basic Usage' below for more information
#' or \href{http://deanattali.com/shinyjs}{view the shinyjs webpage}
#' to learn more.
#'
#' @param script Path to a JavaScript file that contains all the functions.
#' Each function name must begin with `shinyjs.`, for example
#' `shinyjs.myfunc`. See 'Basic Usage' below.
#' @param text Inline JavaScript code to use. If your JavaScript function is very
#' short and you don't want to create a separate file for it, you can provide the
#' code as a string. See 'Basic Usage' below.
#' @param functions The names of the shinyjs JavaScript functions which you defined and
#' want to be able to call using \code{shinyjs}. Only use this argument if you cannot
#' install \code{V8} on your machine. I repeat: do not use this argument if you're
#' able to install \code{V8} on your machine. For example, if you defined JavaScript functions
#' named \code{shinyjs.foo} and \code{shinyjs.bar}, then use \code{functions = c("foo", "bar")}.
#'
#' @section Basic Usage:
#' Any JavaScript function defined in your script that begins with `shinyjs.`
#' will be available to run from R through the `js$` variable. For example,
#' if you write a JavaScript function called `shinyjs.myfunc`, then you can
#' call it in R with `js$myfunc()`.
#'
#' It's recommended to write JavaScript code in a separate file and provide the
#' filename as the \code{script} argument, but it's also possible to use the
#' \code{text} argument to provide a string containing valid JavaScript code. Using the
#' \code{text} argument is meant to be used when your JavaScript code is very short
#' and simple.
#'
#' As a simple example, here is a basic example of using \code{extendShinyjs}
#' to define a function that changes the colour of the page.
#'
#' \preformatted{
#' library(shiny)
#' library(shinyjs)
#'
#' jsCode <- "shinyjs.pageCol = function(params){$('body').css('background', params);}"
#'
#' shinyApp(
#' ui = fluidPage(
#' useShinyjs(),
#' extendShinyjs(text = jsCode),
#' selectInput("col", "Colour:",
#' c("white", "yellow", "red", "blue", "purple"))
#' ),
#' server = function(input, output) {
#' observeEvent(input$col, {
#' js$pageCol(input$col)
#' })
#' }
#' )
#' }
#'
#' As the example above shows, after defining the JavaScript function
#' \code{shinyjs.pageCol} and passing it to \code{extendShinyjs}, it's possible
#' to call \code{js$pageCol()}.
#'
#' You can add more functions to the JavaScript code, but remember that every
#' function you want to use in R has to have a name beginning with
#' `shinyjs.`. See the section on passing arguments and the examples below
#' for more information on how to write effective functions.
#'
#' @section Running JavaScript code on page load:
#' If there is any JavaScript code that you want to run immediately when the page loads
#' rather than having to call it from the server, you can place it inside a
#' \code{shinyjs.init} function. The function \code{shinyjs.init}
#' will automatically be called when the Shiny app's HTML is initialized. A common
#' use for this is when registering event handlers or initializing JavaScript objects,
#' as these usually just need to run once when the page loads.
#'
#' For example, the following example uses \code{shinyjs.init} to register an event
#' handler so that every keypress will print its corresponding key code:
#'
#' \preformatted{
#' jscode <- "
#' shinyjs.init = function() {
#' $(document).keypress(function(e) { alert('Key pressed: ' + e.which); });
#' }"
#' shinyApp(
#' ui = fluidPage(
#' useShinyjs(),
#' extendShinyjs(text = jscode),
#' "Press any key"
#' ),
#' server = function(input, output) {}
#' )
#' }
#'
#' @section Passing arguments from R to JavaScript:
#' Any \code{shinyjs} function that is called will pass a single array-like
#' parameter to its corresponding JavaScript function. If the function in R was
#' called with unnamed arguments, then it will pass an Array of the arguments;
#' if the R arguments are named then it will pass an Object with key-value pairs.
#'
#' For example, calling \code{js$foo("bar", 5)} in R will call \code{shinyjs.foo(["bar", 5])}
#' in JS, while calling \code{js$foo(num = 5, id = "bar")} in R will call
#' \code{shinyjs.foo({num : 5, id : "bar"})} in JS. This means that the
#' \code{shinyjs.foo} function needs to be able to deal with both types of
#' parameters.
#'
#' To assist in normalizing the parameters, \code{shinyjs} provides a
#' \code{shinyjs.getParams()} function which serves two purposes. First of all,
#' it ensures that all arguments are named (even if the R function was called
#' without names). Secondly, it allows you to define default values for arguments.
#'
#' Here is an example of a JS function that changes the background colour of an
#' element and uses \code{shinyjs.getParams()}.
#'
#' \preformatted{
#' shinyjs.backgroundCol = function(params) {
#' var defaultParams = {
#' id : null,
#' col : "red"
#' };
#' params = shinyjs.getParams(params, defaultParams);
#'
#' var el = $("#" + params.id);
#' el.css("background-color", params.col);
#' }
#' }
#'
#' Note the \code{defaultParams} object that was defined and the call to
#' \code{shinyjs.getParams}. It ensures that calling \code{js$backgroundCol("test", "blue")}
#' and \code{js$backgroundCol(id = "test", col = "blue")} and
#' \code{js$backgroundCol(col = "blue", id = "test")} are all equivalent, and
#' that if the colour parameter is not provided then "red" will be the default.
#'
#' All the functions provided in \code{shinyjs} make use of \code{shinyjs.getParams},
#' and it is highly recommended to always use it in your functions as well.
#' Notice that the order of the arguments in \code{defaultParams} in the
#' JavaScript function matches the order of the arguments when calling the
#' function in R with unnamed arguments.
#'
#' See the examples below for a shiny app that uses this JS function.
#' @return Scripts that \code{shinyjs} requires in order to run your JavaScript
#' functions as if they were R code.
#' @note You still need to call \code{useShinyjs()} as usual, and the call to
#' \code{useShinyjs()} must come before the call to \code{extendShinyjs()}.
#' @note The \code{V8} package is strongly recommended if you use this function.
#' @note If you are deploying your app to shinyapps.io and are using \code{extendShinyjs()},
#' then you need to let shinyapps.io know that the \code{V8} package is required.
#' The easiest way to do this is by simply including \code{library(V8)} somewhere.
#' This is an issue with shinyapps.io that might be resolved by them in the future --
#' see \href{https://github.com/daattali/shinyjs/issues/20}{here} for more details.
#' @seealso \code{\link[shinyjs]{runExample}}
#' @examples
#' \dontrun{
#' Example 1:
#' Change the page background to a certain colour when a button is clicked.
#'
#' jsCode <- "shinyjs.pageCol = function(params){$('body').css('background', params);}"
#'
#' shinyApp(
#' ui = fluidPage(
#' useShinyjs(),
#' extendShinyjs(text = jsCode),
#' selectInput("col", "Colour:",
#' c("white", "yellow", "red", "blue", "purple"))
#' ),
#' server = function(input, output) {
#' observeEvent(input$col, {
#' js$pageCol(input$col)
#' })
#' }
#' )
#'
#' # If you do not have `V8` package installed, you will need to add another
#' # argument to the `extendShinyjs()` function:
#' # extendShinyjs(text = jsCode, functions = c("pageCol"))
#'
#' ==============
#'
#' Example 2:
#' Change the background colour of an element, using "red" as default
#'
#' jsCode <- '
#' shinyjs.backgroundCol = function(params) {
#' var defaultParams = {
#' id : null,
#' col : "red"
#' };
#' params = shinyjs.getParams(params, defaultParams);
#'
#' var el = $("#" + params.id);
#' el.css("background-color", params.col);
#' }'
#'
#' shinyApp(
#' ui = fluidPage(
#' useShinyjs(),
#' extendShinyjs(text = jsCode),
#' p(id = "name", "My name is Dean"),
#' p(id = "sport", "I like soccer"),
#' selectInput("col", "Colour:",
#' c("white", "yellow", "red", "blue", "purple")),
#' textInput("selector", "Element", "sport"),
#' actionButton("btn", "Go")
#' ),
#' server = function(input, output) {
#' observeEvent(input$btn, {
#' js$backgroundCol(input$selector, input$col)
#' })
#' }
#' )
#'
#' ==============
#'
#' Example 3:
#' Create an `increment` function that increments the number inside an HTML
#' tag (increment by 1 by default, with an optional parameter). Use a separate
#' file instead of providing the JS code in a string.
#'
#' Create a JavaScript file "myfuncs.js":
#' shinyjs.increment = function(params) {
#' var defaultParams = {
#' id : null,
#' num : 1
#' };
#' params = shinyjs.getParams(params, defaultParams);
#'
#' var el = $("#" + params.id);
#' el.text(parseInt(el.text()) + params.num);
#' }
#'
#' And a shiny app that uses the custom function we just defined. Note how
#' the arguments can be either passed as named or unnamed, and how default
#' values are set if no value is given to a parameter.
#'
#' library(shiny)
#' shinyApp(
#' ui = fluidPage(
#' useShinyjs(),
#' extendShinyjs("myfuncs.js"),
#' p(id = "number", 0),
#' actionButton("add", "js$increment('number')"),
#' actionButton("add5", "js$increment('number', 5)"),
#' actionButton("add10", "js$increment(num = 10, id = 'number')")
#' ),
#' server = function(input, output) {
#' observeEvent(input$add, {
#' js$increment('number')
#' })
#' observeEvent(input$add5, {
#' js$increment('number', 5)
#' })
#' observeEvent(input$add10, {
#' js$increment(num = 10, id = 'number')
#' })
#' }
#' )
#' }
#' @export
extendShinyjs <- function(script, text, functions) {
if (missing(script) && missing(text)) {
errMsg("Either `script` or `text` need to be provided.")
}
# if V8 is not installed, the user must provide the JS function names
if (!requireNamespace("V8", quietly = TRUE)) {
if (missing(functions)) {
errMsg(paste0("V8 package is required to use `extendShinyjs`. Please install it ",
"with `install.packages(\"V8\")`.\nIf you cannot successfully install ",
"V8 on your machine, you need to use the `functions` argument."))
}
jsFuncs <- functions
}
# if V8 is installed (preferable method), parse the input for JS functions
else {
# create a js context with a `shinyjs` object that user-defined functions
# can populate
ct <- V8::new_context(NULL, FALSE, FALSE)
ct$assign("shinyjs", c())
# read functions from a script
if (!missing(script)) {
if (!file.exists(script)) {
errMsg(sprintf("Could not find JavaScript file `%s`.", script))
}
tryCatch({
ct$source(script)
}, error = function(err) {
errMsg(sprintf("Error parsing the JavaScript file: %s.", err$message))
})
}
# read functions from in-line text
if (!missing(text)) {
tryCatch({
ct$eval(text)
}, error = function(err) {
errMsg(sprintf("Error parsing the JavaScript code provided.", err$message))
})
}
# find out what functions the user defined
jsFuncs <- ct$get(V8::JS("Object.keys(shinyjs)"))
if (length(jsFuncs) == 0) {
errMsg(paste0("Could not find any shinyjs functions in the JavaScript file. ",
"Did you remember to prepend every function's name with `shinyjs.`?"))
}
}
# add all the given functions to the shinyjs namespace so that they can be
# called as if they were regular shinyjs functions
lapply(jsFuncs, function(x) {
assign(x, jsFunc, js)
})
# Add the script as a resource
if (!missing(script)) {
shiny::addResourcePath("shinyjs-extend", dirname(script))
script <- file.path("shinyjs-extend", basename(script))
}
# set up the message handlers for all functions
setupJS(jsFuncs, script, text)
}
#' Call user-defined JavaScript functions from R
#' @seealso \code{\link[shinyjs]{extendShinyjs}}
#' @export
#' @keywords internal
js <- new.env()
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.