How to create a YUI 3 module

Revision as of 22:48, 13 March 2013 by Dom Royko (talk | contribs) (fixed typos)

Jump to: navigation, search

Moodle 2.0

This document explains how to create a YUI 3 module in Moodle.

The two ways to write a YUI 3 module

There is two ways to write a YUI 3 module. The two following paragraphs are sourced from a chat with Sam Hemelryk.

The static module

The static module is the simpler of the two, in this case you simply put your code into a module.js file within your plugin. Into that file you create a namespace and define any functions or objects you want to use.

Within PHP static modules are also pretty easy to use. The first step if to create a module definition which tells moodle what your module is called, what it requires, and can optionally give it strings.

Once the module is defined you can then use any of the several JS methods for including JS in your page and pass your module definition along.

Whilst this method is simpler to write and understand especially when you are first starting out with JavaScript it isn't as easily flexible and has some quirks that you need will need to overcome, especially with more complex JavaScript.

This method of writing a module should be used if you are just starting out with JavaScript or if the JS solution you are creating is going to be relatively simple in nature. If you are writing something more complex I would strongly suggest looking at the other method as it will result in cleaner, easier to read code at the end of the day. However it will require you to learn your way around writing a Moodle module.

The YUI3 Moodle Module

This method is certainly more complex than the previous method however it is much more flexible and when you have learnt and understand what you are doing it is much quicker to write and more versatile as well. These modules can also easily include other YUI3 Moodle modules, and can choose to include module specific CSS as well.

In this method you create a yui directory within your plugin directory, and then sub directories for each module you wish to write. You name the subdirectories the same as you want your module named. In each subdirectory you create a JavaScript file with the same name that you gave the subdirectory, e.g./local/myplugin/yui/mymodule/mymodule.js The JS that you then put into this file should then be written in the same way you would write a YUI module. Before you ask it certainly requires a good understanding of YUI.

I would recommend this method of writing a module providing you either have an understanding of how to write a YUI module, or you are happy to learn. It normally results in cleaner easier to read code, and if desired can easily be written in such as way as to make it VERY easy to re-use in other bits of code.... particularly helpful if you have several JavaScript objects in play at a time.

Static module quick start

1. Create a module.js file somewhere

2. The two ways Moodle can load this file are:

a- the file is in core: declare the file into /lib/outputrequirementslib.php/find_module($component)

//example:
          case 'core_rating':
                    $module = array('name'     => 'core_rating',
                                    'fullpath' => '/rating/module.js',
                                    'requires' => array('node', 'event', 'overlay', 'io', 'json'));

b- the file is in a local plugin: the first line of your module should be

