diff --git a/.circleci/config.yml b/.circleci/config.yml index 00c63e0c..2d9fe5c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: . venv/bin/activate pip install -e git+https://github.com/plotly/dash.git#egg=dash[testing] export PATH=$PATH:/home/circleci/.local/bin/ - pytest --log-cli-level DEBUG --nopercyfinalize --junitxml=test-reports/dashr.xml tests/integration/ + pytest --nopercyfinalize --junitxml=test-reports/dashr.xml tests/integration/ - store_artifacts: path: test-reports - store_test_results: @@ -55,7 +55,7 @@ jobs: - run: name: 🔎 Unit tests command: | - sudo Rscript -e 'testthat::test_dir("tests/")' + sudo Rscript -e 'res=devtools::test("tests/", reporter=default_reporter());df=as.data.frame(res);if(sum(df$failed) > 0 || any(df$error)) {q(status=1)}' workflows: version: 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4f728f..e9a4e546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Change Log for Dash for R All notable changes to this project will be documented in this file. + +## [0.3.0] - 2020-02-12 +### Added +- Support for config-aware relative paths [#172](https://github.com/plotly/dashR/pull/172) +- Support index customization and index templates [#168](https://github.com/plotly/dashR/pull/168) +- Application titles may be set using the `app$title()` method, for parity with Dash for Python's `app.title` syntax [#168](https://github.com/plotly/dashR/pull/168) + +### Changed +- Dash for R now requires `dashCoreComponents` v1.8.0 +- Dash for R now requires `dashTable` v4.6.0 +- Automatically set routes and requests pathname prefixes if `DASH_APP_NAME` environment variable has been set [#165](https://github.com/plotly/dashR/pull/165) + +### Deprecated +- Application titles can no longer be set using `name` parameter, which is now deprecated with a warning, for parity with Dash for Python [#168](https://github.com/plotly/dashR/pull/168) +- Removed `DASH_HOST` and `DASH_PORT`, Dash for R now respects `HOST` and `PORT` [#167](https://github.com/plotly/dashR/pull/167) + ## [0.2.0] - 2020-01-03 ### Added - Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157) @@ -28,6 +44,7 @@ All notable changes to this project will be documented in this file. - Fixes for hot reloading interval handling and refreshing apps within viewer pane [#148](https://github.com/plotly/dashR/pull/148) - `get_asset_url` checks `getAppPath()` as well as `DASH_APP_ROOT_PATH` environment variable when invoked [#161](https://github.com/plotly/dashR/pull/161) + ## [0.1.0] - 2019-07-10 ### Added - Initial release diff --git a/DESCRIPTION b/DESCRIPTION index 7a2d5289..e91fe991 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,14 +1,14 @@ Package: dash Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications -Version: 0.2.0 -Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph")) +Version: 0.3.0 +Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person("Hammad", "Khan", role = c("aut"), email = "hammadkhan@plot.ly"), person(family = "Plotly Technologies", role = "cph")) Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required. Depends: R (>= 3.0.2) Imports: dashHtmlComponents (== 1.0.2), - dashCoreComponents (== 1.6.0), - dashTable (== 4.5.1), + dashCoreComponents (== 1.8.0), + dashTable (== 4.6.0), R6, fiery (> 1.0.0), routr (> 0.2.0), @@ -32,8 +32,8 @@ Collate: 'print.R' 'internal.R' Remotes: plotly/dash-html-components@55c3884, - plotly/dash-core-components@c107e0f, - plotly/dash-table@3058bd5 + plotly/dash-core-components@fc153b4, + plotly/dash-table@79d46ca License: MIT + file LICENSE Encoding: UTF-8 LazyData: true diff --git a/R/dash.R b/R/dash.R index ba122302..14fc024c 100644 --- a/R/dash.R +++ b/R/dash.R @@ -5,7 +5,7 @@ #' @usage Dash #' #' @section Constructor: Dash$new( -#' name = "dash", +#' name = NULL, #' server = fiery::Fire$new(), #' assets_folder = 'assets', #' assets_url_path = '/assets', @@ -24,7 +24,7 @@ #' @section Arguments: #' \tabular{lll}{ #' `name` \tab \tab Character. The name of the Dash application (placed in the `` -#' of the HTML page).\cr +#' of the HTML page). DEPRECATED; please use `index_string()` or `interpolate_index()` instead.\cr #' `server` \tab \tab The web server used to power the application. #' Must be a [fiery::Fire] object.\cr #' `assets_folder` \tab \tab Character. A path, relative to the current working directory, @@ -100,6 +100,9 @@ #' from the Dash backend. The latter may offer improved performance relative #' to callbacks written in R. #' } +#' \item{`title("dash")`}{ +#' The title of the app. If no title is supplied, Dash for R will use 'dash'. +#' } #' \item{`callback_context()`}{ #' The `callback_context` method permits retrieving the inputs which triggered #' the firing of a given callback, and allows introspection of the input/state @@ -114,15 +117,92 @@ #' present a warning and return `NULL` if the Dash app was not loaded via `source()` #' if the `DASH_APP_PATH` environment variable is undefined. #' } -#' \item{`run_server(host = Sys.getenv('DASH_HOST', "127.0.0.1"), -#' port = Sys.getenv('DASH_PORT', 8050), block = TRUE, showcase = FALSE, ...)`}{ +#' \item{`get_relative_path(path, requests_pathname_prefix)`}{ +#' The `get_relative_path` method simplifies the handling of URLs and pathnames for apps +#' running locally and on a deployment server such as Dash Enterprise. It handles the prefix +#' for requesting assets similar to the `get_asset_url` method, but can also be used for URL handling +#' in components such as `dccLink` or `dccLocation`. For example, `app$get_relative_url("/page/")` +#' would return `/app/page/` for an app running on a deployment server. The path must be prefixed with +#' a `/`. +#' \describe{ +#' \item{path}{Character. A path string prefixed with a leading `/` which directs at a path or asset directory.} +#' \item{requests_pathname_prefix}{Character. The pathname prefix for the app on a deployed application. Defaults to the environment variable set by the server, or `""` if run locally.} +#' } +#' \item{`strip_relative_path(path, requests_pathname_prefix)`}{ +#' The `strip_relative_path` method simplifies the handling of URLs and pathnames for apps +#' running locally and on a deployment server such as Dash Enterprise. It acts almost opposite the `get_relative_path` +#' method, by taking a `relative path` as an input, and returning the `path` stripped of the `requests_pathname_prefix`, +#' and any leading or trailing `/`. For example, a path string `/app/homepage/`, would be returned as +#' `homepage`. This is particularly useful for `dccLocation` URL routing. +#' \describe{ +#' \item{path}{Character. A path string prefixed with a leading `/` and `requests_pathname_prefix` which directs at a path or asset directory.} +#' \item{requests_pathname_prefix}{Character. The pathname prefix for the app on a deployed application. Defaults to the environment variable set by the server, or `""` if run locally.} +#' } +#' \item{`index_string(string)`}{ +#' The `index_string` method allows the specification of a custom index by changing +#' the default `HTML` template that is generated by the Dash UI. Meta tags, CSS, Javascript, +#' are some examples of features that can be modified. +#' This method will present a warning if your HTML template is missing any necessary elements +#' and return an error if a valid index is not defined. The following interpolation keys are +#' currently supported: +#' \describe{ +#' \item{`{%metas%}`}{Optional - The registered meta tags.} +#' \item{`{%favicon%}`}{Optional - A favicon link tag if found in assets.} +#' \item{`{%css%}`}{Optional - Link tags to css resources.} +#' \item{`{%config%}`}{Required - Config generated by dash for the renderer.} +#' \item{`{%app_entry%}`}{Required - The container where dash react components are rendered.} +#' \item{`{%scripts%}`}{Required - Collected dependencies scripts tags.} +#' } +#' \describe{ +#' \item{Example of a basic HTML index string:}{ +#' \preformatted{ +#' "<!DOCTYPE html> +#' <html> +#' <head> +#' \{\%meta_tags\%\} +#' <title>\{\{%css\%\}\} +#' \{\%favicon\%\} +#' \{\%css_tags\%\} +#' +#' +#' \{\%app_entry\%\} +#' +#' +#' " +#' } +#' } +#' } +#' } +#' \item{`interpolate_index(template_index, ...)`}{ +#' With the `interpolate_index` method, we can pass a custom index with template string +#' variables that are already evaluated. We can directly pass arguments to the `template_index` +#' by assigning them to variables present in the template. This is similar to the `index_string` method +#' but offers the ability to change the default components of the Dash index as seen in the example below: +#' \preformatted{ +#' app$interpolate_index( +#' template_index, +#' metas = "", +#' renderer = renderer, +#' config = config) +#' } +#' \describe{ +#' \item{template_index}{Character. A formatted string with the HTML index string. Defaults to the initial template} +#' \item{...}{Named List. The unnamed arguments can be passed as individual named lists corresponding to the components +#' of the Dash html index. These include the same arguments as those found in the `index_string()` template.} +#' } +#' } +#' \item{`run_server(host = Sys.getenv('HOST', "127.0.0.1"), +#' port = Sys.getenv('PORT', 8050), block = TRUE, showcase = FALSE, ...)`}{ #' The `run_server` method has 13 formal arguments, several of which are optional: #' \describe{ -#' \item{host}{Character. A string specifying a valid IPv4 address for the Fiery server, or `0.0.0.0` to listen on all addresses. Default is `127.0.0.1` Environment variable: `DASH_HOST`.} -#' \item{port}{Integer. Specifies the port number on which the server should listen (default is `8050`). Environment variable: `DASH_PORT`.} +#' \item{host}{Character. A string specifying a valid IPv4 address for the Fiery server, or `0.0.0.0` to listen on all addresses. Default is `127.0.0.1` Environment variable: `HOST`.} +#' \item{port}{Integer. Specifies the port number on which the server should listen (default is `8050`). Environment variable: `PORT`.} #' \item{block}{Logical. Start the server while blocking console input? Default is `TRUE`.} #' \item{showcase}{Logical. Load the Dash application into the default web browser when server starts? Default is `FALSE`.} -#' \item{use_viewer}{Logical. Load the Dash application into RStudio's viewer pane? Requires that `host` is either `127.0.0.1` or `localhost`, and that Dash application is started within RStudio; if `use_viewer = TRUE` and these conditions are not satsified, the user is warned and the app opens in the default browser instead. Default is `FALSE`.} +#' \item{use_viewer}{Logical. Load the Dash application into RStudio's viewer pane? Requires that `host` is either `127.0.0.1` or `localhost`, and that Dash application is started within RStudio; if `use_viewer = TRUE` and these conditions are not satisfied, the user is warned and the app opens in the default browser instead. Default is `FALSE`.} #' \item{debug}{Logical. Enable/disable all the dev tools unless overridden by the arguments or environment variables. Default is `FALSE` when called via `run_server`. Environment variable: `DASH_DEBUG`.} #' \item{dev_tools_ui}{Logical. Show Dash's dev tools UI? Default is `TRUE` if `debug == TRUE`, `FALSE` otherwise. Environment variable: `DASH_UI`.} #' \item{dev_tools_hot_reload}{Logical. Activate hot reloading when app, assets, and component files change? Default is `TRUE` if `debug == TRUE`, `FALSE` otherwise. Requires that the Dash application is loaded using `source()`, so that `srcref` attributes are available for executed code. Environment variable: `DASH_HOT_RELOAD`.} @@ -174,7 +254,7 @@ Dash <- R6::R6Class( config = list(), # i.e., the Dash$new() method - initialize = function(name = "dash", + initialize = function(name = NULL, server = fiery::Fire$new(), assets_folder = 'assets', assets_url_path = '/assets', @@ -191,13 +271,18 @@ Dash <- R6::R6Class( suppress_callback_exceptions = FALSE) { # argument type checking - assertthat::assert_that(is.character(name)) assertthat::assert_that(inherits(server, "Fire")) assertthat::assert_that(is.logical(serve_locally)) assertthat::assert_that(is.logical(suppress_callback_exceptions)) # save relevant args as private fields - private$name <- name + if (!is.null(name)) { + warning(sprintf( + "The supplied application title, '%s', should be set using the title() method, or passed via index_string() or interpolate_index(); it has been ignored, and 'dash' will be used instead.", + name), + call. = FALSE + ) + } private$serve_locally <- serve_locally private$eager_loading <- eager_loading # remove leading and trailing slash(es) if present @@ -213,8 +298,8 @@ Dash <- R6::R6Class( private$in_viewer <- FALSE # config options - self$config$routes_pathname_prefix <- resolve_prefix(routes_pathname_prefix, "DASH_ROUTES_PATHNAME_PREFIX", url_base_pathname) - self$config$requests_pathname_prefix <- resolve_prefix(requests_pathname_prefix, "DASH_REQUESTS_PATHNAME_PREFIX", url_base_pathname) + self$config$routes_pathname_prefix <- resolvePrefix(routes_pathname_prefix, "DASH_ROUTES_PATHNAME_PREFIX", url_base_pathname) + self$config$requests_pathname_prefix <- resolvePrefix(requests_pathname_prefix, "DASH_REQUESTS_PATHNAME_PREFIX", url_base_pathname) self$config$external_scripts <- external_scripts self$config$external_stylesheets <- external_stylesheets @@ -763,11 +848,61 @@ Dash <- R6::R6Class( sep="/"))) }, + # ------------------------------------------------------------------------ + # return relative asset URLs + # ------------------------------------------------------------------------ + + get_relative_path = function(path, requests_pathname_prefix = self$config$requests_pathname_prefix) { + asset = get_relative_path(requests_pathname = requests_pathname_prefix, path = path) + return(asset) + }, + + + # ------------------------------------------------------------------------ + # return relative asset URLs + # ------------------------------------------------------------------------ + + strip_relative_path = function(path, requests_pathname_prefix = self$config$requests_pathname_prefix) { + asset = strip_relative_path(requests_pathname = requests_pathname_prefix, path = path) + return(asset) + }, + + # specify a custom index string + # ------------------------------------------------------------------------ + index_string = function(string) { + private$custom_index <- validate_keys(string) + }, + + # ------------------------------------------------------------------------ + # modify the templated variables by using the `interpolate_index` method. + # ------------------------------------------------------------------------ + interpolate_index = function(template_index = private$template_index[[1]], ...) { + template = template_index + kwargs <- list(...) + + for (name in names(kwargs)) { + key = paste0('\\{\\%', name, '\\%\\}') + template = sub(key, kwargs[[name]], template) + } + + invisible(validate_keys(names(kwargs))) + + private$template_index <- template + }, + + # ------------------------------------------------------------------------ + # specify a custom title + # ------------------------------------------------------------------------ + title = function(string = "dash") { + assertthat::assert_that(is.character(string)) + private$name <- string + }, + # ------------------------------------------------------------------------ # convenient fiery wrappers # ------------------------------------------------------------------------ - run_server = function(host = Sys.getenv('DASH_HOST', "127.0.0.1"), - port = Sys.getenv('DASH_PORT', 8050), + run_server = function(host = Sys.getenv('HOST', "127.0.0.1"), + port = Sys.getenv('PORT', 8050), block = TRUE, showcase = FALSE, use_viewer = FALSE, @@ -827,7 +962,7 @@ Dash <- R6::R6Class( # set the modtime to track state of the Dash app directory # this calls getAppPath, which will try three approaches to # identifying the local app path (depending on whether the app - # is invoked via script, source(), or executed dire ctly from console) + # is invoked via script, source(), or executed directly from console) self$config$ui <- dev_tools_ui if (dev_tools_hot_reload) { @@ -848,7 +983,6 @@ Dash <- R6::R6Class( self$server$on('cycle-end', function(server, ...) { # handle case where assets are not present, since we can still hot reload the app itself # - # private$last_refresh is set after the asset_map is refreshed # private$last_reload stores the time of the last hard or soft reload event # private$last_cycle will be set when the cycle-end handler terminates # @@ -1013,7 +1147,7 @@ Dash <- R6::R6Class( # assuming private$layout is either a function or a list of components... layout_ <- if (is.function(private$layout_)) private$layout_() else private$layout_ - # accomodate functions that return a single component + # accommodate functions that return a single component if (is.component(layout_)) layout_ <- list(layout_) # make sure we are working with a list of components @@ -1266,6 +1400,24 @@ Dash <- R6::R6Class( # akin to https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L338 # note discussion here https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L279-L284 + custom_index = NULL, + template_index = c( + " + + + {%meta_tags%} + {%title%} + {%favicon%} + {%css_tags%} + + + {%app_entry%} + + + ", NA), .index = NULL, generateReloadHash = function() { @@ -1315,7 +1467,7 @@ Dash <- R6::R6Class( !is.null(v[["script"]]) && tools::file_ext(v[["script"]]) == "map" }, logical(1))] - # styleheets always go in header + # stylesheets always go in header css_deps <- compact(lapply(depsAll, function(dep) { if (is.null(dep$stylesheet)) return(NULL) dep$script <- NULL @@ -1434,13 +1586,32 @@ Dash <- R6::R6Class( css_tags <- all_tags[["css_tags"]] # retrieve script tags for serving in the index - scripts_tags <- all_tags[["scripts_tags"]] + scripts <- all_tags[["scripts_tags"]] # insert meta tags if present meta_tags <- all_tags[["meta_tags"]] + + # define the react-entry-point + app_entry <- "
Loading...
" + # define the dash default config key + config <- sprintf("", to_JSON(self$config)) + + if (is.null(private$name)) + private$name <- 'dash' + + if (!is.null(private$custom_index)) { + string_index <- glue::glue(private$custom_index, .open = "{%", .close = "%}") + + private$.index <- string_index + } + + else if (length(private$template_index) == 1) { + private$.index <- private$template_index + } - private$.index <- sprintf( - ' + else { + private$.index <- sprintf( + ' %s @@ -1450,23 +1621,22 @@ Dash <- R6::R6Class( -
-
Loading...
-
- + %s ', - meta_tags, - private$name, - favicon, - css_tags, - to_JSON(self$config), - scripts_tags - ) + meta_tags, + private$name, + favicon, + css_tags, + app_entry, + config, + scripts + ) + } } ) ) diff --git a/R/utils.R b/R/utils.R index 450040c0..31a4e215 100644 --- a/R/utils.R +++ b/R/utils.R @@ -457,22 +457,25 @@ valid_seq <- function(params) { } } -resolve_prefix <- function(prefix, environment_var, base_pathname) { +resolvePrefix <- function(prefix, environment_var, base_pathname) { if (!(is.null(prefix))) { assertthat::assert_that(is.character(prefix)) return(prefix) } else { + # Check environment variables prefix_env <- Sys.getenv(environment_var) - if (prefix_env != "") { + env_base_pathname <- Sys.getenv("DASH_URL_BASE_PATHNAME") + app_name <- Sys.getenv("DASH_APP_NAME") + + if (prefix_env != "") return(prefix_env) - } else { - env_base_pathname <- Sys.getenv("DASH_URL_BASE_PATHNAME") - if (env_base_pathname != "") - return(env_base_pathname) - else - return(base_pathname) - } + else if (app_name != "") + return(sprintf("/%s/", app_name)) + else if (env_base_pathname != "") + return(env_base_pathname) + else + return(base_pathname) } } @@ -1267,3 +1270,75 @@ tryCompress <- function(request, response) { } return(response$compress()) } + +get_relative_path <- function(requests_pathname, path) { + # Returns a path with the config setting 'requests_pathname_prefix' prefixed to + # it. This is particularly useful for apps deployed on Dash Enterprise, which makes + # it easier to serve apps under both URL prefixes and localhost. + + if (requests_pathname == "/" && path == "") { + return("/") + } + else if (requests_pathname != "/" && path == "") { + return(requests_pathname) + } + else if (!startsWith(path, "/")) { + stop(sprintf(paste0("Unsupported relative path! Paths that aren't prefixed" , + "with a leading '/' are not supported. You supplied '%s'."), + path)) + } + else { + return(paste(gsub("/$", "", requests_pathname), gsub("^/", "", path), sep = "/")) + } +} + +strip_relative_path <- function(requests_pathname, path) { + # Returns a relative path with the `requests_pathname_prefix` and leadings and trailing + # slashes stripped from it. This function is particularly relevant to dccLocation pathname routing. + + if (is.null(path)) { + return(NULL) + } + else if ((requests_pathname != "/" && !startsWith(path, gsub("/$", "", requests_pathname))) + || (requests_pathname == "/" && !startsWith(path, "/"))) { + stop(sprintf(paste0("Unsupported relative path! Path's that are not prefixed ", + "with a leading 'requests_pathname_prefix` are not suported. ", + "You supplied '%s', and requests_pathname_prefix was '%s'."), + path, requests_pathname + )) + } + else if (requests_pathname != "/" && startsWith(path, gsub("/$", "", requests_pathname))) { + path = sub(gsub("/$", "", requests_pathname), "", path) + } + return(trimws(gsub("/", "", path))) +} + +interpolate_str <- function(index_template, ...) { + # This function takes an index string, along with + # user specified keys for the html keys of the index + # and sets the default values of the keys to the + # ones specified by the keys themselves, returning + # the custom index template. + template = index_template + kwargs <- list(...) + + for (name in names(kwargs)) { + key = paste0('\\{', name, '\\}') + + template = sub(key, kwargs[[name]], template) + } + return(template) +} + +validate_keys <- function(string) { + required_keys <- c("app_entry", "config", "scripts") + + keys_present <- vapply(required_keys, function(x) grepl(x, string), logical(1)) + + if (!all(keys_present)) { + stop(sprintf("Did you forget to include %s in your index string?", + paste(names(keys_present[keys_present==FALSE]), collapse = ", "))) + } else { + return(string) + } +} diff --git a/man/Dash.Rd b/man/Dash.Rd index a93a5191..bf7e9aa4 100644 --- a/man/Dash.Rd +++ b/man/Dash.Rd @@ -13,7 +13,7 @@ A framework for building analytical web applications, Dash offers a pleasant and } \section{Constructor}{ Dash$new( -name = "dash", +name = NULL, server = fiery::Fire$new(), assets_folder = 'assets', assets_url_path = '/assets', @@ -34,7 +34,7 @@ suppress_callback_exceptions = FALSE \tabular{lll}{ \code{name} \tab \tab Character. The name of the Dash application (placed in the \code{} -of the HTML page).\cr +of the HTML page). DEPRECATED; please use \code{index_string()} or \code{interpolate_index()} instead.\cr \code{server} \tab \tab The web server used to power the application. Must be a \link[fiery:Fire]{fiery::Fire} object.\cr \code{assets_folder} \tab \tab Character. A path, relative to the current working directory, @@ -114,6 +114,9 @@ describes a locally served JavaScript function instead. The latter defines a from the Dash backend. The latter may offer improved performance relative to callbacks written in R. } +\item{\code{title("dash")}}{ +The title of the app. If no title is supplied, Dash for R will use 'dash'. +} \item{\code{callback_context()}}{ The \code{callback_context} method permits retrieving the inputs which triggered the firing of a given callback, and allows introspection of the input/state @@ -128,11 +131,67 @@ but this is configurable via the \code{prefix} parameter. Note: this method will present a warning and return \code{NULL} if the Dash app was not loaded via \code{source()} if the \code{DASH_APP_PATH} environment variable is undefined. } -\item{\code{run_server(host = Sys.getenv('DASH_HOST', "127.0.0.1"), port = Sys.getenv('DASH_PORT', 8050), block = TRUE, showcase = FALSE, ...)}}{ +\item{\code{index_string(string)}}{ +The \code{index_string} method allows the specification of a custom index by changing +the default \code{HTML} template that is generated by the Dash UI. Meta tags, CSS, Javascript, +are some examples of features that can be modified. +This method will present a warning if your HTML template is missing any necessary elements +and return an error if a valid index is not defined. The following interpolation keys are +currently supported: +\describe{ +\item{\code{{\%metas\%}}}{Optional - The registered meta tags.} +\item{\code{{\%favicon\%}}}{Optional - A favicon link tag if found in assets.} +\item{\code{{\%css\%}}}{Optional - Link tags to css resources.} +\item{\code{{\%config\%}}}{Required - Config generated by dash for the renderer.} +\item{\code{{\%app_entry\%}}}{Required - The container where dash react components are rendered.} +\item{\code{{\%scripts\%}}}{Required - Collected dependencies scripts tags.} +} +\describe{ +\item{Example of a basic HTML index string:}{ +\preformatted{ +"<!DOCTYPE html> +<html> + <head> + \{\%meta_tags\%\} + <title>\{\{%css\%\}\} + \{\%favicon\%\} + \{\%css_tags\%\} + + + \{\%app_entry\%\} + + +" + } +} +} +} +\item{\code{interpolate_index(template_index, ...)}}{ +With the \code{interpolate_index} method, we can pass a custom index with template string +variables that are already evaluated. We can directly pass arguments to the \code{template_index} +by assigning them to variables present in the template. This is similar to the \code{index_string} method +but offers the ability to change the default components of the Dash index as seen in the example below: +\preformatted{ + app$interpolate_index( + template_index, + metas = "", + renderer = renderer, + config = config) + } +\describe{ +\item{template_index}{Character. A formatted string with the HTML index string. Defaults to the initial template} +\item{...}{Named List. The unnamed arguments can be passed as individual named lists corresponding to the components +of the Dash html index. These include the same arguments as those found in the \code{index_string()} template.} +} +} +\item{\code{run_server(host = Sys.getenv('HOST', "127.0.0.1"), port = Sys.getenv('PORT', 8050), block = TRUE, showcase = FALSE, ...)}}{ The \code{run_server} method has 13 formal arguments, several of which are optional: \describe{ -\item{host}{Character. A string specifying a valid IPv4 address for the Fiery server, or \code{0.0.0.0} to listen on all addresses. Default is \code{127.0.0.1} Environment variable: \code{DASH_HOST}.} -\item{port}{Integer. Specifies the port number on which the server should listen (default is \code{8050}). Environment variable: \code{DASH_PORT}.} +\item{host}{Character. A string specifying a valid IPv4 address for the Fiery server, or \code{0.0.0.0} to listen on all addresses. Default is \code{127.0.0.1} Environment variable: \code{HOST}.} +\item{port}{Integer. Specifies the port number on which the server should listen (default is \code{8050}). Environment variable: \code{PORT}.} \item{block}{Logical. Start the server while blocking console input? Default is \code{TRUE}.} \item{showcase}{Logical. Load the Dash application into the default web browser when server starts? Default is \code{FALSE}.} \item{use_viewer}{Logical. Load the Dash application into RStudio's viewer pane? Requires that \code{host} is either \code{127.0.0.1} or \code{localhost}, and that Dash application is started within RStudio; if \code{use_viewer = TRUE} and these conditions are not satsified, the user is warned and the app opens in the default browser instead. Default is \code{FALSE}.} diff --git a/man/dash-package.Rd b/man/dash-package.Rd index f17d0269..0f816519 100644 --- a/man/dash-package.Rd +++ b/man/dash-package.Rd @@ -34,6 +34,7 @@ Authors: \item Chris Parmer \email{chris@plot.ly} \item Ryan Patrick Kyle \email{ryan@plot.ly} \item Carson Sievert + \item Hammad Khan \email{hammadkhan@plot.ly} } Other contributors: diff --git a/tests/integration/test_name.py b/tests/integration/test_name.py new file mode 100644 index 00000000..5000bf92 --- /dev/null +++ b/tests/integration/test_name.py @@ -0,0 +1,55 @@ +named_app = """ +library(dash) +library(dashHtmlComponents) +app <- Dash$new() + +app$title("Testing") + +app$layout(htmlDiv(list(htmlDiv(id='container',children='Hello Dash for R testing')))) +app$run_server() +""" + +app_with_template = """ +library(dash) +library(dashHtmlComponents) +app <- Dash$new() + +string <- + " + + + {%meta_tags%} + Testing Again + {%favicon%} + {%css_tags%} + + + {%app_entry%} +
+ {%config%} + {%scripts%} +
+ + " + +app$index_string(string) + +app$layout(htmlDiv(list(htmlDiv(id='container',children='Hello Dash for R testing')))) +app$run_server() +""" + + +def test_rapp001r_with_appname(dashr): + dashr.start_server(named_app) + dashr.wait_for_text_to_equal( + "#container", "Hello Dash for R testing", timeout=1 + ) + assert dashr.find_element("title").get_attribute("text") == "Testing" + + +def test_rapp002_r_with_template(dashr): + dashr.start_server(app_with_template) + dashr.wait_for_text_to_equal( + "#container", "Hello Dash for R testing", timeout=1 + ) + assert dashr.find_element("title").get_attribute("text") == "Testing Again" diff --git a/tests/testthat/test-index.R b/tests/testthat/test-index.R new file mode 100644 index 00000000..4a5ccd47 --- /dev/null +++ b/tests/testthat/test-index.R @@ -0,0 +1,52 @@ +context("customindex") + +test_that("Omitting required template keys produces warnings", { + string <- + " + + + {%meta_tags%} + Testing Again + {%favicon%} + {%css_tags%} + + + {%app_entry%} + + + " + + app <- Dash$new() + + expect_error( + app$index_string(gsub("\\{\\%config\\%\\}|\\{\\%scripts\\%\\}|\\{\\%app_entry\\%\\}", "", string)), + "Did you forget to include app_entry, config, scripts in your index string?" + ) + + expect_error( + app$index_string(gsub("\\{\\%scripts\\%\\}", "", string)), + "Did you forget to include scripts in your index string?" + ) + + expect_error( + app$index_string(gsub("\\{\\%app_entry\\%\\}", "", string)), + "Did you forget to include app_entry in your index string?" + ) + + expect_error( + app$index_string(gsub("\\{\\%config\\%\\}", "", string)), + "Did you forget to include config in your index string?" + ) +}) + +test_that("Customizing title using `name` produces a warning", { + + expect_warning( + Dash$new(name="Testing"), + "The supplied application title, 'Testing', should be set using the title() method, or passed via index_string() or interpolate_index(); it has been ignored, and 'dash' will be used instead.", + fixed=TRUE + ) +})