blob: 78a902a02d8df4600dc25a0510e846698ffa1eca [file] [log] [blame]
#summary Javascript and CSS in Melange
#labels 2013DeveloperDoc
= Introduction =
To make page loading faster we use [http://labjs.com/ LAB], which allows us to load all scripts asynchronously and evaluate them in specific order, without making the browser wait for all scripts to be loaded before allowing users to interact with the page.
Along with this, we have a simple dependency management in place. More dependencies (CSS, remote javascript and inline javascript) can be easily added into Django templates, along with some context for javascript files that are meant to work inside Django templates.
= Dependencies =
== General Dependencies ==
General dependencies (e.g. Purr plugin, jqGrid, etc.) are written inside _soc/content/js/melange.dependency.js_ file. It is basically a collection of arrays which list either
* other dependency arrays (for example *s.jqgrid*, which includes all javascript files that need to be loaded for lists to work). This means that all dependency arrays can be nested. The *unpack()* function will take care of linearize the nested arrays.
* null, which basically says to the downloader to wait for the former script to be evaluated before evaluating the next. For example, you can't load jqGrid if jQuery is not downloaded and evaluated.
* a string, which contains the full path of a script to be downloaded.
== Template Dependencies ==
All template specific JS and CSS dependencies should be listed into an array inside the *dependencies* block. The base template provides specific facilities to include CSS files and to give context to Javascript files related to Django templates. You can also include inline scripts that need to be ran after some other script is loaded.
So, in each template you can create an array like this:
{{{
{% block dependencies %}
[
dep.melange.menu,
null,
css("soc/content/css/search_page.css"),
tc(
"soc/content/js/templates/modules/gsoc/program/list/allocation-100413.js",
{"slots_number": 25}
),
function () {
do_something();
}
]
{% endblock %}
}}}
=== The dep object ===
In the _dep_ object you can find all the dependencies that are included in the _melange.dependency_ script. You can notice that all the arrays are now found in this _dep_ object instead of the _s_ object, which is internal in the _melange.dependency_ script. This has been done to make it more intuitive to use when writing Django templates. You can also notice that _dep.melange.menu_ then refers to _s.melange.menu_ in the _melange.dependency_ script, which is an array of arrays in itself.
This means that you can still nest arrays of dependencies in the Django templates.
=== null ===
As for the _melange.dependency_ script, if you put a _null_ as an element of the array, the following scripts will wait to evaluate before the previous ones are evaluated.
=== css(css_path) ===
The *css(css_path)* method is actually a shortcut for *melange.dependency.cssFile(css_path)*. This has been renamed for conveniency.
This method will create a new {{{ <link> }}} tag in the HTML file, so the browser will download the CSS file and evaluate it in the page.
This facility should be used when the absence of the CSS won't compromise the overall rendering of the page, for example when we want to include CSS files that are needed for a widget that is only displayed once something in the page happens or after a Javascript file is evaluated.
The reason for this is pretty obvious: dynamic linking a CSS file when the page is already displayed to the user will make the page look suddenly different after some time, disorienting the user.
So, basically, global CSS files or CSS styles that are *immediately* needed in the page should still be linked using a {{{ <link> }}} tag in the _stylesheet_ Django block on the top of the page.
=== tc(script_path, context) ===
The *tc(script_path, context)* method is actually a shortcut for *melange.dependency.templateWithContext(script_path, context)*. This has been renamed for conveniency.
This method will take care of downloading the script related to the template, evaluate it and then it will inject the context.
This was formerly done using the {{{ melangeContext }}} attribute of the script tag. So now there is no need to convert the JSON object to a string for the attribute, but you can just put the plain JSON file inside the context parameter of this function calling.
All the {{{ tc() }}} calls are *synchronous*. This means that it automatically evaluates itself as {{{ [null, tc(), null] }}}. The reason to do this is because there is no way for JS scripts to know about their context in the page (e.g. pass some parameters from the Django template to the JS scope), so we need a small hack to make them so and attach them a context. When a JS template is loaded, it will register itself in a global "template queue" hosted in the main _melange.js_ file, in the _melange.templates_ object. The context is then attached to the *last* template that has been registered. So we can't allow another template to be attached before the former one has been coupled with a context.
The JS code for each Django template should be put under _app/soc/content/js/templates_ and then following the same path the Django template has. To receive the context in the JS code, then, your code should be inside the following function:
{{{
melange.templates.inherit(
function (_self, context) {
// Your code here
// _self contains a reference to this function
// context contains the context added in your tc() call
}
);
}}}
=== function() ===
You can also attach a custom inline function, which is a sort of callback that is called after the previous scripts are loaded and evaluated in the browser. This is discouraged unless really necessary, though: all the logic should be put outside the Django template in the separate JS template file.