Javascript

Style guidelines for writing Javascript.

See also

Writing a JS module for the OSF? See the Javascript Modules page in the OSF section.

Style

Follow Felix’s Node Style and airbnb’s Style Guide with a few exceptions:

  • Use 4 spaces for indentation.
  • Use self to save a reference to this.

Errors

  • Always throw Error instances, not strings.
// Yes
throw new Error('Something went wrong');

// No
throw 'Something went wrong';

// No
throw Error('Something went wrong');

CommonJS Modules

  • Group imports in the following order, separated by a blank line:
  1. Third party libraries
  2. Local application/library-specific imports
  • module.exports are always grouped at the end of a file. Do not use export throughout the file.
  • Keep testability in mind in deciding what to export.
// Yes
module.exports = {
    SomeClass: SomeClass,
    _privateFunction: privateFunction
}

// Yes
function SomeClass() { ... }
SomeClass._privateFunction = function() {...}

module.exports = SomeClass;

// No
var SomeClass = exports.SomeClass = function() { ... };
var privateFunction = exports._privateFunction = function() { ... };

Documentation

Use the YUIDoc standard for writing JS comment blocks.

Example:

/**
* A wrapper around the ACE editor that asynchronously loads
* and publishes its content.
*
* @param {String} selector Selector for the editor element
* @param {String} url URL for retrieving and posting content.
*/

For simple functions and methods, a single-line docstring will suffice.

/** Update the viewModel with data fetched from a server. */

jQuery

Follow Abhinay Rathore’s jQuery Coding Standards Guide.

AJAX

For PUTting and POSTing to JSON endpoints in the OSF, use the $osf.postJSON and $osf.putJSON functions (located in osfHelpers.js). This will handle JSON stringification as well as set the correct dataType and contentType.

When using $osf.postJSON, $osf.putJSON, or jQuery.ajax, use the Promises interface.

function successHandler(response) { ... }
function failureHandler(jqXHR, status, error) {...}


var request = $.ajax({ ... });
request.done(successHandler);
request.fail(failureHandler);

// OR
$.ajax({ ... }).then(successHandler, failureHandler);

Promises

  • Prefer promises to callbacks.
// Yes
function makeRequest() {
    var request = $.getJSON('/api/projects/');
    return request;
}
var request = makeRequest();
request.done(function(response) { console.log(response); })

// No
function makeRequest(callback){
    $.getJSON('/api/projects/', function(response) {
        callback && callback(response);
    }) ;
}
makeRequest(function(response) {console.log(response)});
  • When doing AJAX requests or other async work, it’s often useful to return a promise that resolves to a useful value (e.g. model objects or “unwrapped” responses).
function User(data) {
    this._id = data._id;
    this.username = data.username;
}

/** Return a promise that resolves to a list of Users */
var getUsers = function() {
    var ret = $.Deferred();

    var request = $.getJSON('/users/');
    request.done(function(response) {
        var users = $.map(response.users, function(data){
            return User(data);
        });
        ret.resolve(users);
    });
    request.fail(function(xhr, status, error) {
        Raven.captureMessage(...);
        ret.reject(xhr, status, error);
    });
    return ret.promise();
};

getUsers().done(function(users){
    users.forEach(function(user)){
        console.log(user._id);
        console.log(user.username);
    };
})

Encapsulation

Use the Combination Constructor/Prototype pattern for encapsulation. You can use the following functions to provide syntactic sugar for creating “classes”:

function noop() {}

function defclass(prototype) {
    var constructor = prototype.hasOwnProperty('constructor') ? prototype.constructor : noop;
    constructor.prototype = prototype;
    return constructor;
}

function extend(cls, sub) {
    var prototype = Object.create(cls.prototype);
    for (var key in sub) { prototype[key] = sub[key]; }
    prototype.super = cls.prototype;
    return defclass(prototype);
}

// Example usage:
var Animal = defclass({
    constructor: function(name) {
        this.name = name || 'unnamed';
        this.sleeping = false;
    },
    sayHi: function() {
        console.log('Hi, my name is ' + this.name);
    }
});

var Person = extend(Animal, {
    constructor: function(name) {
        this.super.constructor.call(name);
        this.name = name || 'Steve';
    }
});

Note

In the OSF, the defclass and extend functions are available in the oop.js module.