Difference between revisions of "User:Damyon Wiese/Javascript Modules"

Jump to: navigation, search
(Javascript Modules)
m (Add more structure for some sections.)
Line 1: Line 1:
 
= Javascript Modules =
 
= Javascript Modules =
What is a Javascript module and why do I care?
+
 
 +
== 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.  
 
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript.  
  
Why should package my code as a module?
+
== Why should 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:
 
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:
Line 12: Line 13:
 
c) You can re-use common code instead of duplicating it
 
c) You can re-use common code instead of duplicating it
  
How do I write a Javascript module in Moodle?
+
= How do I write a Javascript module in Moodle? =
  
 
Since version 2.9(?), Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD 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.  
 
Since version 2.9(?), Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD 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.  

Revision as of 01:23, 3 February 2015

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 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 it's 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" from the top of the Moodle directory to install all of the required tools.

Running grunt

Grunt is a special node package in that it comes in 2 parts. One part is the grunt package that is installed in the node_modules folder by the "npm install" command listed in the previous section. The second part is that you also need to install the grunt-cli package globally in order to have a "grunt" command in your path. To do this run "npm install -g grunt-cli" (you may need additional permissions to do this).

Now you can run the "grunt" command from any folder in Moodle and it should "build" all of the javascript from that directory and it's sub directories.

When grunt runs it currently does 3 things:

  • - it runs shifter to build any YUI modules in the source tree. See [YUI/Shifter] for more information on shifter
  • - it runs jshint to detect invalid Javascript, or Javascript that does not comply with our coding guidelines
  • - it runs uglifyjs to reduce the size of any Javascript by removing whitespace, stripping comments, shortening variable names etc.

You must run "grunt" after making any change to the Javascript in Moodle.

"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. Note: modules (mod_*) are special in that their component name does not start with "mod_" - this is because these plugins are rock stars and laugh in the face of naming conflicts.

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:

helloworld.js

// 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.

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.
});

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 (good name pending)

$OUTPUT->require->requirejs_inlinecode($code);

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. We should probably force calling a single function with params here - we don't want kb if inline javascript.