JavaScript

General considerations

  • this document is split in multiple sections
    • general JavaScript
    • TypeScript
    • Angular
      • Angular templates
    • Adhocracy 3
    • Tests
  • We prefer conventions set by 3rd party tools (e.g. tslint) over our own preferences.
  • We try to be consistent with other guidelines from the adhocracy3 project

General JavaScript

We follow most rules proposed by tslint (see tslint config for details). However, there are some rules we want to adhere to that can not (yet) be checked with tslint.

  • Use strict mode everywhere

  • No implicit boolean conversions: if (typeof x === "undefined") instead of if (!x)

  • Chaining is to be preferred.

    • If chain elements are many lines long, it is ok to avoid chaining. In this case, if chaining is used anyway, newlines and comments between chain elements are encouraged.
    • Layout: Each function (also the first one) starts a new line. The first line (without a .) is indented at n+0, all functions at n+1 (4 spaces deeper).

    Example:

    adhHttp.get(url)
        .then(update)
        .then(exit);
    
  • Each new identifier has its own var. (rationale: git diff / conflicts)

    Example:

    // bad
    var someVariable
        someOtherVariable;
    
    // good
    var someVariable;
    var someOtherVariable;
    
  • No whitespace immediately inside parentheses, brackets or braces (this includes empty blocks):

    Yes: spam(ham[1], {eggs: 2})
    No:  spam( ham[ 1 ], { eggs: 2 } )
    
  • Do not align your code. Use the following indentation rules instead (single-line option is always allowed if reasonably short):

    • objects:

      foo = {
          a: 1,
          boeifj: 2,
          cfhe: 3
      }
      
    • lists:

      foo = [
          138,
          281128
      ]
      
    • function definitions:

      var foo(a : number) : number => a + 1;
      
      var foo = (arg : number) : void => {
          return;
      };
      
      var foo = (
          arg : number,
          otherarg : Class
      ) : void => {
          return;
      };
      
  • The last item in a list or in function parameters may be split across multiple lines:

    app.directive('myDirective', ["$q", "$http", ($q, $http) => {
        ...
    }]);
    
  • Do not use named functions. Assign anonymous functions to variables instead. This is less confusing. Further reading

  • If you need an alias for this, always use self (as in knockout) or _self (in TypeScript classes). (_this is used by TypeScript in compiled code and is disallowed in typescript source in e.g. class instance methods.)

    If more than one nested self is needed, re-assign outer selfs locally.

TypeScript

  • imports at top

    • standard libs first (if such a thing ever exists), then external modules, then a3-internal modules.
    • only import from lower level. (FIXME: “lower level” does not mean file directory hierarchy, but something to be clarified. This rule is to be re-evaluated at some point.)
  • imported adhocracy modules must be prefixed with “Adh”.

  • nested generic types are allowed up to 2 levels (Foo<Bar<Baz>>). Fewer is to be preferred where possible.

  • Type functions, not the variables they are assigned to.

  • Use type[] rather than Array<type>.

  • A colon used for types must always be surrounded by single spaces:

    // bad
    var x: number;
    var y:number;
    
    // good
    var x : number;
    

Lambdas

TypeScript has its own lambda syntax. It has two differences from JavaScript’s functions:

  • The result of the final statement is returned automatically.
  • this is the this from the enclosing scope.

Example:

var lambda = () => {
    var nested_fn = function() {
         return this;
    };
    var nested_lambda = () => this;
}

var fn = function() {
    var nested_fn = function() {
         return this;
    };
    var nested_lambda = () => this;
}

is compiled to:

var _this = this;
var lambda = function () {
    var nested_fn = function () {
        return this;
    };
    var nested_lambda = function () {
        return _this;
    };
};

var fn = function () {
    var _this = this;
    var nested_fn = function () {
        return this;
    };
    var nested_lambda = function () {
        return _this;
    };
};

These lambdas should always be preferred over functions because they avoid common mistakes like this:

class Greeter {
    greeting = "Hello";

    greet = function() {
        alert(this.greeting);
    };
}

var greeter = new Greeter();
setTimeout(greeter.greet, 1000);  // will alert 'undefined'

Still you should not use this behaviour extensively. Prefer to use the explicit aliases _self and _class in class methods:

class Greeter {
    public static greeting = "Hello";

    constructor(public name) {}

    greet = function() {
        var _self = this;
        var _class = (<any>_self).constructor;

        setTimeout(() => {
            console.log(_class.greeting + " " + _self.name + "!");
        }, 1000);
    }
}

Angular

  • prefer isolated scope in directives and pass in variables explicitly.
  • direct DOM manipulation/jQuery is only allowed inside directives.
  • dependency injection
    • always use ["$q", function($q) {…}] style
  • do not use $ in your variable names (leave it to angular).
  • all directives, filters and services are prefixed with “adh”.

Note

In the future, this prefix may be split up in several ones, making refactoring necessary. Client-specific prefixes may be added without the need for refactoring.

  • angular scopes should be typed with interfaces.

Template

  • write polyglot HTML5.

    • prefix any angular-specific attributes with data-:

      <span data-ng-bind="foo"></span>
      
    • Exception: The preferred way to use angular directives is the element syntax:

      <adh-proposal data-path="/adhocracy/proposal/1"></adh-proposal>
      
  • prefer {{…}} over ngBind (except for root template).

FIXME: when to apply which classes (should be in balance with CSS)

  • apply classes w/o a specific need/by default?
  • CSS and JavaScript are not allowed in templates. This includes ngStyle.
  • Since templates (1) ideally are to be maintained by designers rather than software developers, and (2) are not type-checked by typescript, they must contain as little code as possible.

Documentation