The following is an index of other front-end CKAN documentation.
- Coding Standards: The CKAN project coding standards.
- Templating: A guide to the Jinja templating system.
- Extension Templating: A quick guide to templating extensions.
- Basic Template Tutorial: A quick guide to creating a page.
- CKAN Modules: A tutorial on building a CKAN JavaScript module.
The front end stylesheets are written using LESS. This depends on node being installed on the system.
Instructions for installing node can be found on the project website.
On ubuntu node can be installed using the following commands:
$ sudo apt-get install nodejs $ sudo apt-get install npm # node package manager
LESS can then be installed via the node package manager which is bundled with node (or installed with apt as it is not bundled with node on Ubuntu). We also need nodewatch.
cd
into the pyenv/src/ckan
and run:
$ npm install less nodewatch
A watcher script can then be used to compile the CSS whenever the LESS changes:
$ ./bin/less
This will generate a main.debug.css file. To build the production CSS to commit into the repository run:
$ ./bin/less --production
This will generate the main.css file that should be committed for use in
packaged versions of CKAN. (This command will actually start a watch
script so you'll have to exit it with ctrl-c
to actually commit the
file.)
(If someone could move this into a paster script that would be great)
All front-end files to be served via a web server are located in the
public
directory (in the case of the new CKAN base theme it's
public/base
).
css/ main.css less/ main.less ckan.less javascript/ main.js utils.js components/ vendor/ jquery.js jquery.plugin.js underscore.js bootstrap.css test/ index.html spec/ main.spec.js utils.spec.js vendor/ mocha.js mocha.css chai.js
All files and directories should be lowercase with hyphens used to separate words.
- css
- Should contain any site specific CSS files including compiled production builds generated by LESS.
- less
- Should contain all the less files for the site. Additional vendor styles should be added to the vendor directory and included in main.less.
- javascript
- Should contain all website files. These can be structured appropriately. It is recommended that main.js be used as the bootstrap filename that sets up the page.
- vendor
- Should contain all external dependencies. These should not contain version numbers in the filename. This information should be available in the header comment of the file. Library plugins should be prefixed with the library name. If a dependency has many files (such as bootstrap) then the entire directory should be included as distributed by the maintainer.
- test
- Contains the test runner index.html. vendor contains all test dependencies and libraries. spec contains the actual test files. Each test file should be the filename with .spec appended.
Because all the stylesheets are using LESS we need to compile them
before beginning development. In production CKAN will look for the
main.css
file which is included in the repository. In development
CKAN looks for the file main.debug.css
which you will need to
generate by running:
$ ./bin/less
This will watch for changes to all of the less files and automatically
rebuild the CSS for you. To quit the script press ctrl-c
.
There are many LESS scripts which attempt to group the styles in useful groups.
The main two are:
- main.less: This contains all the styles for the website including dependancies and local styles. The only files that are excluded here are those that are conditionally loaded such as IE only CSS and large external apps (like recline) that only appear on a single page.
- ckan.less: This includes all the local ckan stylesheets.
There is a very basic pattern primer available at:
http://localhost:5000/test/primer/
This should contain common components and slowly be expanded to provide documentation on the CKAN markup and styles. But for the moment it serves as useful unit tests for the CSS.
The core of the CKAN JavaScript is split up into three areas.
- Core (such as i18n, pub/sub and API clients)
- Modules (small HTML components or widgets)
- jQuery Plugins (very small reusable components)
Everything in the CKAN application lives on the ckan
namespace.
Currently there are four main components that make up the core.
Modules are the core of the CKAN website, every component that is
interactive on the page should be a module. These are then initialized
by including a data-module
attribute on an element on the page.
The idea is to create small isolated components that can easily be tested. They should ideally not use any global objects, all functionality should be provided to them via a "sandbox" object.
There is a global factory that can be used to create new modules.
ckan.module('my-module', { initialize: function () { // Called when a module is created. }, teardown: function () { // Called before a module is removed from the page. } });
jQuery and Localisation methods are available via
this.sandbox.jQuery
and this.sandbox.translate()
respectively.
To save typing these two common objects we can take advantage of
JavaScript closures and use an alternative module syntax that accepts a
factory function.
ckan.module('my-module', function (jQuery, translate) { return { initialize: function () { // Called when a module is created. // jQuery and translate are available here. }, teardown: function () { // Called before a module is removed from the page. } } });
There is a simple pub/sub module included under ckan.pubsub
it's
methods are available to modules via
this.sandbox.publish/subscribe/unsubscribe
. This can be used to
publish messages between modules.
Ideally no module should use jQuery.ajax() to make XHR requests to the CKAN API, all functionality should be provided via the client object.
ckan.module('my-module', { initialize: function () { this.sandbox.client.getCompletions(this.options.completionsUrl); } });
Jed is a Gettext implementation in
JavaScript. It is used throughout the application to create translatable
strings. An instance of Jed is available on the ckan.i18n
object.
Modules get access to the translate()
function via both the initial
factory function and the this.sandbox.translate()
object.
ckan.module('my-module', function (jQuery, translate) { return {/* my module */}; }); ckan.module('my-module', { initialize: function () { this.sandbox.translate('my string'); } });
String interpolation can be provided using the sprintf formatting.
We always use the named arguments to keep in line with the Python translations.
And we name the translate function passed into ckan.module()
_
.
ckan.module('my-module', function (jQuery, _) { return { initialize: function () { // Keyword arguments _('Hello %(name)s').fetch({name: 'Bill'}); // Hello Bill // Multiple. _("I like your %(color)s %(fruit)s.").fetch({color: 'red', fruit: 'apple'); // Plurals. _("I have %(num)d apple.") .ifPlural(2, "I have %(num)d apples.") .fetch({num: 2, fruit: 'apple'); } }; });
CKAN modules are intialised on document ready. The
ckan.module.initialize()
will look for all elements on the page with
a data-module
attribute and attempt to create an instance.
<select name="format" data-module="autocomplete"></select>
The module will be created with the element, an options object extracted
from data-module-*
attributes and a new sandbox instance.
Once created the modules initialize()
method will be called allowing
the module to set themselves up.
Modules should also provide a teardown()
method this isn't used at
the moment except in the unit tests to restore state but may become
useful in the future.
Modules should use the publish/subscribe methods to talk to each other and allow different areas of the UI to update where relevant.
ckan.module('language-picker', { initialize: function () { var sandbox = this.sandbox; this.el.on('change', function () { sandbox.publish('change:lang', this.selected); }); } }); ckan.module('language-notifier', { initialize: function () { this.sandbox.subscribe('change:lang', function (lang) { alert('language is now ' + lang); }); } });
All strings within modules should be internationalised. Strings can be
set in the options.i18n
object and there is a .i18n()
helper for
retrieving them.
ckan.module('language-picker', function (jQuery, _) { return { options: { i18n: { hello: _('Hello') } }, initialize: function () { this.i18n('hello'); // "Hello" } } });
String interpolation can be provided by passing extra arguments.
ckan.module('language-picker', function (jQuery, _) { return { options: { i18n: { hello: _('Hello %(name)s') } }, initialize: function () { var name = 'Dave'; this.i18n('hello', {name: name}); // "Hello Dave" } } });
Plural versions can also be provided using a function and the chained Jed API.
ckan.module('language-picker', function (jQuery, _) { return { options: { i18n: { apples: function (params) { var n = params.num; return _('I have %(num)d apple').isPlural(n, 'I have %(num)d apples'); } } }, initialize: function () { var total = 1; this.i18n('apples', {num: total}); // "I have 1 apple" this.i18n('apples', {num: 3}); // "I have 3 apples" } } });
Any functionality that is not directly related to ckan should be packaged up in a jQuery plug-in if possible. This keeps the modules containing only ckan specific code and allows plug-ins to be reused on other sites.
Examples of these are jQuery.fn.slug()
, jQuery.fn.slugPreview()
and jQuery.proxyAll()
.
There is currently a test suite available at:
http://localhost:5000/base/test/index.html
Every core component, module and plugin should have a set of unit tests.
Tests can be filtered using the grep={regexp}
query string
parameter.
http://localhost:5000/base/test/index.html?grep=^jQuery
The libraries used for the tests are as follows.
- Mocha: A test runner using a BDD style syntax.
- Chai: An assertion library (we use the assert style).
- Sinon: A stubbing library, can stub objects, timers and ajax requests.
Each file has a description block for it's top level object and then within that a nested description for each method that is to be tested:
describe('ckan.module.MyModule()', function () { describe('.initialize()', function () { it('should do something...', function () { // assertions. }); }); describe('.myMethod(arg1, arg2, arg3)', function () { }); });
The `.beforeEach()`
and `.afterEach()`
callbacks can be used to setup
objects for testing (all blocks share the same scope so test variables can
be attached):
describe('ckan.module.MyModule()', function () { // Pull the class out of the registry. var MyModule = ckan.module.registry['my-module']; beforeEach(function () { // Create a test element. this.el = jQuery('<div />'); // Create a test sandbox. this.sandbox = ckan.sandbox(); // Create a test module. this.module = new MyModule(this.el, {}, this.sandbox); }); afterEach(function () { // Clean up. this.module.teardown(); }); });
Templates can also be loaded using the .loadFixtures()
method that is
available in all test contexts. Tests can be made asynchronous by setting a
done
argument in the callback (Mocha checks the arity of the functions):
describe('ckan.module.MyModule()', function () { before(function (done) { // Load the template once. this.loadFixture('my-template.html', function (html) { this.template = html; done(); }); }); beforeEach(function () { // Assign the template to the module each time. this.el = this.fixture.html(this.template).children(); });