-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal for Offline CSS and JS and Customizable index.html
#265
Comments
Wow! That sounds very promising and powerful!! Just a question regarding this:
We are interested in having some parts of the web application without any Dash components at all (just html and js), using the flask server underneath. How might we manage this? would it be possible to not include "react-entry-point" div or to not load Dash depending on the flask.request.url variable? |
@Akronix, for this scenario, you could simply use pure Flask routes for those non-Dash parts of your application and have them wired up to appropriate endpoints that are not routed to Dash. |
@chriddyp, this proposal sounds like a great way to address both the common use-case of adding CSS and JS files and also contexts that require more customisation. It doesn't get much more simple than dropping the files in a static folder, and the manual override method on the other hand allows full control over arbitrary HTML customisations. Can't think of any downsides to this proposal currently. Looking forward to it! |
It would also be nice if we could just pass in a simple styles dictionary and include it in the styles section of the head. Sometimes I end up having to go into the source code and changing the smallest things like this.
|
@ItsBlinkHere, you can override that function within your Dash app files in a simpler way, without having to modify the Dash library itself. This is the approach that I use within my import dash
# Modify Dash template to improve cross-browser compatibility (support intranet apps viewed in IE11)
def index(self, *args, **kwargs): # pylint: disable=unused-argument
scripts = self._generate_scripts_html()
css = self._generate_css_dist_html()
config = self._generate_config_html()
title = getattr(self, 'title', 'Dash')
return '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{}</title>
{}
<link rel="icon" type="image/png" href="/static/favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="react-entry-point">
<div class="_dash-loading">
Loading...
</div>
</div>
<footer>
{}
{}
</footer>
</body>
</html>
'''.format(title, css, config, scripts)
dash.Dash.index = index |
@amarvin Yes that works but it is still very unnecessary. We need to be able to pass in custom html into the head, weather it be meta tags or styles . It would be very easy to implement. |
Totally agree. So I'm excited for an upcoming update with such features. |
I'm very excited about this; this is fixing a lot of issues/smoothing over a lot of "gotchyas" that I run into all the time. This is, in my humble opinion, A Big Deal that takes the tool one HUGE step forward. A few questions of clarification: If I were to add a local favicon (or any local resource), would it go in this static/ directory? Also, this seems like it could be generic enough to add all sorts of useful information in the head, like meta tags for SEO or (as aforementioned) favicons. Am I getting too ahead of myself here, or is that what this? |
I include my favicon in the /static/ directory, and then add a Flask route to serve static files (see related topic) |
@felixvelariusbos, you just need to put your favicon somewhere that is being exposed publicly and make sure that you get its location right in the corresponding element in the head. |
@amarvin you should't have to explicitly add a Flask route to serve static files any more. Dash now follows the default Flask setup of exposing a |
My solution for this is would use Jinja2 for it's index rendering and customization #281. Here a summary of the api I am working on: static file management.Dash will now include javascript and stylesheet files contained in a static folder on the index render. You can also give a static_folder param to dash constructor to change the static folder path if it's not the root import dash
app = dash.Dash(static_folder='./resources/static') The following applies to the static folder includes:
index.html rendering.Changed the index route to use Jinja2, added Either:
You just need to put the template in a folder named templates on your root dir. The new template has to start with: You can override parts of the template with blocks: {% extends 'index.html' %}
{% block header %}
<div>My static header</div>
{% endblock %} Available blocks
Note: Overriding a block will prevent the default block from rendering. ie: If you override the css_files, no css files will be included unless you do it. Context variables
Default Project structure
|
Regarding this proposed solution @T4rk1n, besides from the concerns about coupling with Jinja2 (which I raise in #281 ), I'm also worried that this starts coercing people into a specific project directory structure. I feel like Dash's un-opinionated approach to this side of Dash applications is beneficial. More documentation here, potentially including suggested static assets arrangements would certainly be desirable though. |
So @T4rk1n and I spoke about this proposal yesterday and I'm warming up to it (or a variant of it), despite some reservations remaining... If Dash used Jinja for
The 'manual override' proposal above is basically string-based and has some special tokens that need to appear in order for things to work. We can just as easily implement this as a Jinja template: file instead of string, Jinja Re imposition of project directories, we could make things a bit more implicit by saying that you have to tell Dash about where your |
I can do it without the jinja2 rendering, I just feel it get quite messy with string interpolation the more you had to it. With the template file, you also get syntax highlight without the need to copy/paste from another file. I also think it gives more power to the user, but as discussed with @nicolaskruchten, we want an easy experience for the common user who probably doesn't know anything about jinja. I feel this is still easier than manually overriding the index with a string format, as you can get already formed blocks and just change what you need. Just need more documentation. With the reusable blocks a user could do without extending the template: <div>some html</div>
{% include 'blocks/react_entry_point.html' %}
<div>some more html</div> I also tried the approach of just taking the |
For the static files loading, do we want to do like the other resources and provide a way to load them externally |
Thanks to everyone who is providing feedback on this one :) I think that I'm still in favor of the interpolated string option:
For me, I think the main reason that I prefer the interpolated string method is that I find it to be more transparent in three areas:
Here are some use cases that I have in mind:
In those cases, if I used And while I like how simple something like this looks:
I feel like in order to understand that, I'd need knowledge of:
Since our index string is so simple and readable, I feel like it's just as much work to read the index string and see exactly where the different blocks go. In response to some other comments:
|
I like the For the index customization with string interpolations, I thought of something like this: A if its a string: format the string by keywords import dash
app = dash.Dash()
index = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{title}</title>
{css}
</head>
<body>
<div>Custom header</div>
<div id="react-entry-point">
<div class="_dash-loading">
Loading...
</div>
</div>
<footer>
{config}
{scripts}
</footer>
<div>Custom footer</div>
</body>
</html>
'''
app.custom_index = index The keys would get replaced by dash, they can be omitted. Or if it's a function: We call the function with the variables as kwargs. import dash
app = dash.Dash()
def custom_index(scripts, css, config, **kwargs):
return '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>My app title</title>
{}
</head>
<body>
<div>Custom header</div>
<div id="react-entry-point">
<div class="_dash-loading">
Loading...
</div>
</div>
<footer>
{}
{}
</footer>
<div>Custom footer</div>
</body>
</html>
'''.format(css, config, scripts)
app.custom_index = custom_index |
One of the strengths of the "just provide a string" approach is that Dash can be quite strict about requiring developers to include the four required tags, and can have very informative error messages if users forget one of them. If we officially support a function as an escape hatch we will likely field a number of issues where people forget or misspell one of them and can't figure out why things aren't working. We'll also start seeing people doing database queries inside that function etc etc. Perhaps going the string-only approach is the most entry-level developer-friendly thing to do. That said, if |
One problem with the string format I proposed, If there is I like @nicolaskruchten idea of calling interpolate_index from the index, that way someone can override it and doesn't have to worry about the |
Ah good point about the |
Yes, I did that in the past, would you recommand a particular syntax ? I like |
Implemented in #286 🎉 |
Opening this issue to propose the officially endorsed and documented method for embedding custom, local CSS and JavaScript in Dash apps.
Background: In Dash, HTML tags and higher-level components are embedded in the App by assigning the
app.layout
property to a nested hierarchy of Dash components (e.g. the components in thedash-core-components
or thedash-html-components
library). These components are serialized as JSON and then rendered client-side with React.js. On page load, Dash serves a very minimal HTML string which includes the blank container for rendering the app within, the component library's JavaScript and CSS, and a few meta HTML tags like the page title and the encoding (seedash/dash/dash.py
Lines 293 to 318 in 6a1809f
This architecture doesn't work well for embedding custom JavaScript scripts and CSS Stylesheets because these scripts and stylesheets usually need to be included in the HTML that is served on page load.
We will support user-supplied JavaScript and CSS through two enhancements:
Enhancement 1 - Automatic
static
folder/static/<path:string>
) for serving these files will be configured automatically by DashThis method will be what most Dash users will use. It's very easy to use and easy to document ("To include custom CSS, just place your CSS files in
static
folder. Dash will take care of the rest"). Since the files will be templated alphabetically, the user can control the order (if necessary) by prefixing ordered numbers to the files (e.g.1-loading.css
,2-app.css
).With this method, we'll be able to add custom CSS processing middleware like minifying CSS or creating cache-busting URLs completely automatically.
If the user needs more control over the placement of these files, they can use the method in "Enhancement 2 - Manual Override".
Enhancement 2 - Manual Override
Currently on page load, Dash serves this HTML string:
dash/dash/dash.py
Lines 293 to 318 in 6a1809f
This enhancement will make this overridable:
app
object (e.g.app.index
).Here is an example:
The following items will be required in creating an index string:
dash_component_css_bundles
,dash_component_javascript_bundles
,dash_renderer_css_bundle
,dash_renderer_javascript_bundle
template names. Dash will look for these names and fill them in with the appropriate component CSS and JavaScript on page load.<div/>
with anid
react-entry-point
. Dash's front-end will render the app within thisdiv
once the JavaScript has been evaluated.Note the level of customizability in this solution:
<head/>
. In the example, see the custom<title/>
and custom<meta/>
description.<div/>
withMy Custom Header
)dash-renderer
build, they could remove the defaultdash_renderer
template variable and include their own version.flask.request.url
variable.The text was updated successfully, but these errors were encountered: