Best practices for writing CKAN themes¶
Don’t use c
¶
As much as possible, avoid accessing the old Pylons template context c
(or tmpl_context
). c
is a thread-global variable, which
encourages spaghetti code that’s difficult to understand and to debug. same
applies for the Flask g
object. Current uses of them in templates are
to provide backwards compatibility but will be removed in the future.
Instead, have controller methods add variables to the extra_vars
parameter of render()
, or have the templates
call
template helper functions instead.
extra_vars
has the advantage that it allows templates, which are
difficult to debug, to be simpler and shifts logic into the easier-to-test and
easier-to-debug Python code. On the other hand, template helper functions are
easier to reuse as they’re available to all templates and they avoid
inconsistencies between the namespaces of templates that are rendered by
different controllers (e.g. one controller method passes the package dict as an
extra var named package
, another controller method passes the same thing
but calls it pkg
, a third calls it pkg_dict
).
You can use the ITemplateHelpers
plugin
interface to add custom helper functions, see
Adding your own template helper functions.
Use url_for()
¶
Always use url_for()
(available to templates as
h.url_for()
) when linking to other CKAN pages, instead of hardcoding URLs
like <a href="/dataset">
. Links created with
url_for()
will update themselves if the URL routing
changes in a new version of CKAN, or if a plugin changes the URL routing.
Use {% trans %}
, {% pluralize %}
, _()
and ungettext()
¶
All user-visible strings should be internationalized, see String internationalization.
Avoid name clashes¶
See Avoid name clashes.
JavaScript modules should have docstrings¶
A JavaScript module should have a docstring at the top of the file, briefly documentating what the module does and what options it takes. For example:
"use strict";
/* example_theme_popover
*
* This JavaScript module adds a Bootstrap popover with some extra info about a
* dataset to the HTML element that the module is applied to. Users can click
* on the HTML element to show the popover.
*
* title - the title of the dataset
* license - the title of the dataset's copyright license
* num_resources - the number of resources that the dataset has.
*
*/
ckan.module('example_theme_popover', function ($) {
return {
initialize: function () {
// Access some options passed to this JavaScript module by the calling
// template.
var num_resources = this.options.num_resources;
var license = this.options.license;
// Format a simple string with the number of resources and the license,
// e.g. "3 resources, Open Data Commons Attribution License".
var content = 'NUM resources, LICENSE'
.replace('NUM', this.options.num_resources)
.replace('LICENSE', this.options.license)
// Add a Bootstrap popover to the HTML element (this.el) that this
// JavaScript module was initialized on.
this.el.popover({title: this.options.title,
content: content,
placement: 'left'});
}
};
});
JavaScript modules should unsubscribe from events in teardown()
¶
Any JavaScript module that calls this.sandbox.client.subscribe()
should have a teardown()
function that calls
unsubscribe()
, to prevent memory leaks.
CKAN calls the teardown()
functions of modules when those modules are
removed from the page.
Don’t overuse pubsub¶
There shouldn’t be very many cases where a JavaScript module really needs to use Pubsub, try to only use it when you really need to.
JavaScript modules in CKAN are designed to be small and loosely-coupled, for example modules don’t share any global variables and don’t call each other’s functions. But pubsub offers a way to tightly couple JavaScript modules together, by making modules depend on multiple events published by other modules. This can make the code buggy and difficult to understand.
Use {% snippet %}
, not {% include %}
¶
Always use CKAN’s custom {% snippet %}
tag instead of Jinja’s default
{% include %}
tag. Snippets can only access certain global variables, and
any variables explicitly passed to them by the calling template. They don’t
have access to the full context of the calling template, as included files do.
This makes snippets more reusable, and much easier to debug.
Snippets should have docstrings¶
A snippet should have a docstring comment at the top of the file that briefly documents what the snippet does and what parameters it requires. For example:
{#
Renders a list of the site's most popular groups.
groups - the list of groups to render
#}
<h3>Most popular groups</h3>
<ul>
{% for group in groups %}
<li>
<a href="{{ h.url_for('group_read', action='read', id=group.name) }}">
<h3>{{ group.display_name }}</h3>
</a>
{% if group.description %}
<p>
{{ h.markdown_extract(group.description, extract_length=80) }}
</p>
{% else %}
<p>{{ _('This group has no description') }}</p>
{% endif %}
{% if group.package_count %}
<strong>{{ ungettext('{num} Dataset', '{num} Datasets', group.package_count).format(num=group.package_count) }}</strong>
{% else %}
<span>{{ _('0 Datasets') }}</span>
{% endif %}
</li>
{% endfor %}
</ul>