Manual:Coding conventions/JavaScript
This page describes the coding conventions used within files of the MediaWiki codebase written in JavaScript. See also the general conventions that apply to all program languages, including JavaScript.
Contents |
[edit] Code structure
[edit] Closures
Always wrap your code in a closure. This avoids leakage of variables from or to another modules. Use the following closure ("Immediately-Invoked Function Expression"[1]):
( function ( mw, $ ) { // Code here will be invoked immediately }( mediaWiki, jQuery ) );
When writing a jQuery plugin that doesn't use any MediaWiki variables and want to execute it at the document ready event, you may also use the following format, which removes the need for a separate closure as well as aliasing jQuery to $
:
jQuery( document ).ready( function ( $ ) { // This function is called by jQuery internally as fn( jQuery ) } );
[edit] Whitespace
- See also the general conventions.
For optimum readability, put one space on both sides of operators, for example.
// No: a.foo=bar+baz; // Yes: a.foo = bar + baz;
Feel free to use blank lines to separate logically related blocks from each other.
Keywords that are followed by (
should be separated by one space. This helps distinguish between keywords and function invocations (if
, else
, for
, function
, etc.).
// No: if( foo ) { foo.bar = doBar(); } // No: if ( foo ) { foo.bar = doBar (); } // Yes: if ( foo ) { foo.bar = doBar(); }
Don't use operators as if they are functions (such as typeof
, new
, delete
, ..). Best practices for readability still apply and if necessary using parentheses is fine, but don't forget to apply the above to avoid confusion.
// No: a.foo = typeof( bar ); // Yes: a.foo = typeof bar; delete bar.baz; a.foo = new bar.baz.Quux(); a.foo = typeof ( bar.baz ? bar.baz.getQuux() : bar.baz.getQuux() );
[edit] Globals
The only globals should be mediaWiki
and jQuery
(and perhaps document
or location
if needed). Other globals should never be referenced directly.
Any and all code should be written as an extension to either of these. General purpose (not MediaWiki-specific) modules that manipulate DOM elements should be written as a jQuery plugin. And MediaWiki core or extension modules should extend the global mediaWiki
object.
If code is made available standalone and/or provided by a third party that exposes itself as a global, then (for use in MediaWiki context) alias or instantiate it into a property of mw.libs
and use it from there.
Aside from references to globals, usage of the jQuery
and mediaWiki
globals should be avoided. Instead alias them by using the function argument list of your file's closure. That way they are safe from bad code that re-defines globals (e.g. overwriting jQuery with an old version, that unregisters plugins and lacks new features not in the old version). This is also faster for performance as it safes property and scope chain lookups.
Note that for backward compatibility, MediaWiki exposes lots of wgXxx configuration variables in global scope if $wgLegacyJavaScriptGlobals on the server is set to true; some day this will be turned off (bug 33837), so don't rely on this.
The scoping behavior of the ResourceLoader changes when you toggle debug mode so that it loads .js files directly; don't rely on this and test in both modes.
[edit] Comments
Make sure to use the JavaScript terminology (such as Number
instead of integer
).
In older versions of MediaWiki, JavaScript code was often very poorly commented to keep it small. Since MediaWiki 1.17 and higher code is automatically optimized by ResourceLoader's minifiers and the like. Please be generous with comments in your code, as they help future readers of the code (possibly yourself).
Generally use (single or multi-line) // line comments. Save /* block comments */ for documentation blocks and for commenting out code.
[edit] Example code
Instead of endlessly describing all conventions, below are a few sample codes that demonstrate the following:
- the order in which code should generally be structured
- the use and placement of
function
andvar
statements (all var statements at the top of the function scope) - Use literal arrays and objects
- Use strict comparison to undefined instead of executing a typeof operation and comparing a string to "undefined".
- No Yoda conditional[2]
/** * Example module for MediaWiki */ ( function ( mw, $ ) { var foo, bar, cache, ids, myLib; function convertFooToQuux( foo, quux ) { /* ... */ return [foo, quux]; } foo = mw.util.wikiScript( 'api' ); bar = mw.config.get( 'quux' ); cache = {}; ids = []; myLib = { ids: ids, addIds: function ( add ) { return $.isArray( add ) ? ids.push.apply( ids, add ) : ids.push( add ); }, doStuff: function () { /* .. */ }, bar: function ( x ) { if ( x === undefined ) { x = myLib.doStuff(); } /* .. */ }, newQuux: function ( quux ) { if ( arguments.length === 0 ) { return quux; } /* .. */ return convertFooToQuux( foo, quux ); } }; // Expose mw.myLibrary = myLib; }( mediaWiki, jQuery ) );
[edit] Naming
All variables and functions must use lowerCamelCase for their naming. For functions, verb phrases are preferred (so getFoo() instead of foo()).
The only exception to this are object constructors. Due to the nature of JavaScript a function is not declared as an object constructor, instead it is declared like any other and used as an object constructor with the new
operator. Since no error checking exists, always use UpperCamelCase for naming constructors so that the required new
operator is easily recognized by the reader.
Names with acronyms in them should treat the acronym as a normal word and only uppercase the first letter. For example, getHTMLSource
is wrong and getHtmlSource
is correct.
[edit] Creating elements
To create a new element, use the simple <tag>
syntax in the jQuery constructor:
var $hello = $( '<div>' ).text( 'Hello' );
If you're creating elements based on a variable, you can do it either of these ways:
var tag, $who, who; tag = who.useStrong ? 'strong' : 'span'; $who = $( '<' + tag + '>' ); tag = who.getTagName(); who = document.createElement( tag );
Always use the latter syntax if the input is not in direct control because putting it inside < and > can do a lot of unexpected things.
Only use <valid html="syntax" like>this</valid>
when you need to parse HTML (as opposed to creating a plain element).
For example:
var $form, $hello, $userMagic; $hello = $( '<input class="mw-hello-search" type="search" name="query"/>' ); $userMagic = $( '<a>') .text( mw.msg( 'hello-usermagic-link' ) ) .href( mw.hello.getSignupUrl() ); $form = $( '<form class="mw-hello-form"></form>' ) .append( $hello, $userMagic );
$('<a/>') and $('<a></a>') will also work. Though we recommend not using $('<a/>') because it looks like invalid HTML.
[edit] Pitfalls
- Declare your variables at the top of every function. JavaScript does not have block scope.
- Bracket notation on a string (
'foobar'[2]
) doesn't work in older versions of IE such as IE6. Use the String prototypecharAt
instead ('foobar'.charAt(2)
). - Be sure to check the MDN Docs before using a prototype. For example, the filter Array prototype isn't supported in any version of Internet Explorer before 9. See also Compatibility#Browser.
- Never make an assumption or imply its scope. A variable should be created as either
var foo
orwindow.doo
. Undeclared variables are assumed to be global - the opposite of PHP. - When throwing exceptions, use
throw new Error('message')
rather thanthrow 'message'
. You can throw a string directly in JavaScript, but some debuggers won't pick up a stack trace or the location of the error without using theError
constructor. - Use
$.isArray()
to cover for edge cases and cross-browser differences (instead oftypeof
,instanceof
orArray.isArray
) - Be careful to preserve compatibility with left-to-right and right-to-left languages (ie.
float: right
ortext-align: left
), especially when styling text containers. This is another reason why such declarations should be in CSS files, so that they are automagically flipped by CSSJanus in ResourceLoader for RTL-languages. - Make sure you're using
attr()
orprop()
appropriately. Read more at http://javascript.info/tutorial/attributes-and-custom-properties - Always quote attribute selector values:
[foo="bar"]
instead of[foo=bar]
. Avoid bugs like jqbug 8229. - Don't use
.size()
. It's the same number of characters as.length
and the property lookup is faster than a function call plus the property lookup (source of .size()). - As of jQuery 1.4 the jQuery constructor has a new feature that allows passing an object as second argument, like
jQuery( '<div>', { foo: 'bar', click: function () {}, css: { .. } } );
. Don't use this because it makes code harder to follow and it is unstable due to mixing of jQuery methods with element attributes (e.g. a plugin called "title" might convert an element into a heading, which means the title attribute can no longer be set through this method). Be explicit and call.attr()
,.prop()
,.on()
etc. directly.
[edit] Environment
There is a few things that MediaWiki specifically (or inherently due to use of jQuery), does not support:
- jQuery doesn't support environments that have manipulated the
Object.prototype
as it's considered harmful. - Both MediaWiki and jQuery do not support environments that have manipulated the global
undefined
variable as it's considered harmful. As of ECMAScript5 this is no longer an issue since it is made read-only (as it should be), but in older browsers this can cause issues.
[edit] Documentation comments
We don't automatically build HTML documentation from JavaScript comments, e.g. with Doxygen or JDDoc (bug 40143). Some advanced editors like NetBeans can make use of JSDoc-style annotations to aid in autocomplete and code navigation.
Whatever the case, here's the format we are using now:
- Use
/**
for the opening block, not/*****...
,/*!
or some other variation. - Start with a short description about the object (object, array, function, regex, date, jQuery, mw.Title, whatever it may be).
- Use any of these annotations as appropriate:
@constructor
@example
(indented example on the next line(s))@since [MWVersion]
@context {Type}
(if appropriate and/or overridden, documents thethis
context)@param {Type} varName Description.
@return type, primitive and/or description.
- For type, use a lowercase type of primitive value or the name of the constructor if an object is expected. JavaScript implements the below primitive values. Anything else is an object and should be described by its constructor (e.g.
{Function}
,{Object}
,{HTMLElement}
,{Array}
,{jQuery}
,{jQuery.Deferred}
,{mw.Title}
, ..):{undefined}
{null}
{bolean}
{string}
{number}
/** * Create an outsources Cool object. * @constructor * * @example * new Cool( 5 ); * * @param {number} foo Total sources. * @param {string} bar Message to be displayed. */ function Cool( foo, bar ) { // ... } /** * Some short description of this function. * * @example * $( '.foo' ).foobar(); * * @context {jQuery} * @return {string} HTML-fragment */ $.fn.foobar = function () { if ( this.val() === 'baz' ) { return this.html(); } return this.html( '<baz>Foo foo foo</baz>' ).html(); }
[edit] jQuery
- See also jQuery
As of MediaWiki 1.17 jQuery is loaded by default and available everywhere. familiarize yourself with its API and don't be afraid to use jQuery for DOM manipulation, Event binding, AJAX requests and Animations. These are usually much easier to work with than classic low-level DOM and DHTML interfaces, and much more consistent across browsers.
To avoid confusion with raw elements or other variables, we prefix variables storing an instance of jQuery with a dollar symbol. This makes it easy to recognize and manipulate them even with great distance between point X and the point of definition. A common problem is when referring to them in a conditional statement. document.getElementById
returns null if no element matched the ID, therefore (since null casts to boolean false) it can be used as-is in an if statement. jQuery objects on the other hand (as any object in JavaScript) cast to boolean true no matter what (in a similar scenario one would use if ( $foo.length ) {
instead).
[edit] JSHint
We use JSHint for linting of javascript code. The settings for MediaWiki core can be found in the .jshintrc. Individual settings may use different settings for their project, but it has to pass under some configuration (whatever that is).
You should have nodejs installed (with npm), then install node-jshint from the command line for automatic linting. node-jshint automatically discovers the closest .jshintrc file and uses it.
You can also use online tools, these may or may not produce the same results, use at your own risk
You are also encouraged to use a code editor that supports built-in linting, the following are known to support JSHint (either built-in, through plugins or by shelling out to node-jshint):
- Sublime Text 2 (Mac, Windows, Linux)
See also the Text Editors and IDEs section jshint's website.
[edit] Performance and best practices
JSHint
We use JSHint to catch various bad coding habits. Read more in the #JSHint section.
Use CSS for styling many elements
Don't apply styling to lots of elements at once; this has poor performance. Instead use a common parent's class (or add one) and apply CSS in a .css file. Thanks to ResourceLoader, this will all be loaded in the same HTTP request, so there's no performance penalty for having a separate CSS file. Do not set CSS into inline "style" attributes, don't insert "style" elements from JS either.
Dot notation
Use dot-notation whenever possible to access object members.
Literals and native constructors
Use array and object literal notation (var foo = []; var bar = {};
), do not use new Object()
or new Array()
. Do not use new String()
, new Number()
or new Boolean()
. (Usage of String()
and Number
as conversion functions is encouraged but don't use them as constructors due to equality problems).
[edit] References
- ↑ http://benalman.com/news/2010/11/immediately-invoked-function-expression/
- ↑ http://www.globalnerdy.com/wordpress/wp-content/uploads/2010/05/yodaconditional.jpg
Conventions | |
---|---|
General | All languages · Security for developers · Pre-commit checklist · Style guide (draft) |
PHP | Code conventions · PHPUnit test conventions · Security checklist for developers |
JavaScript | Code conventions · Learning JavaScript |
CSS | Code conventions |
Database | Code conventions |