===============================
Frontend development guidelines
===============================

.. toctree::
   :maxdepth: 1

   templating
   resources
   template-tutorial
   template-blocks
   javascript-module-tutorial

.. seealso::

   :doc:`/contributing/string-i18n`
     How to mark strings for translation.

-----------------------------
Install frontend dependencies
-----------------------------

The front end stylesheets are written using
`LESS <http://lesscss.org/>`_ (this depends on
`node.js <http://nodejs.org/>`_ being installed on the system)

Instructions for installing node can be found on the `node.js website
<http://nodejs.org/>`_. On Ubuntu 12.04 to 13.04, node.js (and npm node.js's
package manager) need to be installed via a `PPA
<https://launchpad.net/~chris-lea/+archive/node.js/>`_:

::

    $ sudo apt-add-repository ppa:chris-lea/node.js
    $ sudo apt-get update

Now run the command to install nodejs from the repository:

::

    $ sudo apt-get install nodejs

On Ubuntu versions later than 13.04, npm can be installed directly from Ubuntu
packages

::
    $ sudo apt-get install npm

For more information, refer to the `Node wiki
<https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os>`_.

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.js on
Ubuntu). We also use ``nodewatch`` to make our LESS compiler a watcher
style script.

``cd`` into the ``pyenv/src/ckan`` and run:

::

    $ npm install less@1.7.5 nodewatch

--------------
File structure
--------------

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.

-----------
Stylesheets
-----------

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 is also
``--production`` flag for compiling the production ``main.css``.

There are many LESS files 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.

.. Note::
    Whenever a CSS change effects ``main.less`` it's important than after
    the merge into master that a ``$ ./bin/less --production`` should be
    run and commited.

There is a basic pattern primer available at:
http://localhost:5000/testing/primer/ that shows all the main page
elements that make up the CKAN core interface.

----------
JavaScript
----------

The core of the CKAN JavaScript is split up into three areas.

-  Core (such as i18n, pub/sub and API clients)
-  :doc:`javascript-module-tutorial` (small HTML components or widgets)
-  jQuery Plugins (very small reusable components)

Core
====

Everything in the CKAN application lives on the ``ckan`` namespace.
Currently there are four main components that make up the core.

- Modules
- Publisher/Subscriber
- Client
- i18n/Jed

Modules
=======

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. For
example:

::
    <select name="format" data-module="autocomplete"></select>

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 and
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, _) {
      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.
        }
      }
    });

.. note::

   A guide on creating your own modules is located in the
   :doc:`javascript-module-tutorial` guide.

Publisher/subscriber
====================

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.

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', function (jQuery, _) {
      return {
        initialize: function () {
          var sandbox = this.sandbox;
          this.el.on('change', function () {
            sandbox.publish('change:lang', this.selected);
          });
        }
      }
    });

    ckan.module('language-notifier', function (jQuery, _) {
      return {
        initialize: function () {
          this.sandbox.subscribe('change:lang', function (lang) {
            alert('language is now ' + lang);
          });
        }
      }
    });

Client
======

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', function (jQuery, _) {
      return {
        initialize: function () {
          this.sandbox.client.getCompletions(this.options.completionsUrl);
        }
      }
    });

i18n/Jed
========

`Jed <http://slexaxton.github.com/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.

String interpolation can be provided using the
`sprintf formatting <http://www.diveintojavascript.com/projects/javascript-sprintf>`_.
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 () {
          // Through sandbox translation
          this.sandbox.translate('my string');

          // 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');
        }
      };
    });

Life cycle
==========

CKAN modules are intialised on dom 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" data-module-key="id"></select>

The module will be created with the element, any 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.

Internationalization
====================

All strings within modules should be internationalized. 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_1: _('Hello'),
            hello_2: _('Hello %(name)s'),
            apples: function (params) {
              var n = params.num;
              return _('I have %(num)d apple').isPlural(n, 'I have %(num)d apples');
            }
          }
        },
        initialize: function () {
          // Standard example
          this.i18n('hello_1'); // "Hello"

          // String interpolation example
          var name = 'Dave';
          this.i18n('hello_2', {name: name}); // "Hello Dave"

          // Plural example
          var total = 1;
          this.i18n('apples', {num: total}); // "I have 1 apple"
          this.i18n('apples', {num: 3});     // "I have 3 apples"
        }
      }
    });


jQuery plugins
==============

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()``.

Unit tests
==========

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.

The libraries used for the tests are as follows.

-  `Mocha <http://visionmedia.github.com/mocha/>`_: A test runner using
   a BDD style syntax.
-  `Chai <http://chaijs.com>`_: An assertion library (we use the assert
   style).
-  `Sinon <http://sinonjs.org>`_: 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();
      });