M.local_xxx = {

xxx being your plugin directory name.

3. Write the module.js

M.local_hub={
    Y : null,
    transaction : [],
    init : function(Y){
        alert('hub module initialisation');
        this.Y = Y;
    },
  }

4. Call this module. The best place to do this is in your renderer class (since, if a theme overrides the renderer to change the HTML, they may also need to use different JavaScript.

$this->page->requires->js_init_call('M.local_hub.init'); 
You should now have loaded and called your first YUI 3 module in Moodle.

5. If you want to load your module with some other YUI modules

$jsmodule = array(
                'name' => 'local_hub',
                'fullpath' => '/local/hub/module.js',
                'requires' => array("overlay", "anim", "plugin")); //on this line you are loading three other YUI modules
   $this->page->requires->js_init_call('M.local_hub.init',
                 null, false, $jsmodule);

6. If you want to pass parameter to the init function

PHP:

$this->page->requires->js_init_call('M.local_hub.init',
                    array('this is the param1 value'), false, $jsmodule);

JS:

init : function(Y, params){
        alert(params);
        ....
    }

7. Then you should be able to fill the module with your own YUI javascript from YUI examples.

YUI 3 Moodle Module quick start

1. Create the module_name.js file in /local/pluginname/yui/module_name/modulename.js or in /mod/modname/yui/modulename/modulename.js

2. The basic module_name.js structure is:

YUI.add('moodle-local_pluginname-modulename', function(Y) {
    var ModulenameNAME = 'this_is_a_module_name';
    var MODULENAME = function() {
        MODULENAME.superclass.constructor.apply(this, arguments);
    };
    Y.extend(MODULENAME, Y.Base, {
        initializer : function(config) { // 'config' contains the parameter values
            alert('I am in initializer');
        }
    }, {
        NAME : ModulenameNAME, //module name is something mandatory. 
                                // It should be in lower case without space 
                                // as YUI use it for name space sometimes.
        ATTRS : {
                 aparam : {}
        } // Attributes are the parameters sent when the $PAGE->requires->yui_module calls the module. 
          // Here you can declare default values or run functions on the parameter. 
          // The param names must be the same as the ones declared 
          // in the $PAGE->requires->yui_module call.
    });
    M.local_pluginname = M.local_pluginname || {}; // This line use existing name path if it exists, otherwise create a new one. 
                                                 // This is to avoid to overwrite previously loaded module with same name.
    M.local_pluginname.init_modulename = function(config) { // 'config' contains the parameter values
        alert('I am in the javascript module, Yeah!');
        return new MODULENAME(config); // 'config' contains the parameter values
    };
  }, '@VERSION@', {
      requires:['base','another_required_YUI_module', 'a_moodle_YUI_module']
  });

3. Call the javascript in PHP. Again, it is best to do this in a renderer class. (if you copied the previous javascript, 2 javascript alert should appear.)

$this->page->requires->yui_module('moodle-local_pluginname-modulename', 'M.local_pluginname.init_modulename',
                array(array('aparam'=>'paramvalue')));

About 'extend'

The example code above shows how to create a class that extends from the YUI 'base' class. If you only want to write code that is on its own and does not extend a YUI class, you do not need to use this format. For example, here is how you could include the same code from the 'static example' above but in the YUI module structure:

YUI.add('moodle-local_pluginname-yuimodulename', function(Y) {
    M.local_pluginname = {
        init : function() {
            alert('pluginname yuimodulename initialisation');
        },
      }
}, '@VERSION@', {
    requires:['node', 'another_required_YUI_module', 'a_moodle_YUI_module']
});

This is the same code as in the simple static example above except that:

  • The YUI.add function is called, giving a unique YUI 3 module name for this code.
  • The Y parameter does not need to be stored inside M.local_hub because it is already available from being inside the outer function.
  • Required YUI 3 modules are explicitly listed in the code.

Full simple example

Once you understand and run the above quick start, here is simple YUI 3 example. It shows how to use another YUI 3 module in your module, and to use some events.

It includes

  • display a parameter value in two javascript alert boxes => demonstrate how to pass a param to the initialize function in two different ways
  • display an overlay when you click on an image
  • hide the overlay when you click on the body
  • some commented code to see how Moodle YUI exception works

PHP

Create a basic local plugin (in this example it is named hub) in /local/hub/ and add this code into one of the plugin pages:

$PAGE->requires->yui_module('moodle-local_hub-comments', 'M.local_hub.init_comments',
                array(array('commentids' => '1 , 23 ,45')));

HTML

The plugin page should generate this HTML code:

<!-- image to click on -->
<div id="comments"><img src='http://moodle.org/theme/moodle2/pix/moodle-logo.gif' alt='the image to click on'/></div>
 
<!-- overlay -->
<div id="commentoverlay" class="yui3-overlay-loading">
  <div class="yui3-widget-hd">Comment</div>
  <div class="yui3-widget-bd">this is a comment</div>
  <div class="yui3-widget-bd">this is another comment</div>
</div>

CSS

The plugin style.css should contain:

/* Hide overlay markup while loading, if js is enabled */
.yui3-js-enabled .yui3-overlay-loading {
    top:-1000em;
    left:-1000em;
    position:absolute;
}
 
/* Overlay Look/Feel */
.yui3-overlay-content {
    padding:3px;
    border:1px solid #000;
    background-color:#aaa;
}
 
.yui3-overlay-content .yui3-widget-hd {
    padding:5px;
    border:2px solid #aa0000;
    background-color:#fff;
}
 
.yui3-overlay-content .yui3-widget-bd {
    padding:5px;
    border:2px solid #0000aa;
    background-color:#fff;
}

JS

In /local/hub/yui/comments/comments.js

YUI.add('moodle-local_hub-comments', function(Y) {
 
    var COMMENTSNAME = 'hub_comments';
 
    //we declare the overlay here so it's accessible to all the extended class function'
    var overlay = new Y.Overlay({
                srcNode:"#commentoverlay", //the main div with id = commentoverlay
                width:"10em",
                height:"10em",
                xy:[ 0, 0],
                visible: false //by default it is not displayed
            });
 
    var COMMENTS = function() {
        COMMENTS.superclass.constructor.apply(this, arguments);
    }
 
    Y.extend(COMMENTS, Y.Base, {
 
        event:null,
 
        initializer : function(params) {
 
            //Example how to retrieve the parameter,
            //see what happens if you don't pass the parameter in the call
            alert(this.get('commentids')); // You usually use an attribute because
                                           //a attribut can have default value.
                                           //use the ATTRS default value if not passed as parameter
            alert(params.commentids); // undefined if not passed as parameter
 
            //render the overlay
            overlay.render();
 
            // position the overlay in the middle of the web browser window
            var WidgetPositionAlign = Y.WidgetPositionAlign;
            overlay.set("align", {
                node:"", //empty => viewport
                points:[WidgetPositionAlign.CC, WidgetPositionAlign.CC]
            });
 
            //attach a show event on the div with id = comments
            //we also change the div style to display the click zone
            Y.one('#comments').setStyle("border", "1px solid red").on('click', this.show, this);
 
            //uncomment the try content to see how works exception in Moodle
            //this exception has been specially created for Moodle
            try {
              //surely_not_existing_js_function();
            } catch (exception) {
                new M.core.exception(exception);
            }
 
        },
 
        show : function (e) {
            overlay.show(); //show the overlay
 
            e.halt(); // we are going to attach a new 'hide overlay' event to the body,
                      // because javascript always propagate event to parent tag,
                      // we need to tell Yahoo to stop to call the event on parent tag
                      // otherwise the hide event will be call right away.
 
            //we add a new event on the body in order to hide the overlay for the next click
            this.event = Y.one(document.body).on('click',this.hide,this);
        },
 
        hide : function () {
            overlay.hide(); //hide the overlay
            this.event.detach(); //we need to detach the hide event
                                 //Note: it would work without but create js warning everytime
                                 //we click on the body
        }
 
    }, {
        NAME : COMMENTSNAME,
        ATTRS : {
            commentids: {value : 450} //default value (has no purpose in this code except to show you
                                      // how to pass/use a param value)
        }
    });
 
    M.local_hub = M.local_hub || {};
    M.local_hub.init_comments = function(params) {
        return new COMMENTS(params);
    }
 
}, '@VERSION@', {
    requires:['base','overlay', 'moodle-enrol-notification']
    //Note: 'moodle-enrol-notification' contains Moodle YUI exception
});

Where are my methods in the IDE?

Some (all?) IDEs will fail to parse the above structure correctly and will not show you your methods in the structure/navigator pane. If this happens, use JSDoc format to mark the Y.extend() line as being a particular class:

/**
 * @class M.local_hub.init_comments
 */
Y.extend(COMMENTS, Y.Base, {

This depends to a certain extent on how you choose to instantiate your objects. The above init_widget() method can be replaced with a more class based method like a lot of the YUI library uses.

What else to read

Two pages are important to start with when you discover YUI 3

  • you should read the base YUI doc. It is very useful to understand the YUI 3 Moodle module.
  • you want to have a look to the YUI 3 examples. Always open the examples in a window to have a look to the complete example codes.

See Also