Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Javascript/Coding Style: Difference between revisions

From MoodleDocs
No edit summary
(MDL-57139: Promises guidance)
(33 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{Work in progress}}
The Moodle JavaScript coding style
The Moodle JavaScript coding style


== Overview ==
== Overview ==


Although most of the Moodle standard coding style applies to JavaScript,
This document outlines the exceptions to the general Moodle [[Coding_style|Coding style]] which apply to JavaScript.  
there are a number of exceptions. These are largely as a result of common
practice within the JavaScript community, or for increased clarity given different
terminology when dealing with frontend code (e.g. use of cartesian positions).
 
=== Scope ===


This document describes style guidelines for developers work on or with JavaScript code in Moodle.
Unless otherwise specified, developers should follow the general coding style for advice on coding style.  


=== Goals ===
=== Goals ===
Line 31: Line 24:
=== Variable and function naming ===
=== Variable and function naming ===


Contrary to the standard Moodle coding style, we prefer to use camelCase
Contrary to the standard Moodle coding style, camelCase should be used to name variables and functions in JavaScript.
for JavaScript.
 
The justification for this is that:
* it is a commonly accepted practice within the wider JavaScript community;
* it is easier to read when dealing with variables describing cartesian points (e.g. currenty versus currentY); and
* it is the style used by the upstream YUI library for all variable names, and all CSS settings.


==== Correct ====
==== Correct ====
<code javascript>
<code javascript>
    var currentY,
var currentY,
     courseCategory,
     courseCategory,
     lastValue,
     lastValue,
     lastBackgroundColor;
     lastBackgroundColor;
function doSomething() {
    // Do stuff here.
}
function doSomethingElse() {
    // Do stuff here.
}
var someFunction = function() {
    // Do stuff here.
};
</code>
</code>


Line 57: Line 56:
     last_background_color,
     last_background_color,
     lastbackgroundcolor;
     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';
}
</code>
</code>


== Variable declaration ==
=== Class naming ===


As usual with JavaScript, all variables musut be declared once for the
Classes should be named using CamelCase starting with an uppercase
scope in which they are used using the ```var``` keyword.
letter.


=== Number of variable declarations ===
This helps to clearly separate variables, and standard functions from those
used to create a new instance.


The number of variable declarations should be kept to a sensible minimum.
==== Correct ====
<code javascript>
// The instantiator:
function Pantry() {
    // Setup code goes here.
}


==== Correct ====
// Making use of it:
var myPantry = new Pantry();


<code javascript>
var oneVariable,
    twoVariable,
    threeVariable,
    more;


// Some other code goes here which breaks up the logical flow
// And another:
function PantryShelf() {
}


// Some more variable definitions relating to the next set of code goes
var myPantryShelf = new PantryShelf();
// here.
var anotherVariable,
    andAnother;
</code>
</code>


==== Incorrect ====
==== Incorrect ====
<code javascript>
// 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();


<code javascript>
// This one is also incorrect:
var oneVariable;
function pantry_shelf() {
var twoVariable;
}
var threeVariable;
var myPantryShelf = new pantry_shelf();
var more;
</code>
</code>


=== Initial values ===
=== Constants ===


Variable definitions describing variables which have initial content
Variables intended to be constants should use the same naming style of ALL UPPERCASE.
should:
* fit on a single line in the case of simple types;
* be spread across multiple lines for complex types (including 'all' objects and arrays, no matter how simple); and
* empty object and array definitions should break into multiple lines. This is so that should values be added later the history on the surrounding lines is preserved.


==== Correct ====
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 ====
<code javascript>
<code javascript>
var aBoolean = true,
var CSS = {
    aNumber = 10,
        MYCLASS: 'myclass',
    anObjectWithoutContent = {
        YOURCLASS: 'yourclass'
     },
     },
     anObjectWithContent = {
     SELECTORS = {
         exampleContent: 'value'
         MYNODES: 'div.example .myclass',
        YOURNODES: 'div.example .yourclass'
     };
     };
function anExampleFunction() {
    theNode = Y.one(SELECTORS.MYNODES)
        .addClass(CSS.YOURCLASS)
        .removeClass(CSS.MYCLASS);
}
</code>
== Variables ==
All variables must:
* 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;
* 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 ====
<code javascript>
var childNode = Y.Node.create('<div />')
        .addClass(CSS.SOMECLASS)
        .setAttribute('someAttribute', 'someValue')
        .appendTo(parentNode);
</code>
</code>
==== Incorrect ====
==== Incorrect ====
<code 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);
</code>
== 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 ====
<code javascript>
<code javascript>
var oneVariable;
// Valid binary operators:
var twoVariable;
var a = 1,
var threeVariable;
    b = (a && 1),
var more;
    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;
}
 
</code>
</code>


== Line wrapping ==
==== Incorrect ====
<code 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>');


== Whitespace ==
for ( index = 0;index<a; index ++ ) {
}
</code>
 
