Difference between revisions of "Javascript/Coding Style"

Jump to: navigation, search
m (Changed protection level for "Javascript/Coding style": Unprotecting to allow edits to get up to agreed integration vote ([Edit=Allow only autoconfirmed users] (indefinite) [Move=Allow only autoconfirmed users] (indefinite)))
(Update following feedback in MDL-43190)
Line 1: Line 1:
{{draft}}
 
{{Work in progress}}
 
 
This article is a proposal for a more codified coding style for JavaScript and will replace any existing coding styles after appropriate discussion, and consensus has been reached.
 
 
 
The Moodle JavaScript coding style
 
The Moodle JavaScript coding style
  
Line 181: Line 176:
 
* only be declared if they are to be used; and
 
* only be declared if they are to be used; and
 
* use sensible naming, following the naming convention.
 
* use sensible naming, following the naming convention.
 
=== Number of variable declarations ===
 
 
The number of variable declarations should be kept to a sensible minimum,
 
though this interpretation is left largely up to the individual developer.
 
 
Where a set of variables is separated by block of code, it is reasonable to
 
logically separate the sets of variables around the code block.
 
 
==== Correct ====
 
 
<code javascript>
 
var oneVariable,
 
    twoVariable,
 
    threeVariable,
 
    more;
 
 
// Some other code goes here which breaks up the logical flow
 
oneVariable = Y.Node.Create('<div></div>');
 
twoVariable = Y.one('#someid');
 
 
// Some more variable definitions relating to the next set of code goes
 
// here.
 
var anotherVariable,
 
    andAnother;
 
</code>
 
 
==== Incorrect ====
 
 
<code javascript>
 
// Only one variable definition should be used:
 
var oneVariable;
 
var twoVariable;
 
var threeVariable;
 
var more;
 
</code>
 
 
=== Initial values ===
 
 
Variable definitions describing variables which have initial content
 
should:
 
* fit on a single line in the case of simple types; and
 
* be spread across multiple lines for complex types (including '''all''' objects and arrays, no matter how simple) - this includes those which are empty.
 
 
The reason for this is primarily to help preserve revision and change
 
history for different lines of code in a sensible and meaningful fashion.
 
 
==== Correct ====
 
 
<code javascript>
 
var someBoolean = true,
 
    someNumber = 10,
 
    anObjectWithoutContent = {
 
    },
 
    anObjectWithContent = {
 
        exampleContent: 'value'
 
    };
 
</code>
 
==== Incorrect ====
 
 
<code javascript>
 
var anObjectWithContent = {},
 
    anObjectWithContent = {exampleContent: 'value')};
 
</code>
 
 
 
=== Object keys ===
 
 
When working with objects, object property names should not be quoted unless they are a reserved word.
 
 
==== Correct ====
 
 
<code javascript>
 
var anObjectWithContent = {
 
        exampleContent: 'value',
 
        "this": 'that'
 
    };
 
</code>
 
==== Incorrect ====
 
 
<code javascript>
 
var anObjectWithContent = {
 
    "exampleContent": 'value'
 
};
 
 
</code>
 
 
== Functions ==
 
 
Function definitions must meet the following rules:
 
