Static Files Overview¶
This page details how DMP supports traditional links. If you’ve moved on to bundling instead of direct links, these are not the droids you’re looking for. Move along to the bundling page.
In the tutorial, you learned how to automatically include CSS and JS based on your page name .
If your page is named mypage.html
, DMP will automatically include mypage.css
and mypage.js
in the page content. Skip back to the tutorial if you need a refresher.
Here’s what DMP can do for your static files:
Automatic Links: | |
---|---|
Suppose base.htm (base.js, base.css)
|
homepage.htm (homepage.js, homepage.css)
|
index.html (index.js, index.css)
Why so many files for a single web page? It’s a scoping issue. Since When |
|
Inject Variables into Javascript: | |
Custom variables are easy to send from view’s |
|
Sass and Less Compiling: | |
Just before DMP links the related CSS files into a page, it can run |
|
Webpack Bundling: | |
Bundling vendor support files is pretty easy, but things get a little more difficult when page-specific code gets bundled. DMP allows appwide bundles, and it automatically runs the right bundle code for each page. |
How It Works¶
Open base.htm
and look at the following code:
## render the static file links for this template
${ django_mako_plus.links(self) }
The call to links(self)
triggers a number of “provider” classes to run. Each provider is responsible for adding specific types of links. For example, the JsContextProvider
adds context variables to the rendered page; the CssLinkProvider
adds <link rel="stylesheet"...>
links for CSS files related to the current page (and its ancestors).
Since this call to links(self)
exists in base.htm
(and since all site pages extend from base), these providers trigger on every page of your site. Just be sure your pages extend from this supertemplate.
DMP’s default app (homepage) contains two “base” files:base.htm
andbase_ajax.htm
. The first is for full pages (you know, the ones that start with<html>
:). The second is for page snippets like those created in ajax requests. Pretty much the only thing inbase_ajax.htm
is the call to links and a block for subpages to override.
dmp-common.js
¶
Lets go back to base.htm
. Just above the call to links(self)
you’ll see the following:
<script src="/django_mako_plus/dmp-common.min.js"></script>
DMP uses this script to make everything work on the browser side. For example, this script injects values sent from your view.py into the client-side JS scope. It’s a small script (3K), and it’s written in old-school Javascript (for a wide browser audience).
When running in production mode, your web server (IIS, Nginx, etc.) should serve this file (rather than Django). Or it could be bundled with other vendor code. In any case, the file just needs to be included on every page of your site.
The following is an example setting for Nginx:
location /django_mako_plus/dmp-common.min.js {
alias /to/django_mako_plus/scripts/dmp-common.min.js;
}
If you don’t know the location of DMP on your server, try this command:
python3 -c 'import django_mako_plus; print(django_mako_plus.__file__)'
Passing Data: View → JSON → JS¶
Getting data from server-side, Python view files (e.g. process_request
functions) to the client-side, browser JS scope can be difficult. The tutorial introduced this issue with a simple example: accessing the server’s current time in your JS code.
Normally, .js
files are static, meaning they don’t change from one request to another. (We actually could render JS and CSS files the same way we render templates, but writing meta code that writes JS is reserved for special, unmentionable cases.)
Since we can’t (aren’t willing) to change the JS itself, a workaround is writing small script sections inside templates. While this method is common, it has some drawbacks. First, it mixes JS into HTML code. Second, it creates variables in the global window scope (so JS files can get to them).
Step 1: Mark Variables¶
We already have a mechanism to get values from process_request
to templates: the context dictionary. DMP just needs to know which keys/values you want pushed on to the browser scope. This is done by “marking” context keys with jscontext
:
from django.conf import settings
from django_mako_plus import view_function, jscontext
from datetime import datetime
@view_function
def process_request(request):
context = {
jscontext('now'): datetime.now(),
}
return request.dmp.render('index.html', context)
When your template calls DMP’s links(self)
method (see base.htm
), the first provider that goes to work is JsContextProvider
. This provider inspects the context dictionary for keys marked with jscontext
. It converts these values to JSON and inserts the first line below into your template. The next provider to run is JsLinkProvider
, which creates the second line below:
<script>DMP_CONTEXT.set(..., "ketkk4MY3rAXNipepsUrV", { "now": "2020-02-11 09:32:35.41233"}, ...)</script>
<script src="/static/homepage/scripts/index.js" data-context="ketkk4MY3rAXNipepsUrV"></script>
Step 2: Use in Javascript¶
In your JS files, you can access your variables in a context dictionary provided by a closure. In layman’s terms, this means you should wrap the entire index.js
file in an anonymous function with a parameter for the context dictionary. The following are three examples in ES5 and ES6:
ES5 Javascript (function style) | ES6 Javascript (fat-arrow style) | |
---|---|---|
Run immediately | (function(context) {
// your JS code here
console.log(context.now);
})(DMP_CONTEXT.get());
|
(context => {
// your JS code here
console.log(context.now);
})(DMP_CONTEXT.get())
|
Run when page is ready (JQuery) | $((function(context) {
return function() {
// your JS code here
console.log(context.now);
}
})(DMP_CONTEXT.get()));
|
$((context => () => {
// your JS code here
console.log(context.now)
})(DMP_CONTEXT.get()))
|
Run when page is ready (vanilla JS) | document.addEventListener("DOMContentLoaded", (function(context) {
return function() {
// your JS code here
console.log(context['now']);
}
})(DMP_CONTEXT.get()));
|
document.addEventListener("DOMContentLoaded", (context => () => {
// your JS code here
console.log(context['now'])
})(DMP_CONTEXT.get()))
|
Undefined Context?¶
If things aren’t working, open your browser console/inspector and see if JS is giving you any messages. The following are a few reasons that the context might be undefined:
- Your JS code is running too late. If you reverse the closure functions, the code doesn’t run in time to catch the context. Compare your code with the examples below.
- You might be missing script lines
<script src="/django_mako_plus/dmp-common.min.js"></script>
and${ django_mako_plus.links(self) }
, or these might be reversed. /django_mako_plus/dmp-common.min.js
might not be available. Check for a 404 error in the Network tab of your browser’s inspector.- In rare situations (JQuery, I’m looking at you!), JS returned via ajax may not be able to find the right context. See the FAQ for more info.
Limitations¶
The context dictionary is sent to Javascript using JSON, which places limits on the types of objects you can mark with jscontext
. This normally means only strings, booleans, numbers, lists, and dictionaries work out of the box.
Custom JSON Encoding¶
There may be times when you need to send “full” objects. When preparing the JS object, DMP looks for a class method named __jscontext__
in the context values. If the method exists on a value, DMP calls it and includes the return as the reduced, “JSON-compatible” version of the object. The following is an example:
class MyNonJsonObject(object):
def __init__(self):
# this is a complex, C-based structure
self.root = etree.fromstring('...')
def __jscontext__(self):
# this string is what DMP will place in the context
return etree.tostring(self.root, encoding=str)
When you add a MyNonJsonObject
instance to the render context, you’ll still get the full MyNonJsonObject
in your template code (since it’s running on the server side). But it’s reduced with instance.__jscontext__()
to transit to the browser JS runtime:
def process_request(request):
mnjo = MyNonJsonObject()
context = {
# DMP will call obj.__jscontext__() and send the result to JS
jscontext('mnjo'): mnjo,
}
return request.dmp.render('template.html', context)
Now adjust your JS to parse the XML string, and you’re back in business.