Difference between revisions of "Javascript Modules"

Jump to: navigation, search
m (Including an external javascript/jquery library: Fix the example code)
m (Fixed typos)
Line 139: Line 139:
If the library is in AMD format and has a define:
If the library is in AMD format and has a define:
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )
* download the module in both norman and miniifed versions
* download the module in both normal and minified versions
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir
* /theme/mytheme/amd/src/jquery.countdown.js
* /theme/mytheme/amd/src/jquery.countdown.js

Revision as of 10:12, 2 March 2016

Moodle 2.9

Javascript Modules

What is a Javascript module and why do I care?

A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript.

Why should I package my code as a module?

By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:

a) Each smaller piece is simpler to understand / debug

b) Each smaller piece is simpler to test

c) You can re-use common code instead of duplicating it

How do I write a Javascript module in Moodle?

Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition (AMD) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format.

To edit or create an AMD module in Moodle you need to do a couple of things.

Install grunt

The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use "grunt" as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the "nodejs" environment.

This means you first have to install nodejs - and its package manager npm. The details of how to install those packages will vary by operating system, but on Linux it's probably similar to "sudo apt-get install nodejs npm". There are downloadable packages for other operating systems here: http://nodejs.org/download/.

Once this is done, you can run the command:

npm install
npm install -g grunt-cli

from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)

Running grunt

See Grunt#Running_grunt for more details of specific grunt commands which can be used.

"Hello World" I am a Javascript Module

Lets now create a simple Javascript module so we can see how to lay things out.

Each Javascript module is contained in a single source file in the <componentdir>/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named "block_overview/helloworld". the name of the module is important when you want to call it from somewhere else in the code.

After running grunt - the minified Javascript files are stored in the <componentdir>/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js).

Don't forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes.

Lets create a simple module now:


// Standard license block omitted.
 * @package    block_overview
 * @copyright  2015 Someone cool
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @module block_overview/helloworld
define(['jquery'], function($) {
      * Give me blue.
      * @access private
      * @return {string}
     var makeItBlue = function() {
          // We can use our jquery dependency here.
          return $('.blue').show();
     * @constructor
     * @alias module:block_overview/helloworld
    var greeting = function() {
        /** @access private */
        var privateThoughts = 'I like the colour blue';
        /** @access public */
        this.publicThoughts = 'I like the colour orange';
     * A formal greeting.
     * @access public
     * @return {string}
    greeting.prototype.formal = function() {
        return 'How do you do?';
     * An informal greeting.
     * @access public
     * @return {string}
    greeting.prototype.informal = function() {
        return 'Wassup!';
    return greeting;

The most interesting line above is:

define(['jquery'], function($) {

All AMD modules must call "define()" as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here).

The first argument to "define" is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example "jquery" is a dependency. "jquery" is shipped as a core module is available to all AMD modules.

The second argument to "define" is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable "$" (it's a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module.

The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.

It is important that we are returning 'greeting'. If there is no return then your module will be declared as undefined.

Loading modules dynamically

What do you do if you don't know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it's ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like.

// Load a new dependency.
require(['mod_wiki/timer'], function(timer) {
   // timer is available to do my bidding.

Including an external javascript/jquery library

If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:

If the library is in AMD format and has a define: e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )

  • download the module in both normal and minified versions
  • place the modules in your moodle install e.g. your custom theme dir, or plugin dir
  • /theme/mytheme/amd/src/jquery.countdown.js

you can now include the module and initialise it (there are multiple ways to do this) php:

// 1. Create your own amd module and initialise it:
$this->page->requires->js_call_amd('theme_mytheme/countdowntimer', 'initialise', $params);


//1. put this code in theme/mytheme/amd/src/countdowntimer.js
define(['jquery', 'theme_mytheme/jquery.countdown'], function($) {
    return {
        initialise: function ($params) {
           $('#clock').countdown('2020/10/10', function(event) {
             $(this).html(event.strftime('%D days %H:%M:%S'));

2. put the javascript into a mustache template

// /theme/mytheme/templates/countdowntimer.mustache
<span id="clock"></span>
require(['jquery', 'theme_mytheme/jquery.countdown'], function($) {
           $('#clock').countdown('2020/10/10', function(event) {
             $(this).html(event.strftime('%D days %H:%M:%S'));

3. call the javascript directly from php -- although who would want to put javascript into php? ergh..

require(['theme_mytheme/jquery.countdown'], function(min) {
           $('#clock').countdown('2020/10/10', function(event) {
             $(this).html(event.strftime('%D days %H:%M:%S'));

Embedding AMD code in a page

So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function "js_call_amd" that will call a single function from an AMD module with parameters.

$PAGE->requires->js_call_amd($modulename, $functionname, $params);

that will "do the right thing" with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. Notes:

  • the $modulename is the 'componentname/modulename' discussed above
  • the $functionname is the name of a public function exposed by the amd module.
  • the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please).
  • if the size of the params array is too large (> 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.

AMD / JS code can also be embedded on a page via mustache templates see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F

But I have a mega JS file I don't want loaded on every page?

Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix "-lazy.js" which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it's just that the module name will also have the "-lazy" suffix.