Javascript/Coding Style: Difference between revisions
(Constant naming guidelines) |
No edit summary |
||
(32 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
The Moodle JavaScript coding style. | |||
{{Deprecated|version=3.10}} | |||
== Overview == | |||
This document outlines the exceptions to the general Moodle [[Coding_style|Coding style]] which apply to JavaScript. | |||
Unless otherwise specified, developers should follow the general coding style for advice on coding style. | |||
Note that some part of this document, specifically those making a reference to [[YUI|Yui]] modules, can be considered outdated. Since Moodle 2.9 all javascript is being moved to [[Javascript Modules|Javascript modules]] and, more recently, since Moodle 3.10, to ES6 Javascript modules (see MDLSITE-6130). | |||
Please note that all new Javascript should be written using best practice ES-style javascript which is transpiled into ES2015 for backwards compatability. The use of jQuery and YUI in new code is not allowed, except where interfacing with legacy code which requires those objects and/or those features. Many parts of this document are specific to YUI and, where possible best practice modern JS should be written instead. | |||
This | This documented will be replaced in the future with an updated guide. | ||
=== Goals === | === Goals === | ||
Line 34: | Line 32: | ||
=== Variable and function naming === | === Variable and function naming === | ||
Contrary to the standard Moodle coding style, | Contrary to the standard Moodle coding style, camelCase should be used to name variables and functions in JavaScript. | ||
==== Correct ==== | ==== Correct ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
var currentY, | var currentY, | ||
courseCategory, | courseCategory, | ||
Line 50: | Line 42: | ||
function doSomething() { | function doSomething() { | ||
// Do stuff here. | |||
} | } | ||
function doSomethingElse() { | function doSomethingElse() { | ||
// Do stuff here. | |||
} | } | ||
</ | |||
var someFunction = function() { | |||
// Do stuff here. | |||
}; | |||
</syntaxhighlight> | |||
==== Incorrect ==== | ==== Incorrect ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
var current_y, | var current_y, | ||
currenty, | currenty, | ||
Line 72: | Line 70: | ||
function do_something_else() { | function do_something_else() { | ||
} | } | ||
</ | |||
var somefunction = function() { | |||
}; | |||
var some_other_function = function() { | |||
}; | |||
var somevalue = null; | |||
if (someTest) { | |||
somevalue = function() { | |||
return (something && complicated || somethingelse); | |||
}; | |||
} else { | |||
somevalue = 'basicvalue'; | |||
} | |||
</syntaxhighlight> | |||
=== Class naming === | === Class naming === | ||
Classes should be named using CamelCase starting with an uppercase | |||
letter. | letter. | ||
Line 84: | Line 98: | ||
==== Correct ==== | ==== Correct ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
// The instantiator: | // The instantiator: | ||
function Pantry() { | function Pantry() { | ||
Line 99: | Line 113: | ||
var myPantryShelf = new PantryShelf(); | var myPantryShelf = new PantryShelf(); | ||
</ | </syntaxhighlight> | ||
==== Incorrect ==== | ==== Incorrect ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
// There is no distinction here between a normal function, and one used to | // There is no distinction here between a normal function, and one used to | ||
// create a new object: | // create a new object: | ||
Line 120: | Line 134: | ||
} | } | ||
var myPantryShelf = new pantry_shelf(); | var myPantryShelf = new pantry_shelf(); | ||
</ | </syntaxhighlight> | ||
=== Constants === | === Constants === | ||
Variables intended to be constants should use the same naming style of ALL UPPERCASE. | |||
Constants are recommended for use in the following scenarios: | |||
* CSS: | * CSS: An object containing any CSS classes you may wish to use with Nodes; and | ||
* SELECTORS: | * SELECTORS: An object containing query selectors for selecting Nodes. | ||
Generally, the keys under this object should also be capitalised. | Generally, the keys under this object should also be capitalised. | ||
==== Example ==== | ==== Example ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
var CSS = { | var CSS = { | ||
MYCLASS: 'myclass', | MYCLASS: 'myclass', | ||
Line 148: | Line 162: | ||
.removeClass(CSS.MYCLASS); | .removeClass(CSS.MYCLASS); | ||
} | } | ||
</ | </syntaxhighlight> | ||
== Variables == | == Variables == | ||
All variables must: | |||
* be declared before they are used and using the <tt>var</tt> keyword; | * be declared before they are used and using the <tt>var</tt> keyword; | ||
* be declared once, and only once, for the scope in which they are used; | * be declared once, and only once, for the scope in which they are used; | ||
Line 158: | Line 172: | ||
* use sensible naming, following the naming convention. | * use sensible naming, following the naming convention. | ||
=== | === Method call wrapping === | ||
When wrapping a long line which consists of a chained series of functions, | |||
break the line at the end of each function, and continue the next chain on | |||
a new line. | |||
The line should be indented by spaces. | |||
The start of each line should contain the concatanation character, and the | |||
final line should contain a trailing semicolon. | |||
==== Correct ==== | ==== Correct ==== | ||
<syntaxhighlight lang="javascript"> | |||
var childNode = Y.Node.create('<div />') | |||
.addClass(CSS.SOMECLASS) | |||
.setAttribute('someAttribute', 'someValue') | |||
.appendTo(parentNode); | |||
</syntaxhighlight> | |||
< | ==== Incorrect ==== | ||
var | <syntaxhighlight lang="javascript"> | ||
// All on one line: | |||
var childNode = Y.Node.create('<div />').addClass(CSS.SOMECLASS).setAttribute('someAttribute', 'someValue').appendTo(parentNode); | |||
// | // A mix of separation and line concatanation: | ||
var childNode = Y.Node.create('<div />').addClass(CSS.SOMECLASS) | |||
.setAttribute('someAttribute', 'someValue').appendTo(parentNode); | |||
// | // The concatanation character is at the end of the line: | ||
/ | var childNode = Y.Node.create('<div />'). | ||
addClass(CSS.SOMECLASS). | |||
setAttribute('someAttribute', 'someValue'). | |||
</ | appendTo(parentNode); | ||
</syntaxhighlight> | |||
== | == Whitespace == | ||
=== Operators === | |||
= | There should be a space at either side of '''all''' binary operators to help improve | ||
legibility of code. This includes: | |||
* = | |||
* && | |||
* || | |||
* === | |||
* + | |||
* - | |||
* / | |||
* * | |||
There should be no space around unary operators. This includes: | |||
should: | * ! | ||
* | * ++ | ||
* | * -- | ||
There should be no space around the function operator (.) | |||
==== Correct ==== | ==== Correct ==== | ||
<syntaxhighlight lang="javascript"> | |||
// Valid binary operators: | |||
var a = 1, | |||
b = (a && 1), | |||
c = (b || 1), | |||
d = (b === c), | |||
e = Y.Node.create('<div>Some Content</div>'); | |||
// No space around the . operator when it's not a continuation: | |||
e.someFunctionCall(); | |||
// Whitespace is allowed for a function operator when it is a continuation starting on a new line: | |||
e.someFunction() | |||
.someOtherFunction() | |||
.someFinalFunction(); | |||
// Unary operators should not be separated by whitespace: | |||
a = a++; | |||
b = b--; | |||
c = (!e.someResult()); | |||
// An example bringing most of these together: | |||
var index, | |||
loopTest = 0; | |||
for (index = 0; (!loopTest <= (a / b * (c + d - e.getValue()))); index++) { | |||
loopTest = index * 12; | |||
} | |||
</syntaxhighlight> | |||
==== Incorrect ==== | ==== Incorrect ==== | ||
<syntaxhighlight lang="javascript"> | |||
var a=1, | |||
b= (a&&1), | |||
c =(b||1), | |||
d = (b===c); | |||
a = a ++; | |||
b =b++; | |||
c= c++; | |||
d = d++ ; | |||
var e = Y . Node . create('<div>Some content</div>'); | |||
= | for ( index = 0;index<a; index ++ ) { | ||
} | |||
</syntaxhighlight> | |||
=== Assignment === | |||
In the case of object property assignment, there should be a space after | |||
the colon, but not before. | |||
==== Correct ==== | ==== Correct ==== | ||
<syntaxhighlight lang="javascript"> | |||
var anObject = { | |||
someKey: 'someValue', | |||
anotherKey: Y.one(SELECTORS.FOO) | |||
}; | |||
</syntaxhighlight> | |||
==== Incorrect ==== | ==== Incorrect ==== | ||
<syntaxhighlight lang="javascript"> | |||
var anObject = { | |||
// Incorrect because a space is present both before and after the assignation character: | |||
someKey : 'someValue', | |||
// Incorrect because there is no whitespace either side of the assignation character: | |||
anotherKey:Y.one(SELECTORS.FOO) | |||
}; | |||
}; | </syntaxhighlight> | ||
== Documentation and comments == | |||
Modules should be documented using valid [https://jsdoc.app/ JSDoc]. | |||
Documentation can be generated locally by running <nowiki>grunt jsdoc</nowiki>. This will generate documentation in the <nowiki>/jsdoc</nowiki> directory at the root of your Moodle site where it can be accessed in your browser by viewing http://[wwwroot]/jsdoc. | |||
=== | === General notes === | ||
* Unless otherwise specified, comments should conform to the general style guidelines; | |||
* all comments must start with leading whitespace before the first word on each line; and | |||
* all indentation must be in addition to any existing leading whitespace on the line. | |||
=== Official documentation === | |||
All JavaScript documentation must: | |||
* use the correct docblock format; | |||
* use the correct JavaScript types where relevant (note, Int is not a valid type in JavaScript); | |||
* use all appropriate tags; | |||
* produce valid documentation using the jsdoc toolset; | |||
* have a linebreak between the description and the list of tags. | |||
< | A few notes: | ||
* Docblocks should not make use of the <nowiki>@package [name]</nowiki> tag as this is not valid in JSDoc. | |||
* JS only has <nowiki>number</nowiki>, and does not have separate types for <nowiki>int</nowiki> and <nowiki>float</nowiki>. | |||
</ | |||
==== Correct ==== | ==== Correct ==== | ||
< | |||
<syntaxhighlight lang="javascript"> | |||
/** | /** | ||
* | * All ES and AMD modules should have a module directive with the AMD module name. | ||
* | |||
* For a module located at question/type/example/amd/src/dothethings.js the module name will be as follows. | |||
* | * | ||
* @ | * @module qtype_example/dothethings | ||
*/ | */ | ||
/** | |||
* If the module defines a class, it should be documented with the class tag. | |||
// | * | ||
* If the class is the default export for the module, then the name specified should match the module name. | |||
* | |||
* @class qtype_example/dothethings | |||
*/ | |||
export default class { | |||
// ... | |||
} | |||
/** | |||
* If the module defines multiple classes then they should be namespaced under the module name. | |||
* | |||
* @class qtype_example/dothethings/MyClass | |||
*/ | |||
= | export const MyClass = class { | ||
// ... | |||
} | } | ||
==== | /** | ||
* This is an example docblock comment. It describes a function called | |||
* marmite. | |||
* | |||
* It adds a number of jars of marmite to the cupboard. | |||
* | |||
* @method | |||
* @param {Number} [jarCount=1] The number of jars of marmite to add to the | |||
* cupboard. This parameter is optional and defaults to 1. | |||
*/ | |||
export const addMarmite = (jarCount = 1) => { | |||
// ... | |||
}; | |||
/** | |||
* This docblock describes the property weight, in grams. | |||
* | |||
* @property {number} weight | |||
*/ | |||
export const weight = 400; | |||
</syntaxhighlight> | |||
==== Incorrect ==== | |||
<syntaxhighlight lang="javascript"> | |||
/* | |||
* This block would not be picked up by jsdoc as it only has a single * in the opening line. | |||
* | |||
* @method foo | |||
*/ | |||
// This comment would not be picked up by jsdoc either. | |||
</syntaxhighlight> | |||
</ | |||
=== General comments === | |||
All shorter comments, for example those explaining the subsequent few lines of code should use the // style of comments. | |||
== | ==== Correct ==== | ||
= | <syntaxhighlight lang="javascript"> | ||
// This is a valid set of comments for one line. | |||
// And this is a valid longer comment to describe the subsequent few lines | |||
// in as much detail as required. It can consist of multiple sentences, as | |||
// long as each new line starts with the correct comment style. | |||
</syntaxhighlight> | |||
</ | |||
==== Incorrect ==== | ==== Incorrect ==== | ||
= | <syntaxhighlight lang="javascript"> | ||
/* This is an invalid comment style for short comments. */ | |||
//This is also an invalid style as there is no leading whitespace after the | |||
//comment indicator. | |||
/** | |||
* This is an invalid multi-line comment. Multi-line comments should not | |||
* use the docblock style comments unless they are a valid and fully | |||
* formatted docblock. | |||
*/ | |||
/* | |||
* This is an also invalid multi-line comment. Although it is not a full | |||
* docblock style, it does not start with the // style of comment | |||
* indicator. | |||
*/ | |||
</syntaxhighlight> | |||
== Documentation and comments == | |||
Modules should be documented using valid [https://jsdoc.app/ JSDoc]. | |||
Documentation can be generated locally by running <nowiki>grunt jsdoc</nowiki>. This will generate documentation in the <nowiki>/jsdoc</nowiki> directory at the root of your Moodle site where it can be accessed in your browser by viewing http://[wwwroot]/jsdoc. | |||
=== General notes === | === General notes === | ||
Line 426: | Line 448: | ||
* have a linebreak between the description and the list of tags. | * have a linebreak between the description and the list of tags. | ||
A few notes: | |||
* Docblocks should not make use of the <nowiki>@package [name]</nowiki> tag as this is not valid in JSDoc. | |||
* JS only has <nowiki>number</nowiki>, and does not have separate types for <nowiki>int</nowiki> and <nowiki>float</nowiki>. | |||
==== Correct ==== | |||
<syntaxhighlight lang="javascript"> | |||
/** | |||
* All ES and AMD modules should have a module directive with the AMD module name. | |||
* | |||
* For a module located at question/type/example/amd/src/dothethings.js the module name will be as follows. | |||
* | |||
* @module qtype_example/dothethings | |||
*/ | |||
= | /** | ||
* If the module defines a class, it should be documented with the class tag. | |||
* | |||
* If the class is the default export for the module, then the name specified should match the module name. | |||
* | |||
* @class qtype_example/dothethings | |||
*/ | |||
export default class { | |||
// ... | |||
} | |||
/** | |||
* If the module defines multiple classes then they should be namespaced under the module name. | |||
* | |||
* @class qtype_example/dothethings/MyClass | |||
*/ | |||
export const MyClass = class { | |||
// ... | |||
} | |||
/** | /** | ||
* This is an example docblock comment. It describes a function called | * This is an example docblock comment. It describes a function called | ||
Line 441: | Line 489: | ||
* It adds a number of jars of marmite to the cupboard. | * It adds a number of jars of marmite to the cupboard. | ||
* | * | ||
* @method | * @method | ||
* @param {Number} [jarCount=1] The number of jars of marmite to add to the | * @param {Number} [jarCount=1] The number of jars of marmite to add to the | ||
* cupboard. This parameter is optional and defaults to 1. | * cupboard. This parameter is optional and defaults to 1. | ||
*/ | */ | ||
</ | export const addMarmite = (jarCount = 1) => { | ||
// ... | |||
}; | |||
/** | |||
* This docblock describes the property weight, in grams. | |||
* | |||
* @property {number} weight | |||
*/ | |||
export const weight = 400; | |||
</syntaxhighlight> | |||
==== Incorrect ==== | ==== Incorrect ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
/* | /* | ||
* This | * This block would not be picked up by jsdoc as it only has a single * in the opening line. | ||
* | * | ||
* @method foo | * @method foo | ||
*/ | */ | ||
// This | // This comment would not be picked up by jsdoc either. | ||
// YUIDoc. | </syntaxhighlight> | ||
=== General comments === | |||
All shorter comments, for example those explaining the subsequent few lines of code should use the // style of comments. | |||
Comments not intended for official documentation must *not* use the Docblock style of commenting as YUIDoc will attempt to include the comment in official documentation. | |||
==== Correct ==== | |||
<syntaxhighlight lang="javascript"> | |||
// This is a valid set of comments for one line. | |||
// And this is a valid longer comment to describe the subsequent few lines | |||
// in as much detail as required. It can consist of multiple sentences, as | |||
// long as each new line starts with the correct comment style. | |||
</syntaxhighlight> | |||
/** | ==== Incorrect ==== | ||
<syntaxhighlight lang="javascript"> | |||
/* This is an invalid comment style for short comments. */ | |||
//This is also an invalid style as there is no leading whitespace after the | |||
//comment indicator. | |||
/** | /** | ||
* | * This is an invalid multi-line comment. Multi-line comments should not | ||
* | * use the docblock style comments unless they are a valid and fully | ||
* | * formatted docblock. | ||
*/ | */ | ||
/ | /* | ||
* This | * This is an also invalid multi-line comment. Although it is not a full | ||
* | * docblock style, it does not start with the // style of comment | ||
* | * indicator. | ||
*/ | */ | ||
</ | </syntaxhighlight> | ||
=== General comments === | === General comments === | ||
Line 489: | Line 563: | ||
==== Correct ==== | ==== Correct ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
// This is a valid set of comments for one line. | // This is a valid set of comments for one line. | ||
Line 495: | Line 569: | ||
// in as much detail as required. It can consist of multiple sentences, as | // in as much detail as required. It can consist of multiple sentences, as | ||
// long as each new line starts with the correct comment style. | // long as each new line starts with the correct comment style. | ||
</ | </syntaxhighlight> | ||
==== Incorrect ==== | ==== Incorrect ==== | ||
< | <syntaxhighlight lang="javascript"> | ||
/* This is an invalid comment style for short comments. */ | /* This is an invalid comment style for short comments. */ | ||
Line 516: | Line 590: | ||
* indicator. | * indicator. | ||
*/ | */ | ||
</code> | </syntaxhighlight> | ||
== Promises == | |||
Promises are used extensively in modern Moodle Javascript APIs to handle asynchronous situations. It is unfortunately common to misunderstand how they operate and introduce bugs which only expose themselves in asynchronous edge cases (see article [https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html we have a problem with promises]). To encourage promise usage to be more understandable, consistent and avoid edge case bugs, we have adopted best practices suggested by Nolan Lawson and verified by [https://github.com/xjamundx/eslint-plugin-promise eslint-plugin-promise]. | |||
=== Always return or throw === | |||
When writing promises they must: | |||
* return another promise, or | |||
* return a synchronous value (or undefined), or | |||
* throw a synchronous error | |||
==== Correct ==== | |||
<syntaxhighlight lang="javascript"> | |||
str.get_strings(stringRequests).then(function(title) { | |||
return templates.renderPix(image, 'core', title); | |||
}).then(function(pixhtml) { | |||
$('#selector').html(pixhtml); | |||
makeUIVisible(); | |||
return; | |||
}); | |||
</syntaxhighlight> | |||
==== Incorrect ==== | |||
<syntaxhighlight lang="javascript"> | |||
str.get_strings(stringRequests).then(function(title) { | |||
templates.renderPix(image, 'core', title).function(pixhtml) { | |||
$('#selector').html(pixhtml); | |||
}); | |||
}).then(function() { | |||
// Wrong because renderPix() has not guaranted to be resolved here. | |||
makeUIVisible(); | |||
}); | |||
</syntaxhighlight> | |||
=== Chain rather than nest === | |||
Promises as are a construct which help prevent the <i>pyramid of doom</i> and regain linear control flow and error handling. They should not be nested. | |||
* Promises should be chained rather than nested. | |||
* $.when() should be used to deal with the result of multiple promises | |||
==== Correct ==== | |||
<syntaxhighlight lang="javascript"> | |||
// Single promise chain: | |||
str.get_string('title') | |||
.then(function(title) { | |||
return templates.renderPix(image, 'core', title); | |||
}).then(function(pixhtml) { | |||
actionitem.find('.icon').replaceWith(pixhtml); | |||
return; | |||
}); | |||
// Need the result of two promises at once: | |||
$.when(Str.get_string('competencypicker', 'tool_lp'), renderPromise()) | |||
.then(function(title, html) { | |||
self._popup = new Dialogue(title, html); | |||
}); | |||
</syntaxhighlight> | |||
==== Incorrect ==== | |||
<syntaxhighlight lang="javascript"> | |||
str.get_string('title') | |||
.then(function(title) { | |||
return templates.renderPix(image, 'core', title).then(function(pixhtml) { | |||
actionitem.find('.icon').replaceWith(pixhtml); | |||
return; | |||
}); | |||
}); | |||
return renderPromise().then(function (html) { | |||
return Str.get_string('competencypicker', 'tool_lp').then(function(title) { | |||
self._popup = new Dialogue(title, html); | |||
}); | |||
}); | |||
</syntaxhighlight> | |||
=== Avoid mixing callbacks and promises === | |||
Avoid mixing callbacks and promises. Design code to embrace promise-y patterns for asynchronous code to make maximum use of promises. | |||
==== Correct ==== | |||
<syntaxhighlight lang="javascript"> | |||
function doWork(input) { | |||
return renderPromise(); | |||
} | |||
doWork(input).then(function () { | |||
$('#selector').html(html); | |||
return; | |||
}).catch(Notification.exception); | |||
</syntaxhighlight> | |||
==== Incorrect ==== | |||
<syntaxhighlight lang="javascript"> | |||
function doWork(input, successHandler, errorHandler) { | |||
renderPromise().then(function (html) { | |||
successHandler(html) | |||
}).catch(errorHandler); | |||
} | |||
doWork(input, function () { | |||
$('#selector').html(html); | |||
}, function (error){ | |||
Notification.exception(error); | |||
}); | |||
</syntaxhighlight> | |||
== See Also == | == See Also == | ||
* [[Coding_style|Coding Style]] | |||
* [[CSS_Coding_Style|CSS Coding Style]] | |||
* [[Coding]] | * [[Coding]] | ||
[[Category:Coding guidelines]] | |||
[[Category:Javascript]] |
Latest revision as of 04:17, 4 October 2021
The Moodle JavaScript coding style.
This feature has been marked as deprecated since Moodle 3.10
Overview
This document outlines the exceptions to the general Moodle Coding style which apply to JavaScript.
Unless otherwise specified, developers should follow the general coding style for advice on coding style.
Note that some part of this document, specifically those making a reference to Yui modules, can be considered outdated. Since Moodle 2.9 all javascript is being moved to Javascript modules and, more recently, since Moodle 3.10, to ES6 Javascript modules (see MDLSITE-6130).
Please note that all new Javascript should be written using best practice ES-style javascript which is transpiled into ES2015 for backwards compatability. The use of jQuery and YUI in new code is not allowed, except where interfacing with legacy code which requires those objects and/or those features. Many parts of this document are specific to YUI and, where possible best practice modern JS should be written instead.
This documented will be replaced in the future with an updated guide.
Goals
Consistent coding style is important in any development project, and particularly when many developers are involved. A standard style helps to ensure that the code is easier to read and understand, which helps overall quality.
Abstract goals we strive for:
- simplicity
- readability
- tool friendliness
Naming conventions
Variable and function naming
Contrary to the standard Moodle coding style, camelCase should be used to name variables and functions in JavaScript.
Correct
var currentY,
courseCategory,
lastValue,
lastBackgroundColor;
function doSomething() {
// Do stuff here.
}
function doSomethingElse() {
// Do stuff here.
}
var someFunction = function() {
// Do stuff here.
};
Incorrect
var current_y,
currenty,
course_category,
coursecategory,
last_value,
lastvalue,
last_background_color,
lastbackgroundcolor;
function dosomething() {
}
function do_something_else() {
}
var somefunction = function() {
};
var some_other_function = function() {
};
var somevalue = null;
if (someTest) {
somevalue = function() {
return (something && complicated || somethingelse);
};
} else {
somevalue = 'basicvalue';
}
Class naming
Classes should be named using CamelCase starting with an uppercase letter.
This helps to clearly separate variables, and standard functions from those used to create a new instance.
Correct
// The instantiator:
function Pantry() {
// Setup code goes here.
}
// Making use of it:
var myPantry = new Pantry();
// And another:
function PantryShelf() {
}
var myPantryShelf = new PantryShelf();
Incorrect
// There is no distinction here between a normal function, and one used to
// create a new object:
function pantry() {
}
// This results in an unclear object creation:
var myPantry = new pantry();
// This one is also incorrect, despite using camelCase:
function pantryShelf() {
}
var myPantryShelf = new pantryShelf();
// This one is also incorrect:
function pantry_shelf() {
}
var myPantryShelf = new pantry_shelf();
Constants
Variables intended to be constants should use the same naming style of ALL UPPERCASE.
Constants are recommended for use in the following scenarios:
- CSS: An object containing any CSS classes you may wish to use with Nodes; and
- SELECTORS: An object containing query selectors for selecting Nodes.
Generally, the keys under this object should also be capitalised.
Example
var CSS = {
MYCLASS: 'myclass',
YOURCLASS: 'yourclass'
},
SELECTORS = {
MYNODES: 'div.example .myclass',
YOURNODES: 'div.example .yourclass'
};
function anExampleFunction() {
theNode = Y.one(SELECTORS.MYNODES)
.addClass(CSS.YOURCLASS)
.removeClass(CSS.MYCLASS);
}
Variables
All variables must:
- be declared before they are used and using the var keyword;
- be declared once, and only once, for the scope in which they are used;
- only be declared if they are to be used; and
- use sensible naming, following the naming convention.
Method call wrapping
When wrapping a long line which consists of a chained series of functions, break the line at the end of each function, and continue the next chain on a new line.
The line should be indented by spaces.
The start of each line should contain the concatanation character, and the final line should contain a trailing semicolon.
Correct
var childNode = Y.Node.create('<div />')
.addClass(CSS.SOMECLASS)
.setAttribute('someAttribute', 'someValue')
.appendTo(parentNode);
Incorrect
// All on one line:
var childNode = Y.Node.create('<div />').addClass(CSS.SOMECLASS).setAttribute('someAttribute', 'someValue').appendTo(parentNode);
// A mix of separation and line concatanation:
var childNode = Y.Node.create('<div />').addClass(CSS.SOMECLASS)
.setAttribute('someAttribute', 'someValue').appendTo(parentNode);
// The concatanation character is at the end of the line:
var childNode = Y.Node.create('<div />').
addClass(CSS.SOMECLASS).
setAttribute('someAttribute', 'someValue').
appendTo(parentNode);
Whitespace
Operators
There should be a space at either side of all binary operators to help improve legibility of code. This includes:
- =
- &&
- ||
- ===
- +
- -
- /
- *
There should be no space around unary operators. This includes:
- !
- ++
- --
There should be no space around the function operator (.)
Correct
// Valid binary operators:
var a = 1,
b = (a && 1),
c = (b || 1),
d = (b === c),
e = Y.Node.create('<div>Some Content</div>');
// No space around the . operator when it's not a continuation:
e.someFunctionCall();
// Whitespace is allowed for a function operator when it is a continuation starting on a new line:
e.someFunction()
.someOtherFunction()
.someFinalFunction();
// Unary operators should not be separated by whitespace:
a = a++;
b = b--;
c = (!e.someResult());
// An example bringing most of these together:
var index,
loopTest = 0;
for (index = 0; (!loopTest <= (a / b * (c + d - e.getValue()))); index++) {
loopTest = index * 12;
}
Incorrect
var a=1,
b= (a&&1),
c =(b||1),
d = (b===c);
a = a ++;
b =b++;
c= c++;
d = d++ ;
var e = Y . Node . create('<div>Some content</div>');
for ( index = 0;index<a; index ++ ) {
}
Assignment
In the case of object property assignment, there should be a space after the colon, but not before.
Correct
var anObject = {
someKey: 'someValue',
anotherKey: Y.one(SELECTORS.FOO)
};
Incorrect
var anObject = {
// Incorrect because a space is present both before and after the assignation character:
someKey : 'someValue',
// Incorrect because there is no whitespace either side of the assignation character:
anotherKey:Y.one(SELECTORS.FOO)
};
Documentation and comments
Modules should be documented using valid JSDoc.
Documentation can be generated locally by running grunt jsdoc. This will generate documentation in the /jsdoc directory at the root of your Moodle site where it can be accessed in your browser by viewing http://[wwwroot]/jsdoc.
General notes
- Unless otherwise specified, comments should conform to the general style guidelines;
- all comments must start with leading whitespace before the first word on each line; and
- all indentation must be in addition to any existing leading whitespace on the line.
Official documentation
All JavaScript documentation must:
- use the correct docblock format;
- use the correct JavaScript types where relevant (note, Int is not a valid type in JavaScript);
- use all appropriate tags;
- produce valid documentation using the jsdoc toolset;
- have a linebreak between the description and the list of tags.
A few notes:
- Docblocks should not make use of the @package [name] tag as this is not valid in JSDoc.
- JS only has number, and does not have separate types for int and float.
Correct
/**
* All ES and AMD modules should have a module directive with the AMD module name.
*
* For a module located at question/type/example/amd/src/dothethings.js the module name will be as follows.
*
* @module qtype_example/dothethings
*/
/**
* If the module defines a class, it should be documented with the class tag.
*
* If the class is the default export for the module, then the name specified should match the module name.
*
* @class qtype_example/dothethings
*/
export default class {
// ...
}
/**
* If the module defines multiple classes then they should be namespaced under the module name.
*
* @class qtype_example/dothethings/MyClass
*/
export const MyClass = class {
// ...
}
/**
* This is an example docblock comment. It describes a function called
* marmite.
*
* It adds a number of jars of marmite to the cupboard.
*
* @method
* @param {Number} [jarCount=1] The number of jars of marmite to add to the
* cupboard. This parameter is optional and defaults to 1.
*/
export const addMarmite = (jarCount = 1) => {
// ...
};
/**
* This docblock describes the property weight, in grams.
*
* @property {number} weight
*/
export const weight = 400;
Incorrect
/*
* This block would not be picked up by jsdoc as it only has a single * in the opening line.
*
* @method foo
*/
// This comment would not be picked up by jsdoc either.
General comments
All shorter comments, for example those explaining the subsequent few lines of code should use the // style of comments.
Correct
// This is a valid set of comments for one line.
// And this is a valid longer comment to describe the subsequent few lines
// in as much detail as required. It can consist of multiple sentences, as
// long as each new line starts with the correct comment style.
Incorrect
/* This is an invalid comment style for short comments. */
//This is also an invalid style as there is no leading whitespace after the
//comment indicator.
/**
* This is an invalid multi-line comment. Multi-line comments should not
* use the docblock style comments unless they are a valid and fully
* formatted docblock.
*/
/*
* This is an also invalid multi-line comment. Although it is not a full
* docblock style, it does not start with the // style of comment
* indicator.
*/
Documentation and comments
Modules should be documented using valid JSDoc.
Documentation can be generated locally by running grunt jsdoc. This will generate documentation in the /jsdoc directory at the root of your Moodle site where it can be accessed in your browser by viewing http://[wwwroot]/jsdoc.
General notes
- Unless otherwise specified, comments should conform to the general style guidelines;
- all comments must start with leading whitespace before the first word on each line; and
- all indentation must be in addition to any existing leading whitespace on the line.
Official documentation
All JavaScript documentation must:
- use the correct docblock format;
- use the correct JavaScript types where relevant (note, Int is not a valid type in JavaScript);
- use all appropriate tags;
- produce valid documentation using the YUIDoc toolset;
- have a linebreak between the description and the list of tags.
A few notes:
- Docblocks should not make use of the @package [name] tag as this is not valid in JSDoc.
- JS only has number, and does not have separate types for int and float.
Correct
/**
* All ES and AMD modules should have a module directive with the AMD module name.
*
* For a module located at question/type/example/amd/src/dothethings.js the module name will be as follows.
*
* @module qtype_example/dothethings
*/
/**
* If the module defines a class, it should be documented with the class tag.
*
* If the class is the default export for the module, then the name specified should match the module name.
*
* @class qtype_example/dothethings
*/
export default class {
// ...
}
/**
* If the module defines multiple classes then they should be namespaced under the module name.
*
* @class qtype_example/dothethings/MyClass
*/
export const MyClass = class {
// ...
}
/**
* This is an example docblock comment. It describes a function called
* marmite.
*
* It adds a number of jars of marmite to the cupboard.
*
* @method
* @param {Number} [jarCount=1] The number of jars of marmite to add to the
* cupboard. This parameter is optional and defaults to 1.
*/
export const addMarmite = (jarCount = 1) => {
// ...
};
/**
* This docblock describes the property weight, in grams.
*
* @property {number} weight
*/
export const weight = 400;
Incorrect
/*
* This block would not be picked up by jsdoc as it only has a single * in the opening line.
*
* @method foo
*/
// This comment would not be picked up by jsdoc either.
General comments
All shorter comments, for example those explaining the subsequent few lines of code should use the // style of comments.
Comments not intended for official documentation must *not* use the Docblock style of commenting as YUIDoc will attempt to include the comment in official documentation.
Correct
// This is a valid set of comments for one line.
// And this is a valid longer comment to describe the subsequent few lines
// in as much detail as required. It can consist of multiple sentences, as
// long as each new line starts with the correct comment style.
Incorrect
/* This is an invalid comment style for short comments. */
//This is also an invalid style as there is no leading whitespace after the
//comment indicator.
/**
* This is an invalid multi-line comment. Multi-line comments should not
* use the docblock style comments unless they are a valid and fully
* formatted docblock.
*/
/*
* This is an also invalid multi-line comment. Although it is not a full
* docblock style, it does not start with the // style of comment
* indicator.
*/
General comments
All shorter comments, for example those explaining the subsequent few lines of code should use the // style of comments.
Comments not intended for official documentation must *not* use the Docblock style of commenting as YUIDoc will attempt to include the comment in official documentation.
Correct
// This is a valid set of comments for one line.
// And this is a valid longer comment to describe the subsequent few lines
// in as much detail as required. It can consist of multiple sentences, as
// long as each new line starts with the correct comment style.
Incorrect
/* This is an invalid comment style for short comments. */
//This is also an invalid style as there is no leading whitespace after the
//comment indicator.
/**
* This is an invalid multi-line comment. Multi-line comments should not
* use the docblock style comments unless they are a valid and fully
* formatted docblock.
*/
/*
* This is an also invalid multi-line comment. Although it is not a full
* docblock style, it does not start with the // style of comment
* indicator.
*/
Promises
Promises are used extensively in modern Moodle Javascript APIs to handle asynchronous situations. It is unfortunately common to misunderstand how they operate and introduce bugs which only expose themselves in asynchronous edge cases (see article we have a problem with promises). To encourage promise usage to be more understandable, consistent and avoid edge case bugs, we have adopted best practices suggested by Nolan Lawson and verified by eslint-plugin-promise.
Always return or throw
When writing promises they must:
- return another promise, or
- return a synchronous value (or undefined), or
- throw a synchronous error
Correct
str.get_strings(stringRequests).then(function(title) {
return templates.renderPix(image, 'core', title);
}).then(function(pixhtml) {
$('#selector').html(pixhtml);
makeUIVisible();
return;
});
Incorrect
str.get_strings(stringRequests).then(function(title) {
templates.renderPix(image, 'core', title).function(pixhtml) {
$('#selector').html(pixhtml);
});
}).then(function() {
// Wrong because renderPix() has not guaranted to be resolved here.
makeUIVisible();
});
Chain rather than nest
Promises as are a construct which help prevent the pyramid of doom and regain linear control flow and error handling. They should not be nested.
- Promises should be chained rather than nested.
- $.when() should be used to deal with the result of multiple promises
Correct
// Single promise chain:
str.get_string('title')
.then(function(title) {
return templates.renderPix(image, 'core', title);
}).then(function(pixhtml) {
actionitem.find('.icon').replaceWith(pixhtml);
return;
});
// Need the result of two promises at once:
$.when(Str.get_string('competencypicker', 'tool_lp'), renderPromise())
.then(function(title, html) {
self._popup = new Dialogue(title, html);
});
Incorrect
str.get_string('title')
.then(function(title) {
return templates.renderPix(image, 'core', title).then(function(pixhtml) {
actionitem.find('.icon').replaceWith(pixhtml);
return;
});
});
return renderPromise().then(function (html) {
return Str.get_string('competencypicker', 'tool_lp').then(function(title) {
self._popup = new Dialogue(title, html);
});
});
Avoid mixing callbacks and promises
Avoid mixing callbacks and promises. Design code to embrace promise-y patterns for asynchronous code to make maximum use of promises.
Correct
function doWork(input) {
return renderPromise();
}
doWork(input).then(function () {
$('#selector').html(html);
return;
}).catch(Notification.exception);
Incorrect
function doWork(input, successHandler, errorHandler) {
renderPromise().then(function (html) {
successHandler(html)
}).catch(errorHandler);
}
doWork(input, function () {
$('#selector').html(html);
}, function (error){
Notification.exception(error);
});