Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Orion/Coding conventions"

(Creating a dictionary)
(Checking if a key is present)
Line 233: Line 233:
 
  }
 
  }
  
While the code may appear correct, in many JS runtimes <code>map[key]</code> will return a reference to the map's prototype object when <code>key</code> equals the nonstandard property name <code>__proto___</code>, which will give false positives. Rather than try to work around the __proto__ special case, it's best to avoid the <code>[]</code> operator entirely and use hasOwnProperty.
+
While the code may appear correct, in many JS runtimes <code>map[key]</code> will return a reference to the map's prototype object when <code>key</code> equals the nonstandard property name <code>__proto__</code>. This will again give false positives. Rather than try to work around the __proto__ special case, it's best to avoid the <code>[]</code> operator entirely and use hasOwnProperty.
  
 
== Writing an event emitter ==  
 
== Writing an event emitter ==  

Revision as of 12:41, 28 January 2013

This is a draft work in progress. If you have suggestions on good naming practices for JavaScript, CSS, and HTML, please raise a discussion on the Orion mailing list.

JavaScript

All member variables starting with an underscore ('_') or having the @private jsdoc tag, are internal and should not be referenced by clients. Such variables are subject to change or removal at any time.

Compatibility

In general, Orion's JavaScript code is targeted toward modern JS runtimes. This means you can (and should) use ECMAScript 5 features like Object.keys, Array.prototype.forEach, and so on. ECMAScript 6 features should not be used, as they are not yet widely supported.

Some of Orion's client bundles may have more strict requirements about what features are allowed. For example, the Orion Editor supports older browsers like Internet Explorer 8, and thus has to avoid most ES5 features (even Array.prototype.indexOf is not allowed in IE8).

References

General Guidelines

We highly recommend writing Orion code using the Orion IDE, as its built-in validator will catch a lot of possible coding errors and anti-patterns (like accidental globals, for example).

When in doubt about coding style, follow these general rules:

  • Favor ES5 features.
    • But don't use strict mode.
  • Favor W3C APIs.
    • If a well-supported spec exists, use it.
    • If a decent-looking spec exists but it isn't well-supported, write a shim for it, then use it.

Modules

  • Your code should be written as an AMD module using define.
  • Use only one module per file.
  • If your code depends on other modules or files, that relationship should be expressed as AMD dependencies:

✔ This module depends on Deferred and xhr:

define(['orion/Deferred', 'orion/xhr'], function(Deferred, xhr) {
    // Use Deferred and xhr here.
});
  • Use the UMD authoring pattern if your module might need to run in non-AMD environments (like CommonJS modules or plain <script> tags).
    • See Deferred.js for an example of a file written using UMD.
    • In the no-module case, your code should place its exports inside the orion property of the root object.

Globals

Your code should not export any* globals. Pay close attention to the problem markers in the Orion editor. Implied globals will be flagged as an error.

✘ Code that creates an implicit global variable. Don't do this.

for (i = 0; i < 10; i++) {       // ERROR: implied global `i`
    console.log("Hello world");
}
  • If your code depends on globals (eg. define, require, jQuery) or host objects from the browser (eg. document, setTimeout), they should be explicitly listed in a /*global */ section near the top of the file. This allows them to be statically checked for misspellings by the validator. At minimum, you should have /*global define*/.

✔ Here we declare the document and define globals:

/*global define document*/
define([], function() {
    // Use document
    document.createElement('div');
});
* Shims are an exception to the "no globals" rule. See URL-shim.js for an example.

Arrays

Testing if a value is an array

✔ Do this:

Array.isArray(value);

Looping through elements

✔ Use Array.prototype.forEach:

array.forEach(function(item) {
    // ...
});

✘ Don't use a loop variable. It is more prone to typing errors, and hinders refactoring by polluting the containing function's scope with i.

for (var i=0; i < array.length; i++) {
    // ...
}

Having said that, a typical for loop will perform faster than the forEach function. Performance-critical code may need to use for, but it's best to use forEach until you've demonstrated that it's too slow for your needs.

✘ Don't treat an array like an object. Never do this:

for (var i in array) {
    // ...
}

Searching for an element with strict-equals

✔ Use Array.prototype.indexOf

var index = array.indexOf(item);
if (index !== -1) {
   console.log("Found it!");
}

✘ Don't do a manual search:

for (var i=0; i < array.length; i++) {
    if (array[i] === item) {
       console.log("Found it!");
       break;
    }
}

Objects

Testing equality

In comparisons, always use the strict equality === and strict inequality !== operators. This makes your code's intent clear to other readers. While the nonstrict equality == may seem like a nice shortcut, it performs type conversions that can be surprising.

✘ For example, a nonstrict comparison against null:

if (value == null) {
    console.log("value is null... or is it?");
}

Since undefined == null, the above code allows value to be undefined, which may cause problems later on.

Testing for objectness

✔ Do this:

if (value !== null && typeof value === "object") {
    // use value as an object
    Object.keys(value);
}

The null check is required, because otherwise null could sneak into the Object.keys call and cause an error. (Recall that typeof null === "object").

Testing for definedness

When writing a function that accepts a variable number of arguments (optional parameters), we sometimes want to check if a parameter is not undefined.

✔ Compare the parameter's type against "undefined":

function variadic(param) {
  if (typeof param === "undefined") {
      console.log("Called with no arguments");
  } else {
      console.log("Called with 1 argument");
  }
}

But why not just compare against undefined (which is not a primitive value, but rather a reference to the global window.undefined property)? While that's usually OK, it does makes your code vulnerable to accidental lexical closures if someone else has foolishly created an in-scope variable named undefined:

✘ A broken comparison against undefined:

