Note: You are currently viewing documentation for Moodle 4.0. Up-to-date documentation for the latest stable version of Moodle may be available here: JavaScript guidelines.

Development:JavaScript guidelines: Difference between revisions

From MoodleDocs
No edit summary
 
(43 intermediate revisions by 12 users not shown)
Line 1: Line 1:
{{Moodle 2.0}}
{{Moodle 2.0}}
This page is currently (September 2008) just a brain-dump by [[User:Tim Hunt|Tim Hunt]]. Hopefully this is a useful starting point for [http://moodle.org/mod/forum/discuss.php?d=106312 discussion - please use this forum thread]. If we can all agree on something, then they will become actual coding guidelines, and we can fix up some of the more horrific things Moodle currently does with JavaScript.


Update, April 2009: It looks like the [[Development:Navigation_2.0_implementation_plan#JavaScript_clean-up|JavaScript cleanup that is now part of Navigation 2.0]] will change some of the details of this, but the general principles will be adopted. Once I have implemented the new $PAGE->requires->... methods, I will update this page.--[[User:Tim Hunt|Tim Hunt]] 03:23, 21 April 2009 (UTC)
These guidelines can only be applied fully from Moodle 2.0 onwards, because they rely on our new API to facilitate use of JavaScript.
 
When writing JavaScript for earlier versions of Moodle, please try to follow these guidelines in spirit and use the '''require_js''' function in place of $PAGE->requires->js_module/yui2_lib.


==General principles==
==General principles==
===Moodle should be usable without JavaScript===


Everything in Moodle should work with JavaScript turned off. This is important for accessibility, and in line with the principles of [[Development:Unobtrusive_Javascript|unobtrusive JavaScript]] and [[Development:Progressive_enhancement|progressive enhancement]].
Everything in Moodle should work with JavaScript turned off. This is important for accessibility, and in line with the principles of [[Development:Unobtrusive_Javascript|unobtrusive JavaScript]] and [[Development:Progressive_enhancement|progressive enhancement]].
===Minimise inline JavaScript===


Almost all JavaScript code should be in separate .js files. There should be the smallest possible amount of JavaScript inline in the HTML code of pages.
Almost all JavaScript code should be in separate .js files. There should be the smallest possible amount of JavaScript inline in the HTML code of pages.


The official JavaScript library for Moodle is [http://developer.yahoo.com/yui/ YUI]. That may not be your favourite, but it's the one that was chosen after careful research, so live with it.
The only <script> tags in the HTML should be
# <script src=... tags to include the necessary .js files.
# Simple function calls to trigger initialisation and pass data from PHP to JavaScript. For example <script type="text/javascript">initialise_my_widget('some', 'data', 123);</script>.
 
Even these small amounts of JS you should not output directly. They will be generated automatically by calls to the $PAGE->requries->js_init_call() API. See below for details.
 
You should not use old-fashioned onXXX="event_handler" attributes in the HTML. Use Modern DOM events. The YUI events library makes this easy.
 
=== JavaScript libraries ===
 
* The official JavaScript library for Moodle is [[Development:YUI|YUI]]. That may not be your favourite, but it's the one that was chosen after careful research, so live with it.
 
* Moodle also has its own JavaScript library code, packaged into in various JavaScript modules.
 
* Moodle uses the '''TinyMCE''' HTML editor.
 
===When to include the JavaScript===
 
As per [http://developer.yahoo.com/performance/rules.html Yahoo's best practice guidelines], load and execute the JavaScript as late as possible, ideally the script tags should be the last thing before the </body> close tag. (This is the default behaviour with Moodle's JavaScript handling functions like $PAGE->requries->js_init_call().)
 
Since everything should work without JavaScript, load and initialising your scripts only after everything else on the page has loaded should not be a problem and will increase the perceived page-load performance for users.
 
===Minimise the number of .js files===
 
Try not to use too many different .js files. Each separate file that the browser has to load incurs an overhead.
 
On the other hand, organise the JavaScript logically to ease maintenance, and don't include large amounts of irrelevant JavaScript code. Code that is loaded but never used is a waste of time.


Try to add as few things as possible to the global JavaScript name-space - use a few objects in the global name-space with properties and methods. An extreme example is the YUI libraries which only add a single YAHOO object to the global scope. Everything else is contained within that.
So, if you are writing a new module that needs its own JavaScript, try starting with a single file mod/mymod/module.js. If you find that you are writing a lot of JavaScript that is only needed when the teacher edits your module, but is not needed by students, then consider splitting that code into a separate file like mod/mymod/edit.js, and only including it where needed.


Load and execute the JavaScript as late as possible. Since our page should work without JavaScript, load the scripts, and initialise your event handlers from the page footer. If you do this, the page appears to load much more quickly. The user can start to read the page, and by the time they have moved the mouse pointer over your control and click (or whatever), the JavaScript will have had time to load and be ready for them.
==How to achive these general principles in Moodle==


Use as few different .js files as possible. Each separate file load that the browser performs has a big overhead. On the other hand, don't include irrelevant JavaScript code. Code that is loaded but never used is a waste of time. Getting this right involves taking an informed decision about how to chunk your JavaScript.
The rest of this page explains how you can achieve the above goals. Note that this particularly applies to loading and using the YUI 3 library which is Moodle 2.0's default. You should check which version of YUI Moodle is using as it may lag behind the latest YUI release (and, hence, the documentation).


The rest of this page explains how you can achieve these goals.
===Getting Moodle to load your JavaScript files===


==Getting Moodle to load your JavaScript files==
Everything required by the current page is tracked by the $PAGE->requires object, which is an instance of the [http://phpdocs.moodle.org/HEAD/moodlecore/page_requirements_manager.html page_requirements_manager] class defined in lib/outputrequirementslib.php.


There is a function '''require_js''' in lib/weblib.php. It takes one argument, which is either the path to the JavaScript file you want (which will automatically have $CFG->wwwroot prefixed). For example:
The most important method in this class is the ->js_init_call(...) method. You use it like this:


<code php>  
<code php>  
require_js('question/qengine.js');
$PAGE->requires->js_init_call('M.mod_mymod.init_something', array('some', 'data', 'from', 'PHP'));
</code>
</code>


To simplify including the various YUI libraries, require_js recognises some short cuts like
Note that this will implicitly load the JavaScript code in mod/mymod/module.js, and then call the M.mod_mymod.init_something function passing four string arguments 'some', 'data', 'from', 'PHP'. You can pass any PHP type as an argument. The PHP values are encoded with json_encode before being passed to JavaScript, so numbers, strings, arrays and objects all work.
 
Sometimes, the code in the JavaScript module mod/mymod/module.js may require some other JavaScript libraries to be loaded, or it may require some language strings. In that case you need to use the full form of js_init_call:


<code php>
<code php>
require_js('yui_yahoo');
$jsmodule = array(
    'name'     => 'mod_mymod',
    'fullpath' => '/mod/mymod/module.js',
    'requires' => array('base', 'io', 'node', 'json'),
    'strings' => array(
        array('something', 'mymod'),
        array('confirmdelete', 'mymod'),
        array('yes', 'moodle'),
        array('no', 'moodle')
    )
);
$PAGE->requires->js_init_call('M.mod_mymod.init_something', array('some', 'data', 'from', 'PHP'), false, $jsmodule);
</code>
</code>
(Naturally, it would be a good idea to put the definition of the $jsmodule array somewhere central like in the locallib.php file.)


The full list of recognised short cuts is in [http://cvs.moodle.org/moodle/lib/ajax/ajaxlib.php?view=markup lib/ajax/ajaxlib.php]. You can also call require_js with an array of libraries, to further save typing. For example:
The third (boolean) parameter is defined as "Wait for DOM Ready".


<code php>
The libraries in the 'requires' sub-array are YUI3 (or YUI2) libraries. Moodle contains YUIs 'phploader' which understands what the library dependencies are, so this is all taken care of for you.
require_js(array('yui_yahoo','yui_event', 'yui_connection'));
 
The strings are Moodle language strings. In order for Moodle to have correctly determined the user's language, you should only call js_init_call after the call to require_login, which sets the current course, and ensures the user is logged in.
Strings that are passed to Javascript in this way are then available from Javascript using M.util.get_string(), which works just like the PHP function.
 
$PAGE->requires keeps track of which files have been included. For example if two other modules both require the YUI base module, then it is only included once.
 
Your 'module.js' file in your plugin's directory will look something like this:
 
<code javascript>
M.mod_mymod = {};
 
M.mod_mymod.init = function(Y) {
 
    // example to submit a form field on change
    Y.on('change', function(e) {
        Y.one('#mform1').submit();
    }, '#id_fieldname' );
};
</code>
</code>


By default, these libraries will be included by adding <script src=...> tags to the footer of the HTML page. This improves performance. If you really need some JavaScript to be loaded before the rest of the page, you can set the optional second argument of require_js to true (note that this only applies from Moodle 2.0 onwards. In Moodle 1.9, require_js put the <script> tags in the header.
''mod_mymod'' needs changed to reflect the plugin type (see [[Development:Frankenstyle]]) and the name of your plugin throughout.
 
===JavaScript coding style===
 
Moodle JavaScript code should should follow the same [[Development:Coding_style|coding style as Moodle PHP code]], allowing for the differences between PHP and JavaScript.
 
For example, all the rules on ''function_names'', ''class_names'' and ''variablenames'' apply. You should document your code with [http://jsdoc.sourceforge.net/ JSDoc] comments. Layout your JavaScript expressions and statements like the equivalent PHP ones.


require_js keeps track of which files have been included, and ensures that each one is only included once, like the PHP require_once directive.
Normally, your .js files should simply define things like functions, classes and variables. When the file is loaded, no JavaScript code should actually be executed that has any effect unless it is the sort of code that can safely be executed once per HTML page. This is so that it plays nicely with the require_once-like behaviour of $PAGE->requires->js.


==What should go in your .js files==
Do not pollute the global JavaScript name-space. Try to package your JavaScript into objects, and put all objects inside the global M object, like the M.mod_mymod example above.  


In almost every case, your .js files should simply define things like functions, classes and variables. When the file is loaded, no JavaScript code should actually be executed that has any effect. This is so that it plays nicely with the require_once-like behaviour of require_js.
===Different content when JavaScript is on or off===


The code should follow all the normal moodle coding guidelines about function_names and class_names, and variablenames - with the obvious allowances made for the differences between PHP and JavaScript - and the layout of standard structures like function definitions and if statements and so on should be the same as the PHP guidelines too.
Remember the overriding principals that everything should work with JavaScript off, and we should adopt a [[Progressive enhancement]] approach. However, there are valid reasons why sometimes you need different content with JavaScript is on or off. We can break it down into three cases:


==Activating your JavaScript==
====Content that should only be visible with JavaScript off====


Since your .js files should not actually do anything when they are loaded, you need some way to trigger them into action. Normally the best way to do this is to put a simple inline script in the HTML that just calls an initialisation function. Typically, you want the HTML page you generate to look like this:
An example of this is the automatic search when you are looking for a user to assign a role to. With JavaScript on, the search automatically starts after a delay. With JavaScript off, we want an explicit Search button visible.


<code php>
To handle this case, Moodle automatically add a class 'jsenabled' to the body tag using JavaScript. So you just need to add a rule like
<!-- HTML code that will work with JavaScript disabled,
<code css>
    perhaps with an id="mything" attribute on the main HTML tag. -->
body.jsenebled .mywidget .submitbutton {
<script type="text/javascript">
     display: none;
     init_my_script('perhaps_some_data', 'for_example', 'mything');
}
</script>
</code>
</code>
to the stylesheet, and the button will be invisible if JavaScript is enabled.


For an example of this, look at [http://cvs.moodle.org/moodle/question/type/questiontype.php?annotate=1.103#l972 case QUESTION_FLAGSEDITABLE in the print_question_flag method in question/type/questiontype.php].
An alternative strategy is to remove the particular bits of HTML from the page using DOM methods. However, if your JavaScript is only loaded at the end of the page, it may take some time for the extra content to disappear, which leads to a disconcerting flicker in the page.


The arguments you pass to the initialisation function are a good way to get data from your PHP code to your JavaScript. Examples of the kind of thing you need to send are the id of a relevant HTML element, a language string, or values like $CFG->wwwroot. If you have more than a few values to pass however, this becomes a pain, so see the next section.
Yet another approach is the old-fashioned <noscript> tag.


In lib/weblib.php, this is a helpful function '''print_js_call''' that prints a simple script like this for you, given the function name and an array of arguments.
====Content that needs to be visible right away when JavaScript is on====


==Getting more variables from PHP to JavaScript==
An example is the <nowiki>[+] or [-]</nowiki> icon that can be used to expand/collapse each block if JavaScript is on.


When you have lots of values to pass from PHP to JavaScript, or when you have lots of initialisation function calls that all need the same values, a better way to handle the communication is to generate a small amount of JavaScript inline in the HTML of your page that sets up JavaScript variables for all the values you need. The sort of inline script you want is:
We can divide this into two subcases:


<code php>
=====Content generated by PHP code=====
  <script type="text/javascript">
 
qengine_config = {
Where the HTML for the JavaScript only widget is generated by PHP, we can make it invisible when JavaScript is off using just CSS:
    pixpath:       'http://example.com/moodle/pix/smartpix.php/standardwhite'
<code css>
    wwwroot:      'http://example.com/moodle'
.mywidget {
     flagtooltip:   'Click to flag this question'
     display: none;
    unflagtooltip: 'Click to un-flag this question'
}
    flaggedalt:    'Flagged'
body.jsenabled .mywidget {
     unflaggedalt: 'Not flagged'
     display: block;
}
}
  </script>
</code>
</code>
However, it could be argued that this approach is not really progressive enhancement.
=====Content generated by JavaScript code=====
This is more in keeping with progressive enhancement, and this is the way that the expand/collapse block icon is handled.
We build the icon using DOM methods. The only problem is that as the JavaScript is loaded in the footer, there is a small delay before the icons appear. Since when the icons appear, they do not cause other content on the page to move around, that is OK. Also, this delayed appearance is becoming more common on the web. For example, on http://twitter.com/, some things only appear a moment after the main part of the page has finished loading.
However, it the delayed appearance is really a problem, then the only solution is to embed the JavaScript that generates the extra content in the middle of the HTML, using the js_writer class.
====Content that only appears when the user does something, when JavaScript is on====


That sets up a number of variables that can be accessed inside your JavaScript functions as qengine_config.pixpath, qengine_config.wwwroot and so on..
An example of this is something like file picker dialog that appears when you add an image to some content in the HTML editor, or the one that pops up when you click 'Add new question' in the quiz editing interface.


The easiest way to generate a script like this is to use the '''print_js_config''' function in lib/weblib.php. To generate the above script, you would need to do:
We have the same two sub-cases:


<code php>
=====Content generated by PHP code=====
$config = array(
    'pixpath'      => $CFG->pixpath,
    'wwwroot'      => $CFG->wwwroot,
    'flagtooltip'  => get_string('clicktoflag', 'question'),
    'unflagtooltip' => get_string('clicktounflag', 'question'),
    'flaggedalt'    => get_string('flagged', 'question'),
    'unflaggedalt'  => get_string('notflagged', 'question'),
);
print_js_config($config, 'qengine_config');
</code>


==Advice on how to chunk your JavaScript==
In this case, you need to make sure the content is always covered by a ''display: none;'' rule in the CSS, but then when the user takes an action like clicking a button to reveal the extra content, you need to override that class name some how, perhaps by adding or removing a className using JavaScript.


The aim is to only require a few different .js files to be loaded by each page, while not including huge amounts of irrelevant JavaScript for the browser to parse and then ignore.
=====Content generated by JavaScript code=====


Generally speaking, each component of Moodle (activity module, block, filter, ...) should just have one JavaScript file. You might make an exception to this if, for example, you have some JavaScript that your module uses all the time, and a lot of JavaScript that is only used when your module is being edited. In this case, it would be sensible to make a second edit.js file.
In this case, there is no problem. When the use triggers the extra content to appear, it is constructed using DOM methods. There may be a tiny delay, but the chances are that it will hardly be noticeable to the human eye.


(At some point, Moodle may implement a system for automatically combining different JavaScript files from different plugins, for example automatically combining all the JavaScript required by all currently enabled filters, but this should be transparent to the rest of the code.)
If the content generation may be slow (perhaps because it is waiting for an Ajax request) then you should display a progress icon. See, for example, the loading of the tooltip for help icons.


===Don't break XHTML strict!===


==Other points==
Remember that all Moodle output must be [[Development:XHTML|XHTML strict]], and that means that the HTML output must be well-formed XML. Inline JavaScript is a great way to break that. (JavaScript uses the < and & symbols that must be escaped in XML.) Therefore any JavaScript inline in the HTML should be escaped in a CDATA section:


When JavaScript is turned on, the '''class name 'jsenabled'''' will be added to the body tag of the page.
<code xml>
<script type="text/javascript">
//<![CDATA[
 
  // Your JavaScript code goes here.
 
//]]>
</script>
</code>
 
Of course, if you are following the above guidelines and putting most of your JavaScript in separate .js files, and using $PAGE->requires->js_init_call, then this is taken care of for you automatically.


==Testing==
==Testing==
Line 120: Line 199:
==See also==
==See also==


* [http://moodle.org/mod/forum/discuss.php?d=106312 Forum thread for discussing this proposal]
* [[Development:Coding|The rest of Moodle coding guidelines]]
* [[Development:Coding|The other coding guidelines]]
* [http://developer.yahoo.com/yui/ YUI documentation]
* [http://developer.yahoo.com/yui/ YUI documentation]
* [[Javascript FAQ]]
* [[Javascript and YUI 3 FAQ]]
* [[Development:Unobtrusive Javascript]]
* [[Development:Unobtrusive Javascript]]
* [[Development:JavaScript functions]]
* [[Development:JavaScript functions]]
* [http://developer.yahoo.com/performance/rules.html Yahoo's Best Practices for Speeding Up Your Web Site]
* [http://developer.yahoo.com/performance/rules.html Yahoo's Best Practices for Speeding Up Your Web Site]
* [[Javascript FAQ]]
{{CategoryDeveloper}}


[[Category:Coding guidelines|JavaScript guidelines]]
[[Category:Javascript|JavaScript guidelines]]
[[Category:Javascript|JavaScript guidelines]]
[[Category:AJAX|JavaScript guidelines]]
[[Category:AJAX|JavaScript guidelines]]
[[ja:開発:Javaスクリプトガイドライン]]

Latest revision as of 13:26, 18 February 2011

Template:Moodle 2.0

These guidelines can only be applied fully from Moodle 2.0 onwards, because they rely on our new API to facilitate use of JavaScript.

When writing JavaScript for earlier versions of Moodle, please try to follow these guidelines in spirit and use the require_js function in place of $PAGE->requires->js_module/yui2_lib.

General principles

Moodle should be usable without JavaScript

Everything in Moodle should work with JavaScript turned off. This is important for accessibility, and in line with the principles of unobtrusive JavaScript and progressive enhancement.

Minimise inline JavaScript

Almost all JavaScript code should be in separate .js files. There should be the smallest possible amount of JavaScript inline in the HTML code of pages.

The only <script> tags in the HTML should be

  1. <script src=... tags to include the necessary .js files.
  2. Simple function calls to trigger initialisation and pass data from PHP to JavaScript. For example <script type="text/javascript">initialise_my_widget('some', 'data', 123);</script>.

Even these small amounts of JS you should not output directly. They will be generated automatically by calls to the $PAGE->requries->js_init_call() API. See below for details.

You should not use old-fashioned onXXX="event_handler" attributes in the HTML. Use Modern DOM events. The YUI events library makes this easy.

JavaScript libraries

  • The official JavaScript library for Moodle is YUI. That may not be your favourite, but it's the one that was chosen after careful research, so live with it.
  • Moodle also has its own JavaScript library code, packaged into in various JavaScript modules.
  • Moodle uses the TinyMCE HTML editor.

When to include the JavaScript

As per Yahoo's best practice guidelines, load and execute the JavaScript as late as possible, ideally the script tags should be the last thing before the </body> close tag. (This is the default behaviour with Moodle's JavaScript handling functions like $PAGE->requries->js_init_call().)

Since everything should work without JavaScript, load and initialising your scripts only after everything else on the page has loaded should not be a problem and will increase the perceived page-load performance for users.

Minimise the number of .js files

Try not to use too many different .js files. Each separate file that the browser has to load incurs an overhead.

On the other hand, organise the JavaScript logically to ease maintenance, and don't include large amounts of irrelevant JavaScript code. Code that is loaded but never used is a waste of time.

So, if you are writing a new module that needs its own JavaScript, try starting with a single file mod/mymod/module.js. If you find that you are writing a lot of JavaScript that is only needed when the teacher edits your module, but is not needed by students, then consider splitting that code into a separate file like mod/mymod/edit.js, and only including it where needed.

How to achive these general principles in Moodle

The rest of this page explains how you can achieve the above goals. Note that this particularly applies to loading and using the YUI 3 library which is Moodle 2.0's default. You should check which version of YUI Moodle is using as it may lag behind the latest YUI release (and, hence, the documentation).

Getting Moodle to load your JavaScript files

Everything required by the current page is tracked by the $PAGE->requires object, which is an instance of the page_requirements_manager class defined in lib/outputrequirementslib.php.

The most important method in this class is the ->js_init_call(...) method. You use it like this:

$PAGE->requires->js_init_call('M.mod_mymod.init_something', array('some', 'data', 'from', 'PHP'));

Note that this will implicitly load the JavaScript code in mod/mymod/module.js, and then call the M.mod_mymod.init_something function passing four string arguments 'some', 'data', 'from', 'PHP'. You can pass any PHP type as an argument. The PHP values are encoded with json_encode before being passed to JavaScript, so numbers, strings, arrays and objects all work.

Sometimes, the code in the JavaScript module mod/mymod/module.js may require some other JavaScript libraries to be loaded, or it may require some language strings. In that case you need to use the full form of js_init_call:

$jsmodule = array(

   'name'     => 'mod_mymod',
   'fullpath' => '/mod/mymod/module.js',
   'requires' => array('base', 'io', 'node', 'json'),
   'strings' => array(
       array('something', 'mymod'),
       array('confirmdelete', 'mymod'),
       array('yes', 'moodle'),
       array('no', 'moodle')
   )

); $PAGE->requires->js_init_call('M.mod_mymod.init_something', array('some', 'data', 'from', 'PHP'), false, $jsmodule); (Naturally, it would be a good idea to put the definition of the $jsmodule array somewhere central like in the locallib.php file.)

The third (boolean) parameter is defined as "Wait for DOM Ready".

The libraries in the 'requires' sub-array are YUI3 (or YUI2) libraries. Moodle contains YUIs 'phploader' which understands what the library dependencies are, so this is all taken care of for you.

The strings are Moodle language strings. In order for Moodle to have correctly determined the user's language, you should only call js_init_call after the call to require_login, which sets the current course, and ensures the user is logged in. Strings that are passed to Javascript in this way are then available from Javascript using M.util.get_string(), which works just like the PHP function.

$PAGE->requires keeps track of which files have been included. For example if two other modules both require the YUI base module, then it is only included once.

Your 'module.js' file in your plugin's directory will look something like this:

M.mod_mymod = {};

M.mod_mymod.init = function(Y) {

   // example to submit a form field on change
   Y.on('change', function(e) {
       Y.one('#mform1').submit();
   }, '#id_fieldname' );

};

mod_mymod needs changed to reflect the plugin type (see Development:Frankenstyle) and the name of your plugin throughout.

JavaScript coding style

Moodle JavaScript code should should follow the same coding style as Moodle PHP code, allowing for the differences between PHP and JavaScript.

For example, all the rules on function_names, class_names and variablenames apply. You should document your code with JSDoc comments. Layout your JavaScript expressions and statements like the equivalent PHP ones.

Normally, your .js files should simply define things like functions, classes and variables. When the file is loaded, no JavaScript code should actually be executed that has any effect unless it is the sort of code that can safely be executed once per HTML page. This is so that it plays nicely with the require_once-like behaviour of $PAGE->requires->js.

Do not pollute the global JavaScript name-space. Try to package your JavaScript into objects, and put all objects inside the global M object, like the M.mod_mymod example above.

Different content when JavaScript is on or off

Remember the overriding principals that everything should work with JavaScript off, and we should adopt a Progressive enhancement approach. However, there are valid reasons why sometimes you need different content with JavaScript is on or off. We can break it down into three cases:

Content that should only be visible with JavaScript off

An example of this is the automatic search when you are looking for a user to assign a role to. With JavaScript on, the search automatically starts after a delay. With JavaScript off, we want an explicit Search button visible.

To handle this case, Moodle automatically add a class 'jsenabled' to the body tag using JavaScript. So you just need to add a rule like body.jsenebled .mywidget .submitbutton {

   display: none;

} to the stylesheet, and the button will be invisible if JavaScript is enabled.

An alternative strategy is to remove the particular bits of HTML from the page using DOM methods. However, if your JavaScript is only loaded at the end of the page, it may take some time for the extra content to disappear, which leads to a disconcerting flicker in the page.

Yet another approach is the old-fashioned <noscript> tag.

Content that needs to be visible right away when JavaScript is on

An example is the [+] or [-] icon that can be used to expand/collapse each block if JavaScript is on.

We can divide this into two subcases:

Content generated by PHP code

Where the HTML for the JavaScript only widget is generated by PHP, we can make it invisible when JavaScript is off using just CSS: .mywidget {

   display: none;

} body.jsenabled .mywidget {

   display: block;

} However, it could be argued that this approach is not really progressive enhancement.

Content generated by JavaScript code

This is more in keeping with progressive enhancement, and this is the way that the expand/collapse block icon is handled.

We build the icon using DOM methods. The only problem is that as the JavaScript is loaded in the footer, there is a small delay before the icons appear. Since when the icons appear, they do not cause other content on the page to move around, that is OK. Also, this delayed appearance is becoming more common on the web. For example, on http://twitter.com/, some things only appear a moment after the main part of the page has finished loading.

However, it the delayed appearance is really a problem, then the only solution is to embed the JavaScript that generates the extra content in the middle of the HTML, using the js_writer class.

Content that only appears when the user does something, when JavaScript is on

An example of this is something like file picker dialog that appears when you add an image to some content in the HTML editor, or the one that pops up when you click 'Add new question' in the quiz editing interface.

We have the same two sub-cases:

Content generated by PHP code

In this case, you need to make sure the content is always covered by a display: none; rule in the CSS, but then when the user takes an action like clicking a button to reveal the extra content, you need to override that class name some how, perhaps by adding or removing a className using JavaScript.

Content generated by JavaScript code

In this case, there is no problem. When the use triggers the extra content to appear, it is constructed using DOM methods. There may be a tiny delay, but the chances are that it will hardly be noticeable to the human eye.

If the content generation may be slow (perhaps because it is waiting for an Ajax request) then you should display a progress icon. See, for example, the loading of the tooltip for help icons.

Don't break XHTML strict!

Remember that all Moodle output must be XHTML strict, and that means that the HTML output must be well-formed XML. Inline JavaScript is a great way to break that. (JavaScript uses the < and & symbols that must be escaped in XML.) Therefore any JavaScript inline in the HTML should be escaped in a CDATA section:

<script type="text/javascript"> //<![CDATA[

  // Your JavaScript code goes here.

//]]> </script>

Of course, if you are following the above guidelines and putting most of your JavaScript in separate .js files, and using $PAGE->requires->js_init_call, then this is taken care of for you automatically.

Testing

JavaScript support varies a lot between browsers. JavaScript needs to be tested in IE, Firefox and Safari. Ideally, Moodle will support all the browsers that YUI does.

See also