Skip to content

Commit 3733ead

Browse files
authored
Support for index page templating (#168)
1 parent cc9e06d commit 3733ead

File tree

7 files changed

+307
-30
lines changed

7 files changed

+307
-30
lines changed

DESCRIPTION

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
Package: dash
22
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
3-
Version: 0.2.0
3+
Version: 0.3.0
44
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "[email protected]"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "[email protected]"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
55
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
66
Depends:
77
R (>= 3.0.2)
88
Imports:
99
dashHtmlComponents (== 1.0.2),
10-
dashCoreComponents (== 1.6.0),
11-
dashTable (== 4.5.1),
10+
dashCoreComponents (== 1.8.0),
11+
dashTable (== 4.6.0),
1212
R6,
1313
fiery (> 1.0.0),
1414
routr (> 0.2.0),
@@ -32,8 +32,8 @@ Collate:
3232
'print.R'
3333
'internal.R'
3434
Remotes: plotly/dash-html-components@55c3884,
35-
plotly/dash-core-components@c107e0f,
36-
plotly/dash-table@3058bd5
35+
plotly/dash-core-components@fc153b4,
36+
plotly/dash-table@79d46ca
3737
License: MIT + file LICENSE
3838
Encoding: UTF-8
3939
LazyData: true

NAMESPACE

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ importFrom(routr,RouteStack)
2727
importFrom(routr,ressource_route)
2828
importFrom(stats,setNames)
2929
importFrom(tools,file_ext)
30-
importFrom(utils,getFromNamespace)
30+
importFrom(utils,getFromNamespace)

R/dash.R

+154-21
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#' @usage Dash
66
#'
77
#' @section Constructor: Dash$new(
8-
#' name = "dash",
8+
#' name = NULL,
99
#' server = fiery::Fire$new(),
1010
#' assets_folder = 'assets',
1111
#' assets_url_path = '/assets',
@@ -24,7 +24,7 @@
2424
#' @section Arguments:
2525
#' \tabular{lll}{
2626
#' `name` \tab \tab Character. The name of the Dash application (placed in the `<title>`
27-
#' of the HTML page).\cr
27+
#' of the HTML page). DEPRECATED; please use `index_string()` or `interpolate_index()` instead.\cr
2828
#' `server` \tab \tab The web server used to power the application.
2929
#' Must be a [fiery::Fire] object.\cr
3030
#' `assets_folder` \tab \tab Character. A path, relative to the current working directory,
@@ -100,6 +100,9 @@
100100
#' from the Dash backend. The latter may offer improved performance relative
101101
#' to callbacks written in R.
102102
#' }
103+
#' \item{`title("dash")`}{
104+
#' The title of the app. If no title is supplied, Dash for R will use 'dash'.
105+
#' }
103106
#' \item{`callback_context()`}{
104107
#' The `callback_context` method permits retrieving the inputs which triggered
105108
#' the firing of a given callback, and allows introspection of the input/state
@@ -114,7 +117,63 @@
114117
#' present a warning and return `NULL` if the Dash app was not loaded via `source()`
115118
#' if the `DASH_APP_PATH` environment variable is undefined.
116119
#' }
117-
#' \item{`run_server(host = Sys.getenv('HOST', "127.0.0.1"),
120+
#' \item{`index_string(string)`}{
121+
#' The `index_string` method allows the specification of a custom index by changing
122+
#' the default `HTML` template that is generated by the Dash UI. Meta tags, CSS, Javascript,
123+
#' are some examples of features that can be modified.
124+
#' This method will present a warning if your HTML template is missing any necessary elements
125+
#' and return an error if a valid index is not defined. The following interpolation keys are
126+
#' currently supported:
127+
#' \describe{
128+
#' \item{`{%metas%}`}{Optional - The registered meta tags.}
129+
#' \item{`{%favicon%}`}{Optional - A favicon link tag if found in assets.}
130+
#' \item{`{%css%}`}{Optional - Link tags to css resources.}
131+
#' \item{`{%config%}`}{Required - Config generated by dash for the renderer.}
132+
#' \item{`{%app_entry%}`}{Required - The container where dash react components are rendered.}
133+
#' \item{`{%scripts%}`}{Required - Collected dependencies scripts tags.}
134+
#' }
135+
#' \describe{
136+
#' \item{Example of a basic HTML index string:}{
137+
#' \preformatted{
138+
#' "<!DOCTYPE html>
139+
#' <html>
140+
#' <head>
141+
#' \{\%meta_tags\%\}
142+
#' <title>\{\{%css\%\}\}</title>
143+
#' \{\%favicon\%\}
144+
#' \{\%css_tags\%\}
145+
#' </head>
146+
#' <body>
147+
#' \{\%app_entry\%\}
148+
#' <footer>
149+
#' \{\%config\%\}
150+
#' \{\%scripts\%\}
151+
#' </footer>
152+
#' </body>
153+
#' </html>"
154+
#' }
155+
#' }
156+
#' }
157+
#' }
158+
#' \item{`interpolate_index(template_index, ...)`}{
159+
#' With the `interpolate_index` method, we can pass a custom index with template string
160+
#' variables that are already evaluated. We can directly pass arguments to the `template_index`
161+
#' by assigning them to variables present in the template. This is similar to the `index_string` method
162+
#' but offers the ability to change the default components of the Dash index as seen in the example below:
163+
#' \preformatted{
164+
#' app$interpolate_index(
165+
#' template_index,
166+
#' metas = "<meta_charset='UTF-8'/>",
167+
#' renderer = renderer,
168+
#' config = config)
169+
#' }
170+
#' \describe{
171+
#' \item{template_index}{Character. A formatted string with the HTML index string. Defaults to the initial template}
172+
#' \item{...}{Named List. The unnamed arguments can be passed as individual named lists corresponding to the components
173+
#' of the Dash html index. These include the same arguments as those found in the `index_string()` template.}
174+
#' }
175+
#' }
176+
#' \item{`run_server(host = Sys.getenv('HOST', "127.0.0.1"),
118177
#' port = Sys.getenv('PORT', 8050), block = TRUE, showcase = FALSE, ...)`}{
119178
#' The `run_server` method has 13 formal arguments, several of which are optional:
120179
#' \describe{
@@ -174,7 +233,7 @@ Dash <- R6::R6Class(
174233
config = list(),
175234

176235
# i.e., the Dash$new() method
177-
initialize = function(name = "dash",
236+
initialize = function(name = NULL,
178237
server = fiery::Fire$new(),
179238
assets_folder = 'assets',
180239
assets_url_path = '/assets',
@@ -191,13 +250,18 @@ Dash <- R6::R6Class(
191250
suppress_callback_exceptions = FALSE) {
192251

193252
# argument type checking
194-
assertthat::assert_that(is.character(name))
195253
assertthat::assert_that(inherits(server, "Fire"))
196254
assertthat::assert_that(is.logical(serve_locally))
197255
assertthat::assert_that(is.logical(suppress_callback_exceptions))
198256

199257
# save relevant args as private fields
200-
private$name <- name
258+
if (!is.null(name)) {
259+
warning(sprintf(
260+
"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.",
261+
name),
262+
call. = FALSE
263+
)
264+
}
201265
private$serve_locally <- serve_locally
202266
private$eager_loading <- eager_loading
203267
# remove leading and trailing slash(es) if present
@@ -763,11 +827,44 @@ Dash <- R6::R6Class(
763827
sep="/")))
764828
},
765829

830+
# ------------------------------------------------------------------------
831+
# specify a custom index string
832+
# ------------------------------------------------------------------------
833+
index_string = function(string) {
834+
private$custom_index <- validate_keys(string)
835+
},
836+
837+
# ------------------------------------------------------------------------
838+
# modify the templated variables by using the `interpolate_index` method.
839+
# ------------------------------------------------------------------------
840+
interpolate_index = function(template_index = private$template_index[[1]], ...) {
841+
template = template_index
842+
kwargs <- list(...)
843+
844+
for (name in names(kwargs)) {
845+
key = paste0('\\{\\%', name, '\\%\\}')
846+
template = sub(key, kwargs[[name]], template)
847+
}
848+
849+
invisible(validate_keys(names(kwargs)))
850+
851+
private$template_index <- template
852+
},
853+
854+
# ------------------------------------------------------------------------
855+
# specify a custom title
856+
# ------------------------------------------------------------------------
857+
title = function(string = "dash") {
858+
assertthat::assert_that(is.character(string))
859+
private$name <- string
860+
},
861+
766862
# ------------------------------------------------------------------------
767863
# convenient fiery wrappers
768864
# ------------------------------------------------------------------------
769865
run_server = function(host = Sys.getenv('HOST', "127.0.0.1"),
770866
port = Sys.getenv('PORT', 8050),
867+
771868
block = TRUE,
772869
showcase = FALSE,
773870
use_viewer = FALSE,
@@ -1266,6 +1363,24 @@ Dash <- R6::R6Class(
12661363

12671364
# akin to https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L338
12681365
# note discussion here https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L279-L284
1366+
custom_index = NULL,
1367+
template_index = c(
1368+
"<!DOCTYPE html>
1369+
<html>
1370+
<head>
1371+
{%meta_tags%}
1372+
<title>{%title%}</title>
1373+
{%favicon%}
1374+
{%css_tags%}
1375+
</head>
1376+
<body>
1377+
{%app_entry%}
1378+
<footer>
1379+
{%config%}
1380+
{%scripts%}
1381+
</footer>
1382+
</body>
1383+
</html>", NA),
12691384
.index = NULL,
12701385

12711386
generateReloadHash = function() {
@@ -1434,13 +1549,32 @@ Dash <- R6::R6Class(
14341549
css_tags <- all_tags[["css_tags"]]
14351550

14361551
# retrieve script tags for serving in the index
1437-
scripts_tags <- all_tags[["scripts_tags"]]
1552+
scripts <- all_tags[["scripts_tags"]]
14381553

14391554
# insert meta tags if present
14401555
meta_tags <- all_tags[["meta_tags"]]
1556+
1557+
# define the react-entry-point
1558+
app_entry <- "<div id='react-entry-point'><div class='_dash-loading'>Loading...</div></div>"
1559+
# define the dash default config key
1560+
config <- sprintf("<script id='_dash-config' type='application/json'> %s </script>", to_JSON(self$config))
1561+
1562+
if (is.null(private$name))
1563+
private$name <- 'dash'
1564+
1565+
if (!is.null(private$custom_index)) {
1566+
string_index <- glue::glue(private$custom_index, .open = "{%", .close = "%}")
1567+
1568+
private$.index <- string_index
1569+
}
1570+
1571+
else if (length(private$template_index) == 1) {
1572+
private$.index <- private$template_index
1573+
}
14411574

1442-
private$.index <- sprintf(
1443-
'<!DOCTYPE html>
1575+
else {
1576+
private$.index <- sprintf(
1577+
'<!DOCTYPE html>
14441578
<html>
14451579
<head>
14461580
%s
@@ -1450,23 +1584,22 @@ Dash <- R6::R6Class(
14501584
</head>
14511585
14521586
<body>
1453-
<div id="react-entry-point">
1454-
<div class="_dash-loading">Loading...</div>
1455-
</div>
1456-
1587+
%s
14571588
<footer>
1458-
<script id="_dash-config" type="application/json"> %s </script>
1589+
%s
14591590
%s
14601591
</footer>
14611592
</body>
14621593
</html>',
1463-
meta_tags,
1464-
private$name,
1465-
favicon,
1466-
css_tags,
1467-
to_JSON(self$config),
1468-
scripts_tags
1469-
)
1594+
meta_tags,
1595+
private$name,
1596+
favicon,
1597+
css_tags,
1598+
app_entry,
1599+
config,
1600+
scripts
1601+
)
1602+
}
14701603
}
14711604
)
14721605
)

R/utils.R

+30
Original file line numberDiff line numberDiff line change
@@ -1270,3 +1270,33 @@ tryCompress <- function(request, response) {
12701270
}
12711271
return(response$compress())
12721272
}
1273+
1274+
interpolate_str <- function(index_template, ...) {
1275+
# This function takes an index string, along with
1276+
# user specified keys for the html keys of the index
1277+
# and sets the default values of the keys to the
1278+
# ones specified by the keys themselves, returning
1279+
# the custom index template.
1280+
template = index_template
1281+
kwargs <- list(...)
1282+
1283+
for (name in names(kwargs)) {
1284+
key = paste0('\\{', name, '\\}')
1285+
1286+
template = sub(key, kwargs[[name]], template)
1287+
}
1288+
return(template)
1289+
}
1290+
1291+
validate_keys <- function(string) {
1292+
required_keys <- c("app_entry", "config", "scripts")
1293+
1294+
keys_present <- vapply(required_keys, function(x) grepl(x, string), logical(1))
1295+
1296+
if (!all(keys_present)) {
1297+
stop(sprintf("Did you forget to include %s in your index string?",
1298+
paste(names(keys_present[keys_present==FALSE]), collapse = ", ")))
1299+
} else {
1300+
return(string)
1301+
}
1302+
}

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![CircleCI](https://circleci.com/gh/plotly/dashR/tree/dev.svg?style=svg)](https://circleci.com/gh/plotly/dashR/tree/dev)
1+
[![CircleCI](https://circleci.com/gh/plotly/dashR/tree/master.svg?style=svg)](https://circleci.com/gh/plotly/dashR/tree/master)
22
[![GitHub](https://img.shields.io/github/license/plotly/dashR.svg?color=dark-green)](https://github.com/plotly/dashR/blob/master/LICENSE)
33
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/plotly/dashR.svg?color=dark-green)](https://github.com/plotly/dashR/graphs/contributors)
44

0 commit comments

Comments
 (0)