I’m not exactly sure how to best start this so I’ll just dive right in and hope for the best. This is a show-and-tell since I have working examples but more importantly, I’d like this to be the start of a feature request discussion for reading optionally embedded declarative application content instead of waiting for the subsequent client requests.
Save As…
Through its offline module and the ‘include_plotlyjs’ functionality, Plotly.py is capable of creating standalone plotlyjs html reports. This feature request and associated code changes enable offline standalone html Dash reports by looking for dash’s declarative application information as named script elements in the originally served, savable, html page, thereby enabling standalone dash clientside application start.
(Standalone html dash reports also face the issue of including enough of the required javascript libraries to function correctly, but as a developer I can track and work around code dependencies so library issues are mostly ignored in this.)
Proposed Changes
The proposal is to make dash-renderer look locally first for named script elements to use for initial page layout and dependency declarations. If found, those declarations are used instead of fetching the data from the server. If not found, everything proceeds as before. Once processed, everything also proceeds as before. This is only about _dash-layout and _dash-depdendencies being processed clientside from embedded content if present.
The changes are straightforward and somewhat follow the lead set by how dash-config is embedded and consumed. In APIController.react.js, just before each data fetch is attempted, look locally first for named elements and use that content if it exists.
This is the layout’s check local first proposed code:
if (isEmpty(layoutRequest) && isEmpty(layout)) {
const layout_element = document.getElementById('_dash-layout')
if ( layout_element ) {
layoutRequest.content = JSON.parse(layout_element.textContent)
layoutRequest.status = STATUS.OK
}
}
The dependencies block is similar:
if (isEmpty(dependenciesRequest)) {
const layout_element = document.getElementById('_dash-dependencies')
if ( layout_element ) {
dependenciesRequest.content = JSON.parse(layout_element.textContent)
dependenciesRequest.status = STATUS.OK
}
}
This has the proposed changes to APIController.react.js:
To leverage these changes ‘serve_layout()’ and ‘dependencies()’ content needs to be proactively put into the original generated html page rather than waiting for dash-renderer to request it. From the dash server perspective, it is easy to create a custom HTML page that includes the new additional script elements somewhere in its definition:
'''<script id="_dash-layout">{auto_layout}</script>
<script id="_dash-dependencies">{auto_deps}</script>'''.format(
auto_layout=self.serve_layout().response[0].decode("utf-8"),
auto_deps=self.dependencies().response[0].decode("utf-8"))
Finally, for required supporting scripts, the problem is transitive closure over the set of scripts required to support standalone dash application execution. I’m sure there are multiple, better ways to go about this, but what I did was to watch the client/server interactions with dev tools to see the list of requested support .js files being served and make local copies. For my dash usage, this was limited to the same 10 files or so that once I copied I didn’t have to touch again. For the time being, since all I want is self contained behavior, I can make do with copying the required support files locally relative to where I save the html page. When I save the html page, I use curl and sed to do some simple post-processing of the html script elements forcing them to reference the local libraries instead of the dash server versions. Ultimately a single embedded inline solution comparable to what ‘include_plotlyjs=True’ does would be the most convenient but isn’t required for clientside operation.
That’s it. Once started, clientside operation is still a normal running dash app, with nothing else changed. The task as a dash developer is to push the right page behaviors into clientside callbacks for the appropriate offline, dash standalone report experience. In practice, the approach I found most productive was to bulk gather data serverside and put it into into dcc.Store declarations that are included in the initial page layout and can be included in dash Input/Output management.
Examples to follow.