function() {
    var undefined = "true";  // Never do this

    function wrong(param) {
        if (param === undefined) {
            console.log("param is undefined... or is it?");
        }
    }
}

Looping through an object's properties

✔ Use Object.keys:

Object.keys(object).forEach(function(property) {
    console.log('property: ' + property);
})

✘ Don't do it this way. While the code is technically correct, Object.keys is clearer and more concise:

for (var property in object) {
    if (Object.prototype.hasOwnProperty.call(object, property)) {
        console.log('property: ' + property);
    }
}

✘ Don't do this. It iterates through all properties in object's prototype chain, which is almost never what you want:

for (var property in object) {
    console.log('property: ' + property);
}

Functions

Attaching a particular this-context to a function

✔ Use Function.prototype.bind:

var context = { myfield: "Hello world" };
var func = function() {
    return this.myfield;
};
var boundFunction = func.bind(context);
boundFunction(); // Hello world

bind is especially useful when dealing with event listeners, which need to be passed around to addEventListener methods but retain their this-context:

function Widget() {
    this.clickCount = 0;
    document.getElementById("myButton").addEventListener("click", this.handleClick.bind(this)); // handleClick always gets the right "this"
}
Widget.protoype.handleClick = function() {
    this.clickCount++;
};

Creating a dictionary

To create a dictionary (also known as a map with string keys), we can use an object.

✔ Do this:

var map = Object.create(null);

✔ Or this:

var map = { };

Checking if a key is present

✔ Use hasOwnProperty to determine if a given key is present in the map:

if (Object.prototype.hasOwnProperty.call(map, key)) {
    console.log("The key " + key + " was found!"
} 

✘ Don't use a truthiness check to determine key presence:

if (map[key]) {
    console.log("The key " + key + " was found!");
}

The truthiness check will give false negatives if the key's value happens to be falsey (like 0 or "").

✘ But even correctly handling falsey values won't save the [] operator. Don't do this:

if (typeof map[key] !== "undefined") {
    console.log("The key " + key + " was found!");
}

While the code may appear correct, in many JS runtimes map[key] will return a reference to the map's prototype object when key equals the nonstandard property name __proto__. This will again give false positives. Rather than try to work around the __proto__ special case, it's best to avoid the [] operator entirely and use hasOwnProperty.

Writing an event emitter

Use the orion/EventTarget mixin to inject event behavior into an object. After calling attach(), your object implements the EventTarget DOM3 interface, and can dispatch events to listeners.

define(["orion/EventTarget"], function(EventTarget) {
    var myobject = {};
    EventTarget.attach(myobject);

    myobject.addEventListener("coolevent", function(event) {
        console.log("Listener received an event");
    });
    myobject.dispatchEvent({ type: "coolevent" }); // every event must have a type
});

Asynchronous operations

Use orion/Deferred to manage asynchronous operations. Its API is documented here.

XMLHttpRequest and Ajax

In most cases it's convenient to use orion/xhr, which wraps the browser's native XMLHttpRequest into a promise. The xhr API is documented here.

xhr("GET", "/index.html").then(
    function(xhrResult) {
        console.log("Got response: " + xhrResult.response);
    },
    function(xhrResult) {
        console.log("An error occurred: " + xhrResult.error);
    });

URLs

Orion provides a shim for the W3C URL API. Load the shim in your module, and then call the window.URL constructor. You should always use the URL methods, rather than concatenate and encode strings by yourself.

✔ Do this:

define(["orion/URL-shim"], function() {
    var url = new URL("http://foo.com");
    url.query.set("param1", "first value");
    url.query.set("param2", "second value");
    var myurl = url.href; // http://foo.com/?param1=first%20value&param2=second%20value
});

✘ Don't do this:

 var myurl = "http://foo.com/?param1=" + encodeURIComponent("first value") + "&" + encodeURIComponent("second value");

Consider also using the orion/URITemplate module, which implements the URI Template spec.

JS Documentation

All JavaScript API use jsdoc syntax. The principle doc tags used are:

@param
Documentation of method parameters. See TagParam in the jsdoc wiki for further details.
@name
For specifying class names. The class name should match the RequireJS module name.
@lends
For specifying what class a prototype contributes to

References:

Library Objects

API library objects all fall under the 'orion' namespace. For example a library object for managing bookmarks might be called orion.Bookmarks.

Services

The DOM location of service objects is not defined. All services must be accessed via the service registry using the service's symbolic id. While concrete service implementations may be found in the DOM, such object names are not API and are subject to change at any time. Service symbolic ids follow the Reverse DNS naming convention (like Java packages).

JSON

Server requests and responses are represented by default as JSON objects. Object member names use title case ("Name", "ChildrenLocation", etc).

HTML

None of the HTML in Orion is API. The existence and contents of any HTML file is subject to change at any time. If you see something in Orion's HTML files that you need in your application, make your own copy.

CSS

Java

Java code on the server follows the standard Eclipse Naming Conventions. The project short name is 'orion', so all server bundles and packages start with org.eclipse.orion.

Provisional API packages use the x-internal or x-friends manifest attribute, and do not have internal as a segment in the package name. Use of PDE API tools is highly recommended to ensure you are using supported API.

Copyrights

The Orion server source code is licensed under the Eclipse Public License. The standard Eclipse copyright header should be used for these source files.

The Orion client code (HTML, CSS, JavaScript), is licensed under both the Eclipse Public License and the Eclipse Distribution License. Here is a sample copyright header to use in JavaScript source files:

/******************************************************************************* 
 * Copyright (c) <date> <contributor name> and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: <contributor name> - initial API and implementation 
 ******************************************************************************/

Back to the top