* they must be appropriately named according to the [[#Naming_conventions|naming schemes]];
 
* they must be documented using standard YUIDoc tags;
 
* they must not declare variables which they do not intend to use within the function body;
 
** unless the argument is before an actively used argument in the function declaration;
 
* variables defined in the function declaration must not be re-declared within the function;
 
* they should have an appropriated return value:
 
** where appropriate they should return the current object and declare themselves chainable.
 
 
=== Return values ===
 
 
Where it makes sense, and no return value is already required, functions should return the current object and describe themselves as chainable in the YUIDoc docblock.
 
 
When setting the @chainable tag, it is not necessary to set a @return value too.
 
 
This enables developers to call a number of functions in a sensible, chained fashion. For example, the YUI Node module defines many chainable functions so it is possible to call a number of functions on a single instance:
 
 
<code javascript>
 
Y.Node.create('<div />')
 
    .addClass('someClass')
 
    .setAttribute('foo', 'bar')
 
    .setData('baz, 'qux');
 
</code>
 
 
=== Examples ===
 
==== Correct ====
 
<code javascript>
 
/**
 
* Do something with the two values provided and return something else.
 
*
 
* @method doSomething
 
* @param optionOne {Number} The first parameter
 
* @param unusedMiddleArgument {Number} This parameter is not used in this
 
* function definition, but perhaps it's provided by the event calling it
 
* so we have to have it and cannot remove it.
 
* @param optionTwo {Number} The second parameter
 
* @return Number The result of a really complex mathematical equation
 
* which you couldn't possible do your own
 
*/
 
var doSomething = function(optionOne, unusedMiddleArgument, optionTwo) {
 
    return optionOne + optionTwo;
 
};
 
</code>
 
 
==== Incorrect ====
 
<code javascript>
 
// Undocumented function:
 
var doSomething = function(anArgument, anotherArgument, aThirdArgument) {
 
    // The second argument is re-declared:
 
    var anotherArgument = anArgument + 1;
 
 
    // The third argument is never used:
 
    return anArgument + anotherArgument;
 
};
 
</code>
 
  
 
== Line length ==
 
== Line length ==
Line 330: Line 181:
 
We use the same [https://docs.moodle.org/dev/Coding_style#Maximum_Line_Length | maximum line guidelines as our php standards].
 
We use the same [https://docs.moodle.org/dev/Coding_style#Maximum_Line_Length | maximum line guidelines as our php standards].
 
=== Line wrapping ===
 
=== Line wrapping ===
 
==== Statement wrapping ====
 
 
When wrapping a long line as part of a statement, indent the following line
 
by 8 spaces rather than 4. For example:
 
 
<code javascript>
 
if (someObject.hasClass(CSS.CLASSTOTEST) &&
 
        someOtherObject.hasClass(CSS.CLASSTOTEST)) {
 
    return true;
 
}
 
</code>
 
  
 
==== Method call wrapping ====
 
==== Method call wrapping ====
Line 474: Line 313:
 
== 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.
+
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.
  
 
=== General notes ===
 
=== General notes ===
Line 500: Line 339:
  
 
<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 510: Line 362:
 
  * 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 582: Line 450:
 
  */
 
  */
 
</code>
 
</code>
 
== Syntax ==
 
 
=== Comparison operators ===
 
 
Strict comparisons should always be used.
 
 
==== Correct ====
 
<code javascript>
 
var a = 1,
 
    b = "1";
 
if (a === b) {
 
}
 
 
var c = (a === b);
 
</code>
 
 
==== Incorrect ====
 
 
<code javascript>
 
var a = 1,
 
    b = "1";
 
if (a == b) {
 
}
 
 
var c = (a == b);
 
</code>
 
 
== To be documented ==
 
  
 
== See Also ==
 
== See Also ==

Revision as of 06:02, 4 February 2014

The Moodle JavaScript coding style

Overview

Although most of the Moodle standard Coding style applies 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.

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, we prefer to use camelCase for JavaScript.

The justification for this is that:

  • functions are stored within variables within JavaScript - that is to say that there is essentially no difference between an object storing a function, and an object storing any other value except the type of the contents;
  • it is a commonly accepted practice within the wider JavaScript community;
  • it is easier to read when dealing with variables describing cartesian points which are more common in JavaScript than PHP (e.g. currenty versus currentY); and
  • it is the style used by the upstream YUI library for all variable names, and all CSS settings (keeping consistency within the codebase).

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

In line with common JavaScript practices, names of new classes being defined should be written 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

We make use of a few constants in YUI modules, but all variables intended to be constants should use the same naming style of ALL UPPERCASE.

We make frequent use of two constants in particular and recommend that they be used where appropriate in your code also:

  • CSS: This is an object containing any CSS classes you may wish to use with Nodes; and
  • SELECTORS: This is 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

As usual with JavaScript, 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.

Line length

We use the same | maximum line guidelines as our php standards.

Line wrapping

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

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.

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

See Also