=== Assignment ===
In the case of object property assignment, there should be a space after
the colon, but not before.


TODO: Spacing between variable names and values:
==== Correct ====
<code javascript>
<code javascript>
    var flatValue = 'value',
var anObject = {
    anObject = {
         someKey: 'someValue',
         someKey: 'someContent'
        anotherKey: Y.one(SELECTORS.FOO)
     };
     };
</code>
</code>


==== Incorrect ====
<code 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)
    };
</code>


== Documentation and comments ==
== Documentation and comments ==


We are attempting to document all YUI modules that we write and as such largely follow the upstream YUI guidance which is available at http://yui.github.io/yuidoc/syntax/index.html.
Modules should be documented using the standard YUI guidance: http://yui.github.io/yuidoc/syntax/index.html.


=== General notes ===
=== General notes ===
Line 162: Line 321:


<code javascript>
<code javascript>
/**
* This docblock describes a YUI module.
*
* @module moodle-mod_food-marmite
*/
/**
* This docblock describes the marmite class within the
* moodle-mod_food-marmite module.
*
* @class Marmite
*/
/**
/**
  * This is an example docblock comment. It describes a function called
  * This is an example docblock comment. It describes a function called
Line 172: Line 344:
  * cupboard. This parameter is optional and defaults to 1.
  * cupboard. This parameter is optional and defaults to 1.
  * @chainable
  * @chainable
*/
/**
* This docblock describes the property weight, in grams.
*
* @property weight
* @type {Number}
* @default '500'
*/
/**
* This docblock describes an attribute.
*
* @attribute weight
* @type {Number}
* @default '500'
  */
  */
</code>
</code>
Line 244: Line 432:
  */
  */
</code>
</code>
== 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 ====
<code javascript>
str.get_strings(stringRequests).then(function(title) {
    return templates.renderPix(image, 'core', title);
}).then(function(pixhtml) {
    $('#selector').html(pixhtml);
    makeUIVisible();
    return;
});
</code>
==== Incorrect ====
<code 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();
});
</code>
=== 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 ====
<code 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);
});
</code>
==== Incorrect ====
<code 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);
    });
});
</code>
=== 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 ====
<code javascript>
function doWork(input) {
    return renderPromise();
}
doWork(input).then(function () {
  $('#selector').html(html);
  return;
}).catch(Notification.exception);
</code>
==== Incorrect ====
<code 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);
});
</code>
== See Also ==
* [[Coding_style|Coding Style]]
* [[CSS_Coding_Style|CSS Coding Style]]
* [[Coding]]
[[Category:Coding guidelines]]
[[Category:Javascript]]

Revision as of 14:21, 12 June 2017

The Moodle JavaScript coding style

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.

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('

')
       .addClass(CSS.SOMECLASS)
       .setAttribute('someAttribute', 'someValue')
       .appendTo(parentNode);

Incorrect

// All on one line:

var childNode = Y.Node.create('
').addClass(CSS.SOMECLASS).setAttribute('someAttribute', 'someValue').appendTo(parentNode);

// A mix of separation and line concatanation:

var childNode = Y.Node.create('
').addClass(CSS.SOMECLASS)
       .setAttribute('someAttribute', 'someValue').appendTo(parentNode);

// The concatanation character is at the end of the line:

var childNode = Y.Node.create('
').
       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('
Some Content
');

// 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('
Some content
');

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 the standard YUI guidance: http://yui.github.io/yuidoc/syntax/index.html.

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.

Note:

YUIDoc will only generate documentation for docblocks starting with /**.

YUIDoc will try to generate documentation for *all* docblocks starting /**.

Correct

/**

* This docblock describes a YUI module.
*
* @module moodle-mod_food-marmite
*/

/**

* This docblock describes the marmite class within the
* moodle-mod_food-marmite module.
*
* @class Marmite
*/

/**

* This is an example docblock comment. It describes a function called
* marmite.
*
* It adds a number of jars of marmite to the cupboard.
*
* @method addMarmite
* @param {Number} [jarCount=1] The number of jars of marmite to add to the
* cupboard. This parameter is optional and defaults to 1.
* @chainable
*/

/**

* This docblock describes the property weight, in grams.
*
* @property weight
* @type {Number}
* @default '500'
*/

/**

* This docblock describes an attribute.
*
* @attribute weight
* @type {Number}
* @default '500'
*/

Incorrect

/*

* This is an invalid comment block. It wouldn't be picked up by yuidoc as
* the comment style is incorrect.
*
* @method foo
*/

// This is also an invalid comment block and wouldn't be picked up by // YUIDoc.

/**

  • Although this style would be picked up by YUIDoc, it is hard to read.
  • @method foo
  • /

/**

*Although this style would be picked up by YUIDoc, it is also hard to read.
*
*@method foo
*/

/**

* This docblock is mostly valid but does not include a linebreak between
* the description, and the tags.
* @method foo
*/


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

});

See Also