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"

(Subclassing)
(Subclassing)
(38 intermediate revisions by 2 users not shown)
Line 11: Line 11:
 
* [http://kangax.github.com/es5-compat-table/ ES5 compatibility table]
 
* [http://kangax.github.com/es5-compat-table/ ES5 compatibility table]
 
* [http://kangax.github.com/es5-compat-table/es6/ ES6 compatibility table]
 
* [http://kangax.github.com/es5-compat-table/es6/ ES6 compatibility table]
 +
* [http://www.chromestatus.com/features Chromium dashboard] (upcoming features in Chrome)
 +
* [http://caniuse.com/ Can I use...?]
  
 
== General Guidelines ==
 
== General Guidelines ==
Line 23: Line 25:
  
 
== Modules ==
 
== Modules ==
* Your code should be written as an AMD module using [http://requirejs.org/docs/api.html#define <code>define</code>].
+
* Your code should be written as an [http://requirejs.org/docs/whyamd.html#amd AMD module] using [http://requirejs.org/docs/api.html#define <code>define</code>].
 
+
* Each <code>.js</code> file should <code>define</code> only a single module.  
* Use only one module per file.
+
* If your code depends on other modules or files, that relationship should be expressed as AMD dependencies (see example below).
 
+
* If your code depends on other modules or files, that relationship should be expressed as AMD dependencies (see below).
+
&#x2714; This module depends on <code>Deferred</code> and <code>xhr</code>:
+
define(['orion/Deferred', 'orion/xhr'], function(Deferred, xhr) {
+
    // Use Deferred and xhr here.
+
});
+
 
+
 
* Use the [https://github.com/umdjs/umd/blob/master/amdWeb.js UMD authoring pattern] if your module might need to run in non-AMD environments (like [http://wiki.commonjs.org/wiki/Modules/1.1 CommonJS modules] or plain <script> tags).
 
* Use the [https://github.com/umdjs/umd/blob/master/amdWeb.js UMD authoring pattern] if your module might need to run in non-AMD environments (like [http://wiki.commonjs.org/wiki/Modules/1.1 CommonJS modules] or plain <script> tags).
 
** See <code>Deferred.js</code> for an example of a file written using UMD.
 
** See <code>Deferred.js</code> for an example of a file written using UMD.
** In the no-module case, your code should place its exports inside the <code>orion</code> property of the root or global object.
+
** When authoring a UMD module, in the "no module-loader" case, your code should place its exports inside the <code>orion</code> property of the global object.
 +
&#x2714; Here's how to write a module that depends on <code>orion/Deferred</code> and <code>orion/xhr</code>:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
define(["orion/Deferred", "orion/xhr"], function(Deferred, xhr) {
 +
    // Use Deferred and xhr here.
 +
});
 +
</source></blockquote>
  
 
== Globals ==
 
== Globals ==
Your code should not export any[[#shims|*]] globals. Pay close attention to the problem markers in the Orion editor. Implied globals will be flagged as an error.
+
Your code should not export any[[#shims|*]] globals (with the exception of shims -- see <code>URL-shim.js</code> for an example). Pay close attention to the problem markers in the Orion editor. Implied globals will be flagged as an error.
  
 
&#x2718; Code that creates an implicit global variable. Don't do this.
 
&#x2718; Code that creates an implicit global variable. Don't do this.
for (i = 0; i < 10; i++) {      // ERROR: implied global `i`
+
<blockquote><source lang="javascript" enclose="div" line highlight="1">
    console.log("Hello world");
+
for (i = 0; i < 10; i++) {      // ERROR: implied global `i`
}
+
    console.log("Hello world");
 +
}
 +
</source></blockquote>
  
* If your code depends on globals (eg. <code>define</code>, <code>require</code>, <code>jQuery</code>) or browser objects (eg. <code>document</code>, <code>setTimeout</code>), they should be explicitly listed in a <code>/*global */</code> section near the top of the file. This allows them to be statically checked for misspellings by the validator. At minimum, you should have <code>/*global define*/</code>.
+
If your code depends on globals (eg. <code>define</code>, <code>require</code>, <code>jQuery</code>) or browser objects (eg. <code>document</code>, <code>setTimeout</code>), they should be explicitly listed in a <code>/*global */</code> section near the top of the file. This allows them to be statically checked for misspellings by the validator. At minimum, you should have <code>/*global define*/</code> or <code>/*jslint amd:true*/</code>.
  
 
&#x2714; Here we declare the <code>document</code> and <code>define</code> globals:
 
&#x2714; Here we declare the <code>document</code> and <code>define</code> globals:
/*global define document*/
+
<blockquote><source lang="javascript" enclose="div" line>
define([], function() {
+
/*global define document*/
    // Use document
+
define([], function() {
    document.createElement('div');
+
    // Use document
});
+
    document.createElement('div');
 +
});
 +
</source></blockquote>
  
: <div id="shims"><nowiki>*</nowiki> Shims are an exception to the "no globals" rule. See <code>URL-shim.js</code> for an example.</div>
+
=== The 'browser:true' option ===
 +
&#x2714; Alternatively, you can use JSLint's <code>browser:true</code> option, which predefines many globals for you (including <code>document</code>, <code>window</code>, <code>setTimeout</code>, etc).
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
/*global define*/
 +
/*jslint browser:true*/
 +
define([], function() {
 +
    // Use document
 +
    document.createElement('div');
 +
});
 +
</source></blockquote>
 +
 
 +
<b>However</b>, <code>browser:true</code> should be used with caution. Here is the full list of globals that it defines:
 +
<blockquote><source lang="javascript" enclose="div">
 +
addEventListener, blur, clearInterval, clearTimeout, close, closed, defaultStatus, document, event, focus,
 +
frames, getComputedStyle, history, Image, length, location, moveBy, moveTo, name, navigator, onbeforeunload,
 +
onblur, onerror, onfocus, onload, onresize, onunload, open, opener, Option, parent, print, removeEventListener,
 +
resizeBy, resizeTo, screen, scroll, scrollBy, scrollTo, setInterval, setTimeout, status, top, window, XMLHttpRequest
 +
</source></blockquote>
 +
Among these are several words that commonly serve as variable and parameter names in JavaScript code: parent, open, closed, event, name, location, etc. If you accidentally reference one of these globals, it can allow a real coding problem to slip past JSLint undetected.
 +
 
 +
&#x2718; For example, here is a coding error hidden by <code>browser:true</code>.
 +
<blockquote><source lang="javascript" enclose="div" line highlight="3">
 +
/*jslint browser:true*/
 +
function handleEvent(e) {
 +
    console.log("Got an event: " + event); // Wrong! We intended to reference 'e' here, but the validator will not warn us.
 +
}
 +
</source></blockquote>
 +
 
 +
== The 'amd:true' option ==
 +
Instead of writing out <code>/*global define require*/</code> you can use the shortcut option <code>/*jslint amd:true*/</code>. This is a good idea, please use it when writing new code.
 +
 
 +
== The 'node:true' option ==
 +
When writing a Node.js module (for example, server-side code for the Orion Node server), use the <code>/*jslint node:true*/</code> option. It will predefine globals like <code>process</code>, <code>__dirname</code>, etc.
  
 
== Internal data ==
 
== Internal data ==
Line 62: Line 99:
 
=== Testing if a value is an array ===
 
=== Testing if a value is an array ===
 
&#x2714; Do this:
 
&#x2714; Do this:
Array.isArray(value);
+
<blockquote><source lang="javascript" enclose="div" line>
 +
Array.isArray(value);
 +
</source></blockquote>
  
 
=== Looping through elements ===
 
=== Looping through elements ===
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach Array.prototype.forEach]:
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach Array.prototype.forEach]:
array.forEach(function(item) {
+
<blockquote><source lang="javascript" enclose="div" line>
    // ...
+
array.forEach(function(item) {
});
+
    // ...
 +
});
 +
</source></blockquote>
  
 
&#x2718; Don't use a loop variable. It is more prone to typing errors, and hinders refactoring by polluting the containing function's scope with <code>i</code>.
 
&#x2718; Don't use a loop variable. It is more prone to typing errors, and hinders refactoring by polluting the containing function's scope with <code>i</code>.
for (var i=0; i < array.length; i++) {
+
<blockquote><source lang="javascript" enclose="div" line>
    // ...
+
for (var i=0; i < array.length; i++) {
}
+
    // ...
 +
}
 +
</source></blockquote>
  
 
Having said that, a typical <code>for</code> loop will perform faster than the <code>forEach</code> function. Performance-critical code may need to use <code>for</code>, but it's best to use <code>forEach</code> until you've demonstrated that it's too slow for your needs.
 
Having said that, a typical <code>for</code> loop will perform faster than the <code>forEach</code> function. Performance-critical code may need to use <code>for</code>, but it's best to use <code>forEach</code> until you've demonstrated that it's too slow for your needs.
  
 
&#x2718; Don't treat an array like an object. Never do this:
 
&#x2718; Don't treat an array like an object. Never do this:
for (var i in array) {
+
<blockquote><source lang="javascript" enclose="div" line>
    // ...
+
for (var i in array) {
}
+
    // ...
 +
}
 +
</source></blockquote>
  
 
=== Searching for an element with strict-equals  ===
 
=== Searching for an element with strict-equals  ===
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf Array.prototype.indexOf]
+
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf Array.prototype.indexOf]:
var index = array.indexOf(item);
+
 
if (index !== -1) {
+
<blockquote><source lang="javascript" enclose="div" line>
 +
var index = array.indexOf(item);
 +
if (index !== -1) {
 
     console.log("Found it!");
 
     console.log("Found it!");
}
+
}</source></blockquote>
  
 
&#x2718; Don't do a manual search:
 
&#x2718; Don't do a manual search:
for (var i=0; i < array.length; i++) {
+
<blockquote><source lang="javascript" enclose="div" line>
    if (array[i] === item) {
+
for (var i=0; i < array.length; i++) {
 +
    if (array[i] === item) {
 
         console.log("Found it!");
 
         console.log("Found it!");
 
         break;
 
         break;
    }
+
    }
}
+
}
 +
</source></blockquote>
  
 
=== Applying a transformation to an array ===
 
=== Applying a transformation to an array ===
Line 103: Line 152:
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/some Array.prototype.some].
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/some Array.prototype.some].
  
Also note that <code>some</code>'s early-exit behavior (when the callback returns <code>true</code>) can be used to replace a manual <code>for .. break</code> search for an element.
+
Also note that <code>some</code>'s early-exit behavior (when the callback returns <code>true</code>) can be used to replace a manual <code>for .. break</code> search for an element. For example:
 +
 
 +
&#x2718; Rather than write this search manually:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
var array = [1, 3, 5, 8, 11];
 +
var found;
 +
for (var i=0; i < array.length; i++) {
 +
    var element = array[i];
 +
    if (element % 2 === 0) {
 +
        found = element;
 +
        break;
 +
    }
 +
}
 +
console.log("Found an even number: " + found);
 +
</source></blockquote>
 +
 
 +
&#x2714; &#x2026; We can encapsulate it into a <code>some</code>:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
var array = [1, 3, 5, 8, 11];
 +
var found;
 +
array.some(function(element) {
 +
    if (element % 2 === 0) {
 +
        found = element;
 +
        return true; // stops the search
 +
    }
 +
    return false;
 +
});
 +
console.log("Found an even number: " + found);
 +
</source></blockquote>
  
 
=== Determining if every element satisfies a condition ===
 
=== Determining if every element satisfies a condition ===
Line 116: Line 193:
  
 
&#x2718; For example, a nonstrict comparison against <code>null</code> doesn't behave like you might expect:
 
&#x2718; For example, a nonstrict comparison against <code>null</code> doesn't behave like you might expect:
if (value == null) {
+
<blockquote><source lang="javascript" enclose="div" line>
    console.log("value is null... or is it?");
+
if (value == null) {
    // do stuff with value
+
    console.log("value is null... or is it?");
}
+
    // do stuff with value
 
+
}
 +
</source></blockquote>
 
Since <code>undefined == null</code>, the above code allows <code>value</code> to be <code>undefined</code> and pass the <code>if</code>-test, which may cause problems later on.
 
Since <code>undefined == null</code>, the above code allows <code>value</code> to be <code>undefined</code> and pass the <code>if</code>-test, which may cause problems later on.
  
 
=== Testing for objectness ===
 
=== Testing for objectness ===
 
&#x2714; Do this:
 
&#x2714; Do this:
if (value !== null && typeof value === "object") {
+
<blockquote><source lang="javascript" enclose="div" line>
    // use value as an object
+
if (value !== null && typeof value === "object") {
    Object.keys(value);
+
    // use value as an object
}
+
    Object.keys(value);
 +
}
 +
</source></blockquote>
  
The null check is required, because otherwise <code>null</code> could sneak into the Object.keys call and cause an error. (Recall that <code>typeof null === "object"</code>).
+
&#x2714; This is also OK:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
if (value && typeof value === "object") {
 +
    // use value as an object
 +
    Object.keys(value);
 +
}
 +
</source></blockquote>
 +
 
 +
The reason that both a <code>typeof</code> and null (or truthiness) check is required is because <code>typeof null === "object"</code>. We need the additional test to prevent <code>null</code> from sneaking into the Object.keys call and causing an error.
  
 
=== Testing for definedness ===
 
=== Testing for definedness ===
Line 136: Line 224:
  
 
&#x2714; Compare the parameter's type against <code>"undefined"</code>:
 
&#x2714; Compare the parameter's type against <code>"undefined"</code>:
function variadic(param) {
+
<blockquote><source lang="javascript" enclose="div" line>
  if (typeof param === "undefined") {
+
function variadic(param) {
      console.log("Called with no arguments");
+
    if (typeof param === "undefined") {
  } else {
+
        console.log("Called with no arguments");
      console.log("Called with 1 argument");
+
    } else {
  }
+
        console.log("Called with 1 argument");
}
+
    }
 +
}
 +
</source></blockquote>
  
But why not just compare against <code>undefined</code>? Well, <code>undefined</code> is actually a reference to the global <code>window.undefined</code> property, not a primitive value. (Older JS engines would actually allow <code>undefined</code> to be overwritten, although ES5 has thankfully prevented that.) Because <code>undefined</code> is a legal variable name, it makes your code vulnerable to accidental lexical closures if someone else has foolishly created an in-scope variable named <code>undefined</code>:
+
&#x2714; Equality comparison against <code>undefined</code>. This approach is also OK, with caveats (see below).
 
+
<blockquote><source lang="javascript" enclose="div" line>
&#x2718; A broken comparison against <code>undefined</code>:
+
function variadic(param) {
function() {
+
    if (param === undefined) {
    var undefined = true;  // Never do this
+
        console.log("Called with no arguments");
+
    } else {
    function wrong(param) {
+
        console.log("Called with 1 argument");
        if (param === undefined) {
+
    }
            console.log("param is undefined... or is it?");
+
}
        }
+
</source></blockquote>
    }
+
Keep in mind that <code>undefined</code> here is actually a reference to the global <code>window.undefined</code> property, '''not''' a primitive value. This means your code is vulnerable to accidental lexical closures if in-scope variable is named <code>undefined</code>. (But who would do such a thing? Nobody, we hope.)
    wrong();    // does nothing
+
    wrong(true); // prints "param is undefined... or is it?"
+
}
+
  
 
=== Looping through an object's properties ===
 
=== Looping through an object's properties ===
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys Object.keys]:
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys Object.keys]:
Object.keys(object).forEach(function(property) {
+
<blockquote><source lang="javascript" enclose="div" line>
 +
Object.keys(object).forEach(function(property) {
 
     console.log('property: ' + property);
 
     console.log('property: ' + property);
})
+
})
 +
</source></blockquote>
  
 
&#x2718; Don't do it this way. While the code is technically correct, <code>Object.keys</code> is clearer and more concise:
 
&#x2718; Don't do it this way. While the code is technically correct, <code>Object.keys</code> is clearer and more concise:
for (var property in object) {
+
<blockquote><source lang="javascript" enclose="div" line>
    if (Object.prototype.hasOwnProperty.call(object, property)) {
+
for (var property in object) {
        console.log('property: ' + property);
+
    if (Object.prototype.hasOwnProperty.call(object, property)) {
    }
+
        console.log('property: ' + property);
}
+
    }
 +
}
 +
</source></blockquote>
  
 
&#x2718; Don't do this. It iterates through all properties in <code>object</code>'s prototype chain, which is almost never what you want:
 
&#x2718; Don't do this. It iterates through all properties in <code>object</code>'s prototype chain, which is almost never what you want:
for (var property in object) {
+
<blockquote><source lang="javascript" enclose="div" line>
    console.log('property: ' + property);
+
for (var property in object) {
}
+
    console.log('property: ' + property);
 +
}
 +
</source></blockquote>
  
 
== Functions ==
 
== Functions ==
 
=== Attaching a particular <code>this</code>-context to a function ===
 
=== Attaching a particular <code>this</code>-context to a function ===
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind Function.prototype.bind]:
 
&#x2714; Use [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind Function.prototype.bind]:
var context = { myfield: "Hello world" };
+
<blockquote><source lang="javascript" enclose="div" line>
var func = function() {
+
var context = { myfield: "Hello world" };
 +
var func = function() {
 
     return this.myfield;
 
     return this.myfield;
};
+
};
var boundFunction = func.bind(context);
+
var boundFunction = func.bind(context);
boundFunction(); // Hello world
+
boundFunction(); // Hello world
 +
</source></blockquote>
  
 
<code>bind</code> is especially useful when dealing with event listeners, which need to be passed around to addEventListener methods but retain their <code>this</code>-context:
 
<code>bind</code> is especially useful when dealing with event listeners, which need to be passed around to addEventListener methods but retain their <code>this</code>-context:
  
function Widget() {
+
<blockquote><source lang="javascript" enclose="div" line>
 +
function Widget() {
 
     this.clickCount = 0;
 
     this.clickCount = 0;
 
     document.getElementById("myButton").addEventListener("click", this.handleClick.bind(this)); // handleClick always gets the right "this"
 
     document.getElementById("myButton").addEventListener("click", this.handleClick.bind(this)); // handleClick always gets the right "this"
}
+
}
Widget.protoype.handleClick = function() {
+
Widget.protoype.handleClick = function() {
 
     this.clickCount++;
 
     this.clickCount++;
 
     alert("You clicked me " + this.clickCount + " times");
 
     alert("You clicked me " + this.clickCount + " times");
};
+
};
<!--
+
</source></blockquote>
Don't go overboard with <code>bind</code>, however. If you find yourself writing deep nested .bind()ed functions only to access outer state, consider using a lexical closure instead. (As a bonus, lexical closures can be statically checked for misspellings by the validator; field accesses through <code>this</code> cannot).
+
 
-->
+
== Errors ==
 +
=== Throwing an error ===
 +
&#x2714; When throwing an error, use the <code>Error</code> constructor:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
throw new Error("Something went wrong");
 +
</source></blockquote>
 +
 
 +
The reason for this is that the JS engine adds additional information to Error instances. For example, Errors have a <code>stack</code> property that gives a backtrace of where the error occurred. This is valuable for debugging. You can also [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types create a custom error type] that inherits from <code>Error</code>.
 +
 
 +
Do not throw strings or other objects directly! Without the extra info provided by <code>Error</code>, [http://www.nczonline.net/blog/2009/03/10/the-art-of-throwing-javascript-errors-part-2/ it can be difficult] for other programmers to track down the source of an error.
 +
 
 +
&#x2718; Do not throw non-Errors:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
throw "Something went wrong, but you may never see this message!";
 +
</source></blockquote>
  
 
== Classes ==
 
== Classes ==
 
=== Writing a class ===
 
=== Writing a class ===
 
The Orion approach to writing "classes" is fairly straightforward. It doesn't require any fancy libraries, just constructor functions and prototypes:
 
The Orion approach to writing "classes" is fairly straightforward. It doesn't require any fancy libraries, just constructor functions and prototypes:
function Duck(name) {
+
<blockquote><source lang="javascript" enclose="div" line>
    this.name = name;
+
function Duck(name) {
}
+
    this.name = name;
Duck.prototype.greet = function() {
+
}
    console.log("Quack quack, my name is " + this.name);
+
Duck.prototype.greet = function() {
};
+
    console.log("Quack quack, my name is " + this.name);
 +
};
 +
</source></blockquote>
  
 
The <code>mixin()</code> function from the <code>orion/objects</code> module can make your code more compact when you have a lot of properties to add to the prototype. Using <code>mixin</code>, the above example would look like:
 
The <code>mixin()</code> function from the <code>orion/objects</code> module can make your code more compact when you have a lot of properties to add to the prototype. Using <code>mixin</code>, the above example would look like:
function Duck(name) {
+
<blockquote><source lang="javascript" enclose="div" line>
    this.name = name;
+
function Duck(name) {
}
+
    this.name = name;
objects.mixin(Duck.prototype, {
+
}
    greet: function() {
+
objects.mixin(Duck.prototype, {
        console.log("Quack quack, I'm a duck named " + this.name);
+
    greet: function() {
    }
+
        console.log("Quack quack, I'm a duck named " + this.name);
    //, more properties here...
+
    }
});
+
    //, more properties here...
 +
});
 +
</source></blockquote>
  
 
=== Subclassing ===
 
=== Subclassing ===
 
To extend a class and add additional properties, we use a pattern similar to the code shown below. The subclass's constructor explicitly invokes the superclass's constructor to perform the usual <code>Duck</code> initialization, and then performs some extra initialization specific to the subclass. Note how <code>Object.create</code> is used to set up the desired prototype chain.
 
To extend a class and add additional properties, we use a pattern similar to the code shown below. The subclass's constructor explicitly invokes the superclass's constructor to perform the usual <code>Duck</code> initialization, and then performs some extra initialization specific to the subclass. Note how <code>Object.create</code> is used to set up the desired prototype chain.
  
function SeaDuck(name, swimSpeed) {
+
<blockquote><source lang="javascript" enclose="div" line>
    Duck.call(this, name);        // explicitly invoke super constructor
+
function SeaDuck(name, diveDepth) {
    this.swimSpeed = swimSpeed;
+
    Duck.call(this, name);        // explicitly invoke super constructor
}
+
    this.diveDepth = diveDepth;
SeaDuck.prototype = Object.create(Duck.prototype); // extend super prototype
+
}
SeaDuck.prototype.swim = function() {
+
SeaDuck.prototype = Object.create(Duck.prototype); // extend super prototype
    console.log(this.name + " is swimming at a speed of " + this.swimSpeed);
+
SeaDuck.prototype.dive = function() {
};
+
    console.log(this.name + " dived to a depth of " + this.diveDepth);
 +
;
 +
</source></blockquote>
  
In older code, you may see <code>new</code> used to extend the superclass's prototype. This is an abuse of constructors, and requires creating a bogus instance of the superclass to achieve the desired side effect of setting the prototype. Always use <code>Object.create</code> instead. If you have to support non-ES5 browsers, write a [http://www.room51.co.uk/js/beget.html beget utility].
+
In older code, you may see <code>new</code> used to extend the superclass's prototype. This is an abuse of constructors, and creates a bogus instance of the superclass just to achieve the desired side effect of setting the prototype. Always use <code>Object.create</code> instead. If you have to support non-ES5 browsers, write a [http://www.room51.co.uk/js/beget.html beget utility] or a shim for <code>Object.create</code>.
  
 
&#x2718; Don't use <code>new</code> to extend a superclass's prototype object.
 
&#x2718; Don't use <code>new</code> to extend a superclass's prototype object.
SeaDuck.prototype = new Duck();
+
<blockquote><source lang="javascript" enclose="div" line>
 +
SeaDuck.prototype = new Duck();
 +
</source></blockquote>
  
 
==== Overriding methods ====
 
==== Overriding methods ====
 
To override a method, we just add a method to the subclass's prototype having the same name as the superclass method:
 
To override a method, we just add a method to the subclass's prototype having the same name as the superclass method:
// overrides Duck.protoype.greet
+
<blockquote><source lang="javascript" enclose="div" line>
SeaDuck.prototype.greet = function() {
+
// overrides Duck.protoype.greet
    console.log("Avast, I'm a sea duck named " + this.name);
+
SeaDuck.prototype.greet = function() {
};
+
    console.log("Avast, I'm a sea duck named " + this.name);
 +
};
 +
</source></blockquote>
 
JavaScript's prototypal inheritance also allows you to override methods on a per-instance basis, rather than per-class (although readers familiar with Java-style class models may find this surprising).
 
JavaScript's prototypal inheritance also allows you to override methods on a per-instance basis, rather than per-class (although readers familiar with Java-style class models may find this surprising).
  
 
To invoke the superclass's implementation of an overridden method (the equivalent of the Java <code>super</code> keyword), you need to explicitly call it using <code>SuperConstructor.prototype.methodName.call(this);</code>:
 
To invoke the superclass's implementation of an overridden method (the equivalent of the Java <code>super</code> keyword), you need to explicitly call it using <code>SuperConstructor.prototype.methodName.call(this);</code>:
  
SeaDuck.prototype.greet = function() {
+
<blockquote><source lang="javascript" enclose="div" line>
    Duck.prototype.greet.call(this);      // prints "Quack quack, I'm a duck named [whatever]"
+
SeaDuck.prototype.greet = function() {
    console.log("Just kidding, I'm a sea duck. Arrr");
+
    Duck.prototype.greet.call(this);      // prints "Quack quack, I'm a duck named [whatever]"
};
+
    console.log("Just kidding, I'm a sea duck. Arrr");
 +
};
 +
</source></blockquote>
  
 
== Creating a dictionary ==
 
== Creating a dictionary ==
Line 258: Line 380:
  
 
&#x2714; Do this:
 
&#x2714; Do this:
var map = Object.create(null);
+
<blockquote><source lang="javascript" enclose="div" >
 +
var map = Object.create(null);
 +
</source></blockquote>
  
 
&#x2714; Or this:
 
&#x2714; Or this:
var map = { };
+
<blockquote><source lang="javascript" enclose="div" >
 +
var map = { };
 +
</source></blockquote>
  
 
=== Checking if a key is present ===
 
=== Checking if a key is present ===
 
&#x2714; Use <code>hasOwnProperty</code> to determine if a given key is present in the map:
 
&#x2714; Use <code>hasOwnProperty</code> to determine if a given key is present in the map:
if (Object.prototype.hasOwnProperty.call(map, key)) {
+
<blockquote><source lang="javascript" enclose="div" line>
    console.log("The key " + key + " was found!"
+
if (Object.prototype.hasOwnProperty.call(map, key)) {
}  
+
    console.log("The key " + key + " was found!"
 +
}
 +
</source></blockquote>
  
 
&#x2718; Don't use a truthiness check to determine key presence:
 
&#x2718; Don't use a truthiness check to determine key presence:
if (map[key]) {
+
<blockquote><source lang="javascript" enclose="div" line>
    console.log("The key " + key + " was found!");
+
if (map[key]) {
}
+
    console.log("The key " + key + " was found!");
 +
}
 +
</source></blockquote>
  
 
The truthiness check will give false negatives if the key's value happens to be falsey (like <code>0</code> or <code>""</code>).  
 
The truthiness check will give false negatives if the key's value happens to be falsey (like <code>0</code> or <code>""</code>).  
  
 
&#x2718; But even correctly handling falsey values won't save the <code>[]</code> operator. Don't do this either:
 
&#x2718; But even correctly handling falsey values won't save the <code>[]</code> operator. Don't do this either:
if (typeof map[key] !== "undefined") {
+
<blockquote><source lang="javascript" enclose="div" line>
    console.log("The key " + key + " was found!");
+
if (typeof map[key] !== "undefined") {
}
+
    console.log("The key " + key + " was found!");
 +
}
 +
</source></blockquote>
  
 
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.
 
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.
Line 286: Line 418:
 
Use the <code>orion/EventTarget</code> mixin to inject event behavior into an object. After calling <code>attach()</code>, your object implements the EventTarget [http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#interface-EventTarget DOM3 interface], and can dispatch events to listeners.
 
Use the <code>orion/EventTarget</code> mixin to inject event behavior into an object. After calling <code>attach()</code>, your object implements the EventTarget [http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#interface-EventTarget DOM3 interface], and can dispatch events to listeners.
  
define(["orion/EventTarget"], function(EventTarget) {
+
<blockquote><source lang="javascript" enclose="div" line>
    var myobject = {};
+
define(["orion/EventTarget"], function(EventTarget) {
    EventTarget.attach(myobject);
+
    var myobject = {};
+
    EventTarget.attach(myobject);
    myobject.addEventListener("coolevent", function(event) {
+
 
        console.log("Listener received an event");
+
    myobject.addEventListener("coolevent", function(event) {
    });
+
        console.log("Listener received an event");
    myobject.dispatchEvent({ type: "coolevent" }); // Every event must have a type. Other fields optional.
+
    });
});
+
    myobject.dispatchEvent({ type: "coolevent" }); // Every event must have a type. Other fields optional.
 +
});
 +
</source></blockquote>
  
 
To add event behavior to a class, call <code>attach</code> inside your constructor:
 
To add event behavior to a class, call <code>attach</code> inside your constructor:
  
function Duck(name) {
+
<blockquote><source lang="javascript" enclose="div" line>
    this.name = name;
+
function Duck(name) {
    EventTarget.attach(this);
+
    this.name = name;
}
+
    EventTarget.attach(this);
new Duck("Howard").dispatchEvent({ type: "quack" });
+
}
 +
new Duck("Howard").dispatchEvent({ type: "quack" });
 +
</source></blockquote>
  
 
== Asynchronous operations ==
 
== Asynchronous operations ==
Use <code>orion/Deferred</code> to manage asynchronous operations. Its API is [https://orionhub.org/jsdoc/symbols/orion.Deferred.html documented here].
+
Use <code>[https://orionhub.org/jsdoc/symbols/orion.Deferred.html orion/Deferred]</code> to manage asynchronous operations.
 
   
 
   
 
== XMLHttpRequest and Ajax ==
 
== XMLHttpRequest and Ajax ==
In most cases it's convenient to use <code>orion/xhr</code>, which wraps the browser's native XMLHttpRequest into a promise. The xhr API is [https://orionhub.org/jsdoc/symbols/orion.xhr.html documented here].
+
In most cases it's convenient to use <code>[https://orionhub.org/jsdoc/symbols/orion.xhr.html orion/xhr]</code>, which wraps the browser's native XMLHttpRequest into a promise API.
  
define["orion/xhr"], function(xhr) {
+
<blockquote><source lang="javascript" enclose="div" line>
    xhr("GET", "/index.html").then(
+
define["orion/xhr"], function(xhr) {
        function(xhrResult) {
+
    xhr("GET", "/index.html").then(
            console.log("Got response: " + xhrResult.response);
+
        function(xhrResult) {
        },
+
            console.log("Got response: " + xhrResult.response);
        function(xhrResult) {
+
        },
            console.log("An error occurred: " + xhrResult.error);
+
        function(xhrResult) {
        });
+
            console.log("An error occurred: " + xhrResult.error);
});
+
        });
 +
});
 +
</source></blockquote>
 +
 
 +
The returned promise resolves when it receives a response with a 2xx or 3xx status code, and rejects when the status code is 4xx or 5xx. It also rejects on network errors or timeouts. If you need finer-grained control over an Ajax request, <code>xhr</code> will not be helpful: use a plain old XMLHttpRequest.
  
 
== URLs ==
 
== URLs ==
Line 324: Line 464:
  
 
&#x2714; Do this:
 
&#x2714; Do this:
define(["orion/URL-shim"], function() {
+
<blockquote><source lang="javascript" enclose="div" line>
    var url = new URL("http://foo.com");
+
define(["orion/URL-shim"], function() {
    url.query.set("param1", "first value");
+
    var url = new URL("http://foo.com");
    url.query.set("param2", "second value");
+
    url.query.set("param1", "first value");
    var myurl = url.href; // http://foo.com/?param1=first%20value&param2=second%20value
+
    url.query.set("param2", "second value");
});
+
    var myurl = url.href; // http://foo.com/?param1=first%20value&param2=second%20value
 +
});
 +
</source></blockquote>
  
 
&#x2718; Don't do this:
 
&#x2718; Don't do this:
  var myurl = "http://foo.com/?param1=" + encodeURIComponent("first value") + "&" + encodeURIComponent("second value");
+
<blockquote><source lang="javascript" enclose="div">
 +
var myurl = "http://foo.com/?param1=" + encodeURIComponent("first value") + "&" + encodeURIComponent("second value");
 +
</source></blockquote>
  
 
Consider also using the <code>orion/URITemplate</code> module, which implements the [http://tools.ietf.org/html/rfc6570 URI Template spec].
 
Consider also using the <code>orion/URITemplate</code> module, which implements the [http://tools.ietf.org/html/rfc6570 URI Template spec].
 
  
  
 
== Services ==
 
== 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 [http://en.wikipedia.org/wiki/Reverse-DNS Reverse DNS] naming convention (like Java packages).
 
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 [http://en.wikipedia.org/wiki/Reverse-DNS Reverse DNS] naming convention (like Java packages).
  
 
== JSON ==
 
== JSON ==
 
 
Server requests and responses are represented by default as [http://json.org JSON] objects. Object member names use title case ("Name", "ChildrenLocation", etc).
 
Server requests and responses are represented by default as [http://json.org JSON] objects. Object member names use title case ("Name", "ChildrenLocation", etc).
  
 
== JS Documentation ==
 
== JS Documentation ==
 +
All JavaScript API uses [http://code.google.com/p/jsdoc-toolkit/ JSDoc] syntax (currently version 2). Orion code is written as self-contained AMD modules that don't expose global objects. So rather than tagging the actual objects with documentation, we tend to use JSDoc tags to construct virtual "classes" corresponding to a module's exported API. These virtual tags can appear almost anywhere in the source code, but it's best to keep them close to the functions that they represent.
  
All JavaScript API uses [http://code.google.com/p/jsdoc-toolkit/ JSDoc] syntax. Orion code is written as self-contained AMD modules that don't expose global objects. So rather than tagging the actual objects with documentation, we tend to use JSDoc tags to construct virtual "classes" corresponding to a module's exported API. These virtual tags can appear almost anywhere in the source code, but it's best to keep them close to the functions that they represent.
+
The main doc tags used are:
 
+
The principal doc tags used are:
+
  
 
; [http://code.google.com/p/jsdoc-toolkit/wiki/TagParam @param]
 
; [http://code.google.com/p/jsdoc-toolkit/wiki/TagParam @param]
Line 372: Line 512:
 
=== Documenting a class ===
 
=== Documenting a class ===
 
JSDoc doesn't distinguish between class documentation and constructor documentation: they both go into a single comment block. So to document a class, we have a comment containing <code>@name</code>, <code>@class</code>, and <code>@description</code> tags, plus all the normal method tags (<code>@param</code> and so on).
 
JSDoc doesn't distinguish between class documentation and constructor documentation: they both go into a single comment block. So to document a class, we have a comment containing <code>@name</code>, <code>@class</code>, and <code>@description</code> tags, plus all the normal method tags (<code>@param</code> and so on).
 
+
<source lang="javascript" enclose="div" line>
/**
+
/**
  * @name orion.MyClass
+
* @name orion.MyClass
  * @class Class header summary of MyClass.
+
* @class Class header summary of MyClass.
  * @description One-line summary of MyClass.
+
* @description One-line summary of MyClass.
  * Longer description of MyClass, which can include code examples (in &lt;code&gt; tags),
+
* Longer description of MyClass, which can include code examples (in <code> tags),
  * HTML and links to other docs (using @see). This also serves as documentation  
+
* HTML and links to other docs (using @see). This also serves as documentation  
  * for the constructor function.
+
* for the constructor function.
  *  
+
*
  * @param {String} param1 The widget to frob.
+
* @param {String} param1 The widget to frob.
  * @param {Boolean} [param2=false] Whether the flux capacitor should be quuxed.
+
* @param {Boolean} [param2=false] Whether the flux capacitor should be quuxed.
  */
+
*/
function MyClass(param1, param2) {
+
function MyClass(param1, param2) {
    // ...
+
    // ...
}
+
}
 +
</source>
  
 
==== Methods ====
 
==== Methods ====
Line 409: Line 550:
 
===== Per-instance method =====  
 
===== Per-instance method =====  
 
If you're injecting methods into each instance of your class, a method doc comment looks like this:
 
If you're injecting methods into each instance of your class, a method doc comment looks like this:
function ServiceTracker() {
+
 
    /**
+
<source lang="javascript" enclose="div" line>
      * Returns service references to the services that are being tracked.
+
function ServiceTracker() {
      * @name orion.ServiceTracker#getServiceReferences
+
    /**
      * @function
+
    * Returns service references to the services that are being tracked.
      * @returns {orion.serviceregistry.ServiceReference[]} References to all services that
+
    * @name orion.ServiceTracker#getServiceReferences
      * are being tracked by this ServiceTracker.
+
    * @function
      */
+
    * @returns {orion.serviceregistry.ServiceReference[]} References to all services that
    this.getServiceReferences = function() {
+
    * are being tracked by this ServiceTracker.
        // ...
+
    */
    };
+
    this.getServiceReferences = function() {
}
+
        // ...
 +
    };
 +
}</source>
  
 
===== Prototype method (mixin lends) =====  
 
===== Prototype method (mixin lends) =====  
 
This is for when your class mixes in (or directly uses) an object literal for its prototype. We tag the object with <code>@lends</code>, causing JSDoc to add all the literal's fields into the class prototype. Our method docs can then omit the <code>@function</code> and <code>@name</code> tags, because JSDoc already knows the containing class and the type of member.
 
This is for when your class mixes in (or directly uses) an object literal for its prototype. We tag the object with <code>@lends</code>, causing JSDoc to add all the literal's fields into the class prototype. Our method docs can then omit the <code>@function</code> and <code>@name</code> tags, because JSDoc already knows the containing class and the type of member.
function ServiceTracker() {
+
<source lang="javascript" enclose="div" line highlight="4">
    // ...
+
function ServiceTracker() {
}
+
    // ...
ServiceTracker.prototype = /** @lends orion.MyClass.prototype */ {
+
}
    /**
+
ServiceTracker.prototype = /** @lends orion.MyClass.prototype */ {
      * Called to customize a service object being added to this ServiceTracker.
+
    /**
      * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
+
    * Called to customize a service object being added to this ServiceTracker.
      * @returns {Object} The service object to be tracked for the given service reference.
+
    * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
      */
+
    * @returns {Object} The service object to be tracked for the given service reference.
    addingService: function(serviceRef) {
+
    */
        // ...
+
    addingService: function(serviceRef) {
    }
+
        // ...
    //, Additional prototype methods can go here.
+
    }
};
+
    //, ... Additional prototype methods can go here.
 
+
};
 +
</source>
 
===== Prototype method (assignment) =====  
 
===== Prototype method (assignment) =====  
 
When assigning methods directly to our class's prototype, JSDoc falls over somewhat, and can't infer the containment or the member type. So we need to explicitly place each method into the class using <code>@name</code>, and function-ify it with <code>@function</code>:
 
When assigning methods directly to our class's prototype, JSDoc falls over somewhat, and can't infer the containment or the member type. So we need to explicitly place each method into the class using <code>@name</code>, and function-ify it with <code>@function</code>:
function ServiceTracker() {
+
<source lang="javascript" enclose="div" line highlight="5-6">
}
+
function ServiceTracker() {
/**
+
}
  * Called to customize a service object being added to this ServiceTracker.
+
/**
  <b> * @name orion.ServiceTracker#addingService
+
* Called to customize a service object being added to this ServiceTracker.
  * @function</b>
+
  * @name orion.ServiceTracker#addingService
  * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
+
* @function
  * @returns {Object} The service object to be tracked for the given service reference.
+
* @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
  */
+
* @returns {Object} The service object to be tracked for the given service reference.
ServiceTracker.prototype.addingService = function(serviceRef) {
+
*/
    // ...
+
ServiceTracker.prototype.addingService = function(serviceRef) {
};
+
    // ...
 
+
};
 +
</source>
 
===== Static methods =====
 
===== Static methods =====
 
To indicate that a method is static, make it a member of the class itself, rather than the class's prototype:
 
To indicate that a method is static, make it a member of the class itself, rather than the class's prototype:
/**
+
<source lang="javascript" enclose="div" line>
  * @name orion.Deferred.all
+
/**
  * @function
+
* @name orion.Deferred.all
  */
+
* @function
  function all(promises) {
+
*/
      // ...
+
function all(promises) {
  }
+
    // ...
 +
}
 +
</source>
 
JSDoc also has a <code>@static</code> tag, but you probably won't need it.
 
JSDoc also has a <code>@static</code> tag, but you probably won't need it.
  
 
===== Types =====
 
===== Types =====
Any type known to the JSDoc parser can appear inside the curly braces of a <code>@param</code> or <code>@return</code> tag. A type can be a fully-qualified class name, or a global JavaScript object like Boolean, String, Function, etc. The Orion source generally uses the capitalized object wrapper names to refer to primitive types: for example we say <code>String</code> rather than <code>string</code>, <code>Boolean</code> rather than <code>boolean</code>, and so on.
+
One helpful feature of JSDoc is ''types'', which can appear inside the curly braces of a <code>@param</code> or <code>@return</code> tag, and a few other places as well. When the JSDoc parser recognizes a reference to a type, it turns it into a clickable hyperlink in the HTML documentation, which is very nice.
 +
 
 +
A type can be a fully-qualified class name, or a global JavaScript object like Boolean, String, Function, etc. The Orion source generally uses the capitalized object wrapper names to refer to primitive types: for example we say <code>String</code> rather than <code>string</code>, <code>Boolean</code> rather than <code>boolean</code>, and so on.
  
 
JSDoc also understands array types: append <code>[]</code> after the name of any type to indicate that it's an array.
 
JSDoc also understands array types: append <code>[]</code> after the name of any type to indicate that it's an array.
 +
<source lang="javascript" enclose="div">
 
  /**
 
  /**
   * @param <b>{orion.Promise[]}</b> promises
+
   * @param {orion.Promise[]} promises
 
   */
 
   */
 +
</source>
  
 
To indicate that a param or return type takes one of a set of types, join the types with a <code>|</code>:
 
To indicate that a param or return type takes one of a set of types, join the types with a <code>|</code>:
 +
<source lang="javascript" enclose="div">
 
  /**
 
  /**
   * @param <b>{String|Element}</b> elementOrDomId
+
   * @param {String|Element} elementOrDomId
 
   */
 
   */
 +
</source>
  
 
When a type can be "anything", we represent it as <code>{Object}</code>.
 
When a type can be "anything", we represent it as <code>{Object}</code>.
  
 
==== Fields ====
 
==== Fields ====
 +
 +
== JSLint ==
 +
While most validation problems raised by JSLint are helpful, there are some situations where they need to be ignored. In these cases, add a <code>/*jslint ... */</code> comment block near the top of your code that turns off the problems. A sample jslint block might look like:
 +
<blockquote><source lang="javascript" enclose="div">
 +
/*jslint forin:true regexp:false sub:true*/
 +
</source></blockquote>
 +
Here's a list of common warnings and how to deal with them:
 +
 +
; <code>["foo"]</code> is better written in dot notation.
 +
: This warning is raised when code uses the <code>[]</code> operator to access a constant property name. It's good practice to use the <code>.</code> operator instead. When changing the code is unfeasible (for example, when dealing with output from the [[Orion/Internationalization#String_Xtrnalizr|Orion string externalization plugin]]), use <code>sub:true</code> to disable this warning.
 +
 +
; Bad line breaking before {something}
 +
: Use <code>laxbreak:true</code> to disable this warning. Be very careful breaking around a <code>return</code> statement, however: JavaScript's [http://en.wikipedia.org/wiki/JavaScript_syntax#Whitespace_and_semicolons semicolon insertion] can produce unexpected results.
 +
 +
; Insecure <code>'^'</code>.
 +
: This warning is raised on regular expressions that use a negated character set like <code>[^A-Z]</code>. Use <code>regexp:false</code> to disable the warning.
 +
 +
; The body of a <code>for in</code> should be wrapped in an if statement to filter unwanted properties from the prototype
 +
: While this is generally good advice, sometimes an unfiltered loop is desirable. Use <code>forin:true</code> to disable this warning.
 +
 +
; Statement body should be inside <code>'{ }'</code> braces.
 +
: JSLint raises this warning when you write a block-style statement (like <code>if</code>, <code>else</code>, <code>for</code>, <code>while</code>) without enclosing the statements inside the block in curly braces <code>{ }</code>. There's no way to disable this warning, so it's best to just add the curly braces and carry on.
 +
 +
; '{variable}' is already defined.
 +
: This warning is raised when you define the same variable more than once within a function. Usually this results from a misunderstanding of [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Statements/block JavaScript's scoping rules]. In JavaScript, <code>var</code> is scoped to the enclosing function, '''not''' the enclosing block. When two blocks define the same variable, it is redundant and may mislead readers as to the underlying language semantics. To eliminate this warning (and make your code clearer), move the variable definition outside the blocks.
 +
 +
&#x2718; Don't redefine a variable inside a block:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
if (condition) {
 +
    var x = 'foo';
 +
} else {
 +
    var x = 'bar'; // 'x' is already defined
 +
}
 +
</source></blockquote>
 +
 +
&#x2714; Define the variable outside the block instead:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
var x;
 +
if (condition) {
 +
    x = 'foo';
 +
} else {
 +
    x = 'bar';
 +
}
 +
</source></blockquote>
 +
 +
It's OK to define a variable inside a block if the variable is not referenced anywhere else in the function.
 +
 +
= JavaScript Formatting =
 +
* The Orion codebase isn't totally consistent in its formatting style. When changing a file, try to maintain the same formatting style that the rest of the file uses.
 +
* Please, '''do not''' use the JavaScript code formatter provided by Eclipse's JavaScript Development Tools (JSDT).
 +
* Feel free to fix sloppy or unclear formatting when you find it, but please keep this work in a separate commit from any functional changes.
  
 
= HTML =
 
= HTML =
Line 491: Line 695:
 
A fragment is a little chunk of HTML that describes a component.  There are a few choices in the mechanics of where a fragment is defined.   
 
A fragment is a little chunk of HTML that describes a component.  There are a few choices in the mechanics of where a fragment is defined.   
 
* A fragment could be defined in js code as a string.  Consider this snippet from the dialog prototype:
 
* A fragment could be defined in js code as a string.  Consider this snippet from the dialog prototype:
<pre>
+
 
Dialog.prototype.TEMPLATE =
+
<source lang="javascript" enclose="div" line>
'<div class="dialog" role="dialog">' + //$NON-NLS-0$
+
Dialog.prototype.TEMPLATE = '' +
 +
  '<div class="dialog" role="dialog">' + //$NON-NLS-0$
 
'<div class="dialogTitle layoutBlock"><span id="title" class="dialogTitleText layoutLeft"></span></div>' + //$NON-NLS-0$
 
'<div class="dialogTitle layoutBlock"><span id="title" class="dialogTitleText layoutLeft"></span></div>' + //$NON-NLS-0$
 
'<div class="layoutBlock"><hr/></div>' + //$NON-NLS-0$
 
'<div class="layoutBlock"><hr/></div>' + //$NON-NLS-0$
 
'<div id="dialogContent" class="dialogContent layoutBlock"></div>' + //$NON-NLS-1$ //$NON-NLS-0$
 
'<div id="dialogContent" class="dialogContent layoutBlock"></div>' + //$NON-NLS-1$ //$NON-NLS-0$
 
'<div id="buttons" class="dialogButtons"></div>' + //$NON-NLS-1$ //$NON-NLS-0$
 
'<div id="buttons" class="dialogButtons"></div>' + //$NON-NLS-1$ //$NON-NLS-0$
'</div>'; //$NON-NLS-0$
+
  '</div>'; //$NON-NLS-0$
</pre>
+
</source>
 +
 
 
An advantage of this approach when using Orion to edit fragments is keeping the fragment close to the JavaScript code that is manipulating it or making assumptions about its structure.  This is, of course, a tooling issue (not having split editors, or popping up references, etc.)  The disadvantage is the noise created by concatenation, $NON-NLS markings, etc.  This kind of code also tempts the developer to directly concatenate NLS message strings into the content, and that is a security risk (described later).
 
An advantage of this approach when using Orion to edit fragments is keeping the fragment close to the JavaScript code that is manipulating it or making assumptions about its structure.  This is, of course, a tooling issue (not having split editors, or popping up references, etc.)  The disadvantage is the noise created by concatenation, $NON-NLS markings, etc.  This kind of code also tempts the developer to directly concatenate NLS message strings into the content, and that is a security risk (described later).
  
 
* A fragment can be defined in an HTML file that is brought into the JavaScript module via a requirejs <tt>text!</tt> module.  For example, the common page footer is defined in a footer.html file like this:
 
* A fragment can be defined in an HTML file that is brought into the JavaScript module via a requirejs <tt>text!</tt> module.  For example, the common page footer is defined in a footer.html file like this:
<pre>
+
 
 +
<source lang="html5" enclose="div" line>
 
<footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo">
 
<footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo">
 
<!-- Requires some js code to bind the NLS variables  -->
 
<!-- Requires some js code to bind the NLS variables  -->
Line 515: Line 722:
 
</div>
 
</div>
 
</footer>
 
</footer>
</pre>
+
</source>
  
 
Javascript code would refer to the fragment by the requireJS module name.
 
Javascript code would refer to the fragment by the requireJS module name.
<pre>
+
<source lang="javascript" enclose="div" line>
 
define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/footer.html'],
 
define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/footer.html'],
function(messages, require, FooterFragment) {
+
  function(messages, require, FooterFragment) {
  
// excerpt from common page code
+
    // excerpt from common page code
var footer = document.getElementById("footer"); //$NON-NLS-0$
+
    var footer = document.getElementById("footer"); //$NON-NLS-0$
if (footer) {
+
    if (footer) {
 
footer.innerHTML = FooterFragment;
 
footer.innerHTML = FooterFragment;
}
+
    }
</pre>
+
  });
 +
</source>
  
* A fragment can also be defined inside a template element.  Orion provides a shim for the draft [https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html HTML Templates API] that lets you define <tt>template</tt> elements. Templates are a promising approach for the following reasons:
+
A fragment can also be defined inside a template element.  Orion provides a shim for the draft [https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html HTML Templates API] that lets you define <tt>template</tt> elements. Templates are a promising approach for the following reasons:
** if you use the fragment many times on a page, using a template means it will be parsed only once.
+
** If you use the fragment many times on a page, using a template means it will be parsed only once.
** once parsed, you can make common, dynamic modifications to the template (binding variable values, etc.) only once.
+
** Once parsed, you can make common, dynamic modifications to the template (binding variable values, etc.) only once.
** the DOM structure behind a template is not associated with the document until a node is actually cloned.  This means that if the template references network resources (such as an image URL), there will be no attempt to download the image until the template is actually cloned into a node and placed in the document.  
+
** The DOM structure behind a template is not associated with the document until a node is actually cloned.  This means that if the template references network resources (such as an image URL), there will be no attempt to download the image until the template is actually cloned into a node and placed in the document.  
  
If we used a template for the footer fragment, it would look like this instead. (Using a template for a footer that appears once is not necessarily a good idea, but this is shown to illustrate the difference in approach:)
+
If we used a template for the footer fragment, it would look like this instead. (Using a template for a footer that appears once is not necessarily a good idea, but this is shown to illustrate the difference in approach:)
<pre>
+
<source lang="html5" enclose="div" line>
 
<template id="footerTemplate">
 
<template id="footerTemplate">
 
   <footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo">
 
   <footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo">
Line 549: Line 757:
 
   </footer>
 
   </footer>
 
</template>
 
</template>
</pre>
+
</source>
  
 
The JavaScript would look like this:
 
The JavaScript would look like this:
<pre>
+
<source lang="javascript" enclose="div" line>
 
define(['i18n!orion/nls/messages', 'require', 'orion/HTMLTemplates-shim' 'template!orion/banner/footer.html'],  // note use of template! not text!
 
define(['i18n!orion/nls/messages', 'require', 'orion/HTMLTemplates-shim' 'template!orion/banner/footer.html'],  // note use of template! not text!
function(messages, require) {
+
  function(messages, require) {
  
var template = document.getElementById("footerTemplate");
+
    var template = document.getElementById("footerTemplate");
var footer = document.getElementById("footer"); //$NON-NLS-0$
+
    var footer = document.getElementById("footer"); //$NON-NLS-0$
if (footer) {
+
    if (footer) {
footer.appendChild(template.content.cloneNode());
+
        footer.appendChild(template.content.cloneNode());
}
+
    }
</pre>
+
  });
 +
</source>
  
 
The example above assumes that we have a requirejs plugin for parsing html files and building a template element.  ([https://bugs.eclipse.org/bugs/show_bug.cgi?id=395402 Bug 395402]).  Until we have such a plugin, the template would have to be marked up in the page itself.
 
The example above assumes that we have a requirejs plugin for parsing html files and building a template element.  ([https://bugs.eclipse.org/bugs/show_bug.cgi?id=395402 Bug 395402]).  Until we have such a plugin, the template would have to be marked up in the page itself.
Line 571: Line 780:
 
==== innerHTML ====
 
==== innerHTML ====
 
If you have a node that is exclusive to the fragment, you can use the <tt>innerHTML</tt> of the node.  The original footer example showed this.
 
If you have a node that is exclusive to the fragment, you can use the <tt>innerHTML</tt> of the node.  The original footer example showed this.
<pre>
+
<source lang="javascript" enclose="div" line>
 
define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/footer.html'],
 
define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/footer.html'],
function(messages, require, FooterTemplate) {
+
  function(messages, require, FooterTemplate) {
  
// excerpt from common page code
+
    // excerpt from common page code
var footer = document.getElementById("footer"); //$NON-NLS-0$
+
    var footer = document.getElementById("footer"); //$NON-NLS-0$
if (footer) {
+
    if (footer) {
footer.innerHTML = FooterTemplate;
+
        footer.innerHTML = FooterTemplate;
}
+
    }
</pre>
+
  });
 +
</source>
 
Of course, if there were any other nodes inside the footer DOM node, they would be removed.
 
Of course, if there were any other nodes inside the footer DOM node, they would be removed.
  
Line 586: Line 796:
 
Another approach, useful when you are adding a fragment to a parent and don't want to remove other child nodes, is to create a contextual fragment and then add it into the DOM as you would any other node.  For example, this code adds a banner fragment as the first child of the document body.  
 
Another approach, useful when you are adding a fragment to a parent and don't want to remove other child nodes, is to create a contextual fragment and then add it into the DOM as you would any other node.  For example, this code adds a banner fragment as the first child of the document body.  
  
<pre>
+
<source lang="javascript" enclose="div" line>
 
define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/banner.html'],
 
define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/banner.html'],
function(messages, require, BannerFragment) {
+
  function(messages, require, BannerFragment) {
var range = document.createRange();
+
      var range = document.createRange();
var parent = document.body;
+
      var parent = document.body;
range.selectNode(parent);
+
      range.selectNode(parent);
var headerFragment = range.createContextualFragment(BannerFragment);
+
      var headerFragment = range.createContextualFragment(BannerFragment);
+
 
if (parent.firstChild) {
+
      if (parent.firstChild) {
parent.insertBefore(headerFragment, parent.firstChild);
+
          parent.insertBefore(headerFragment, parent.firstChild);
} else {
+
      } else {
parent.appendChild(headerFragment);
+
          parent.appendChild(headerFragment);
}
+
      }
</pre>
+
  });
 +
</source>
  
 
=== Security and Data Binding ===
 
=== Security and Data Binding ===
Line 605: Line 816:
  
 
Do not concatenate variable strings into a fragment and insert into the document.  Instead, a data binding approach should be used to refer to the values by their key.  Strings can be bound to a fragment using the <tt>processTextNodes</tt> function in 'orion/webui/littlelib'.  This utility allows you to refer to the strings in your template using variables, and DOM API will be used later to bind your string to the text node in the HTML.  You may have noticed in the Orion footer example, we use ${variable} syntax in the markup.  The user of such a fragment is responsible for providing the correct data binding object for processing the DOM elements.
 
Do not concatenate variable strings into a fragment and insert into the document.  Instead, a data binding approach should be used to refer to the values by their key.  Strings can be bound to a fragment using the <tt>processTextNodes</tt> function in 'orion/webui/littlelib'.  This utility allows you to refer to the strings in your template using variables, and DOM API will be used later to bind your string to the text node in the HTML.  You may have noticed in the Orion footer example, we use ${variable} syntax in the markup.  The user of such a fragment is responsible for providing the correct data binding object for processing the DOM elements.
<pre>
+
<source lang="javascript" enclose="div" line highlight="9">
 
define(['i18n!orion/nls/messages', 'require', 'orion/webui/littlelib','text!orion/banner/footer.html'],
 
define(['i18n!orion/nls/messages', 'require', 'orion/webui/littlelib','text!orion/banner/footer.html'],
function(messages, require, lib, FooterTemplate) {
+
  function(messages, require, lib, FooterTemplate) {
  
// excerpt from common page code
+
    // excerpt from common page code
 
+
    var footer = lib.node("footer"); //$NON-NLS-0$
var footer = lib.node("footer"); //$NON-NLS-0$
+
    if (footer) {
if (footer) {
+
        footer.innerHTML = FooterTemplate;
footer.innerHTML = FooterTemplate;
+
        // do the i18n string substitutions
// do the i18n string substitutions
+
        lib.processTextNodes(footer, messages);
lib.processTextNodes(footer, messages);
+
    }
}
+
  });
</pre>
+
</source>
  
 
This data binding approach can be used for any object, not just NLS message objects.  When the DOM tree text nodes are processed, any nodes that have the ${variable} syntax will be considered to describe a property name.  The text nodes containing the variables will be replaced with nodes containing matching property value found in the bound object.  If no matching property is found, the original text remains.
 
This data binding approach can be used for any object, not just NLS message objects.  When the DOM tree text nodes are processed, any nodes that have the ${variable} syntax will be considered to describe a property name.  The text nodes containing the variables will be replaced with nodes containing matching property value found in the bound object.  If no matching property is found, the original text remains.
Line 636: Line 847:
  
 
&#x2718; A rule with many DOM selectors is brittle:
 
&#x2718; A rule with many DOM selectors is brittle:
.mytable > div > div > label > span {
+
<blockquote><source lang="css" enclose="div" line>
    font-weight: bold;
+
.mytable > div > div > label > span {
}
+
    font-weight: bold;
 +
}
 +
</source></blockquote>
  
 
&#x2714; Something like this is better:
 
&#x2714; Something like this is better:
.mytable span.emphasis {
+
<blockquote><source lang="css" enclose="div" line>
    font-weight: bold;
+
.mytable span.emphasis {
}
+
    font-weight: bold;
 +
}
 +
</source></blockquote>
  
 
=== Don't use unqualified tag selectors ===
 
=== Don't use unqualified tag selectors ===
Line 649: Line 864:
  
 
&#x2718; Don't use selectors that match an entire class name:
 
&#x2718; Don't use selectors that match an entire class name:
div {
+
<blockquote><source lang="css" enclose="div" line>
    background-color: red;
+
div {
}
+
    background-color: red;
 +
}
 +
</source></blockquote>
  
 
&#x2714; Instead, create a class selector that targets only the elements you want:
 
&#x2714; Instead, create a class selector that targets only the elements you want:
div.myclass {
+
<blockquote><source lang="css" enclose="div" line>
    background-color: red;
+
div.myclass {
}
+
    background-color: red;
 +
}
 +
</source></blockquote>
  
 
The html and body elements are exceptions.
 
The html and body elements are exceptions.
Line 664: Line 883:
  
 
&#x2718; Instead of this:
 
&#x2718; Instead of this:
#content-title {
+
<blockquote><source lang="css" enclose="div" line>
    text-shadow: white 0 1px 2px;
+
#content-title {
}
+
    text-shadow: white 0 1px 2px;
 +
}
 +
</source></blockquote>
  
 
&#x2714; &#x2026;Use a class:
 
&#x2714; &#x2026;Use a class:
.content-title {
+
<blockquote><source lang="css" enclose="div" line>
    text-shadow: white 0 1px 2px;
+
.content-title {
}
+
    text-shadow: white 0 1px 2px;
 +
}
 +
</source></blockquote>
  
 
= Java =
 
= 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 <tt>org.eclipse.orion</tt>.
 
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 <tt>org.eclipse.orion</tt>.
  
Line 680: Line 902:
  
 
= Copyrights =
 
= Copyrights =
 
 
The Orion server source code is licensed under the [http://eclipse.org/org/documents/epl-v10.php Eclipse Public License]. The standard Eclipse [http://eclipse.org/legal/copyrightandlicensenotice.php copyright header] should be used for these source files.
 
The Orion server source code is licensed under the [http://eclipse.org/org/documents/epl-v10.php Eclipse Public License]. The standard Eclipse [http://eclipse.org/legal/copyrightandlicensenotice.php copyright header] should be used for these source files.
  
 
The Orion client code (HTML, CSS, JavaScript), is licensed under both the [http://eclipse.org/org/documents/epl-v10.php Eclipse Public License] and the [http://eclipse.org/org/documents/edl-v10.php Eclipse Distribution License]. Here is a sample copyright header to use in JavaScript source files:
 
The Orion client code (HTML, CSS, JavaScript), is licensed under both the [http://eclipse.org/org/documents/epl-v10.php Eclipse Public License] and the [http://eclipse.org/org/documents/edl-v10.php Eclipse Distribution License]. Here is a sample copyright header to use in JavaScript source files:
 +
<blockquote><source lang="javascript" enclose="div" line>
 +
/*******************************************************************************
 +
* 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
 +
******************************************************************************/
 +
</source></blockquote>
  
/*******************************************************************************
+
[[Category:Orion|C]]
  * 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
+
  ******************************************************************************/
+
 
+
[[Category:Orion|Naming Conventions]]
+

Revision as of 12:55, 22 January 2014

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.

Contents

JavaScript

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 yet.
  • 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.
  • Each .js file should define only a single module.
  • If your code depends on other modules or files, that relationship should be expressed as AMD dependencies (see example below).
  • 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.
    • When authoring a UMD module, in the "no module-loader" case, your code should place its exports inside the orion property of the global object.

✔ Here's how to write a module that depends on orion/Deferred and orion/xhr:

  1. define(["orion/Deferred", "orion/xhr"], function(Deferred, xhr) {
  2.     // Use Deferred and xhr here.
  3. });

Globals

Your code should not export any* globals (with the exception of shims -- see URL-shim.js for an example). 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.

  1. for (i = 0; i < 10; i++) {       // ERROR: implied global `i`
  2.     console.log("Hello world");
  3. }

If your code depends on globals (eg. define, require, jQuery) or browser objects (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*/ or /*jslint amd:true*/.

✔ Here we declare the document and define globals:

  1. /*global define document*/
  2. define([], function() {
  3.     // Use document
  4.     document.createElement('div');
  5. });

The 'browser:true' option

✔ Alternatively, you can use JSLint's browser:true option, which predefines many globals for you (including document, window, setTimeout, etc).

  1. /*global define*/
  2. /*jslint browser:true*/
  3. define([], function() {
  4.     // Use document
  5.     document.createElement('div');
  6. });

However, browser:true should be used with caution. Here is the full list of globals that it defines:

addEventListener, blur, clearInterval, clearTimeout, close, closed, defaultStatus, document, event, focus,
frames, getComputedStyle, history, Image, length, location, moveBy, moveTo, name, navigator, onbeforeunload,
onblur, onerror, onfocus, onload, onresize, onunload, open, opener, Option, parent, print, removeEventListener,
resizeBy, resizeTo, screen, scroll, scrollBy, scrollTo, setInterval, setTimeout, status, top, window, XMLHttpRequest

Among these are several words that commonly serve as variable and parameter names in JavaScript code: parent, open, closed, event, name, location, etc. If you accidentally reference one of these globals, it can allow a real coding problem to slip past JSLint undetected.

✘ For example, here is a coding error hidden by browser:true.

  1. /*jslint browser:true*/
  2. function handleEvent(e) {
  3.     console.log("Got an event: " + event); // Wrong! We intended to reference 'e' here, but the validator will not warn us.
  4. }

The 'amd:true' option

Instead of writing out /*global define require*/ you can use the shortcut option /*jslint amd:true*/. This is a good idea, please use it when writing new code.

The 'node:true' option

When writing a Node.js module (for example, server-side code for the Orion Node server), use the /*jslint node:true*/ option. It will predefine globals like process, __dirname, etc.

Internal data

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.

Arrays

Testing if a value is an array

✔ Do this:

  1. Array.isArray(value);

Looping through elements

✔ Use Array.prototype.forEach:

  1. array.forEach(function(item) {
  2.     // ...
  3. });

✘ 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.

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

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:

  1. for (var i in array) {
  2.     // ...
  3. }

Searching for an element with strict-equals

✔ Use Array.prototype.indexOf:

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

✘ Don't do a manual search:

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

Applying a transformation to an array

✔ Use Array.prototype.map.

Deciding if an element satisfies a condition

✔ Use Array.prototype.some.

Also note that some's early-exit behavior (when the callback returns true) can be used to replace a manual for .. break search for an element. For example:

✘ Rather than write this search manually:

  1. var array = [1, 3, 5, 8, 11];
  2. var found;
  3. for (var i=0; i < array.length; i++) {
  4.     var element = array[i];
  5.     if (element % 2 === 0) {
  6.         found = element;
  7.         break;
  8.     }
  9. }
  10. console.log("Found an even number: " + found);

✔ … We can encapsulate it into a some:

  1. var array = [1, 3, 5, 8, 11];
  2. var found;
  3. array.some(function(element) {
  4.     if (element % 2 === 0) {
  5.         found = element;
  6.         return true; // stops the search
  7.     }
  8.     return false;
  9. });
  10. console.log("Found an even number: " + found);

Determining if every element satisfies a condition

✔ Use Array.prototype.every.

Removing elements that satisfy a condition

✔ Use Array.prototype.filter.

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 doesn't behave like you might expect:

  1. if (value == null) {
  2.     console.log("value is null... or is it?");
  3.     // do stuff with value
  4. }

Since undefined == null, the above code allows value to be undefined and pass the if-test, which may cause problems later on.

Testing for objectness

✔ Do this:

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

✔ This is also OK:

  1. if (value && typeof value === "object") {
  2.     // use value as an object
  3.     Object.keys(value);
  4. }

The reason that both a typeof and null (or truthiness) check is required is because typeof null === "object". We need the additional test to prevent null from sneaking into the Object.keys call and causing an error.

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":

  1. function variadic(param) {
  2.     if (typeof param === "undefined") {
  3.         console.log("Called with no arguments");
  4.     } else {
  5.         console.log("Called with 1 argument");
  6.     }
  7. }

✔ Equality comparison against undefined. This approach is also OK, with caveats (see below).

  1. function variadic(param) {
  2.     if (param === undefined) {
  3.         console.log("Called with no arguments");
  4.     } else {
  5.         console.log("Called with 1 argument");
  6.     }
  7. }

Keep in mind that undefined here is actually a reference to the global window.undefined property, not a primitive value. This means your code is vulnerable to accidental lexical closures if in-scope variable is named undefined. (But who would do such a thing? Nobody, we hope.)

Looping through an object's properties

✔ Use Object.keys:

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

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

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

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

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

Functions

Attaching a particular this-context to a function

✔ Use Function.prototype.bind:

  1. var context = { myfield: "Hello world" };
  2. var func = function() {
  3.      return this.myfield;
  4. };
  5. var boundFunction = func.bind(context);
  6. 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:

  1. function Widget() {
  2.      this.clickCount = 0;
  3.      document.getElementById("myButton").addEventListener("click", this.handleClick.bind(this)); // handleClick always gets the right "this"
  4. }
  5. Widget.protoype.handleClick = function() {
  6.      this.clickCount++;
  7.      alert("You clicked me " + this.clickCount + " times");
  8. };

Errors

Throwing an error

✔ When throwing an error, use the Error constructor:

  1. throw new Error("Something went wrong");

The reason for this is that the JS engine adds additional information to Error instances. For example, Errors have a stack property that gives a backtrace of where the error occurred. This is valuable for debugging. You can also create a custom error type that inherits from Error.

Do not throw strings or other objects directly! Without the extra info provided by Error, it can be difficult for other programmers to track down the source of an error.

✘ Do not throw non-Errors:

  1. throw "Something went wrong, but you may never see this message!";

Classes

Writing a class

The Orion approach to writing "classes" is fairly straightforward. It doesn't require any fancy libraries, just constructor functions and prototypes:

  1. function Duck(name) {
  2.     this.name = name;
  3. }
  4. Duck.prototype.greet = function() {
  5.     console.log("Quack quack, my name is " + this.name);
  6. };

The mixin() function from the orion/objects module can make your code more compact when you have a lot of properties to add to the prototype. Using mixin, the above example would look like:

  1. function Duck(name) {
  2.     this.name = name;
  3. }
  4. objects.mixin(Duck.prototype, {
  5.     greet: function() {
  6.         console.log("Quack quack, I'm a duck named " + this.name);
  7.     }
  8.     //, more properties here...
  9. });

Subclassing

To extend a class and add additional properties, we use a pattern similar to the code shown below. The subclass's constructor explicitly invokes the superclass's constructor to perform the usual Duck initialization, and then performs some extra initialization specific to the subclass. Note how Object.create is used to set up the desired prototype chain.

  1. function SeaDuck(name, diveDepth) {
  2.     Duck.call(this, name);         // explicitly invoke super constructor
  3.     this.diveDepth = diveDepth;
  4. }
  5. SeaDuck.prototype = Object.create(Duck.prototype); // extend super prototype
  6. SeaDuck.prototype.dive = function() {
  7.     console.log(this.name + " dived to a depth of " + this.diveDepth);
  8. ;

In older code, you may see new used to extend the superclass's prototype. This is an abuse of constructors, and creates a bogus instance of the superclass just to achieve the desired side effect of setting the prototype. Always use Object.create instead. If you have to support non-ES5 browsers, write a beget utility or a shim for Object.create.

✘ Don't use new to extend a superclass's prototype object.

  1. SeaDuck.prototype = new Duck();

Overriding methods

To override a method, we just add a method to the subclass's prototype having the same name as the superclass method:

  1. // overrides Duck.protoype.greet
  2. SeaDuck.prototype.greet = function() {
  3.     console.log("Avast, I'm a sea duck named " + this.name);
  4. };

JavaScript's prototypal inheritance also allows you to override methods on a per-instance basis, rather than per-class (although readers familiar with Java-style class models may find this surprising).

To invoke the superclass's implementation of an overridden method (the equivalent of the Java super keyword), you need to explicitly call it using SuperConstructor.prototype.methodName.call(this);:

  1. SeaDuck.prototype.greet = function() {
  2.     Duck.prototype.greet.call(this);       // prints "Quack quack, I'm a duck named [whatever]"
  3.     console.log("Just kidding, I'm a sea duck. Arrr");
  4. };

Creating a dictionary

To create a dictionary (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:

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

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

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

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 either:

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

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.

  1. define(["orion/EventTarget"], function(EventTarget) {
  2.     var myobject = {};
  3.     EventTarget.attach(myobject);
  4.  
  5.     myobject.addEventListener("coolevent", function(event) {
  6.         console.log("Listener received an event");
  7.     });
  8.     myobject.dispatchEvent({ type: "coolevent" }); // Every event must have a type. Other fields optional.
  9. });

To add event behavior to a class, call attach inside your constructor:

  1. function Duck(name) {
  2.     this.name = name;
  3.     EventTarget.attach(this);
  4. }
  5. new Duck("Howard").dispatchEvent({ type: "quack" });

Asynchronous operations

Use orion/Deferred to manage asynchronous operations.

XMLHttpRequest and Ajax

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

  1. define["orion/xhr"], function(xhr) {
  2.     xhr("GET", "/index.html").then(
  3.         function(xhrResult) {
  4.             console.log("Got response: " + xhrResult.response);
  5.         },
  6.         function(xhrResult) {
  7.             console.log("An error occurred: " + xhrResult.error);
  8.         });
  9. });

The returned promise resolves when it receives a response with a 2xx or 3xx status code, and rejects when the status code is 4xx or 5xx. It also rejects on network errors or timeouts. If you need finer-grained control over an Ajax request, xhr will not be helpful: use a plain old XMLHttpRequest.

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:

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

✘ 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.


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).

JS Documentation

All JavaScript API uses JSDoc syntax (currently version 2). Orion code is written as self-contained AMD modules that don't expose global objects. So rather than tagging the actual objects with documentation, we tend to use JSDoc tags to construct virtual "classes" corresponding to a module's exported API. These virtual tags can appear almost anywhere in the source code, but it's best to keep them close to the functions that they represent.

The main 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
@class
Indicates that this comment represents a class definition. The text of the @class tag becomes the class description.
@description
A detailed description of what this class, function, or field does. The first sentence will be used as a one-line summary of the class constructor. If @description is not given explicitly, the header of the comment is used.

See also the complete JSDoc tag reference.

Documenting a class

JSDoc doesn't distinguish between class documentation and constructor documentation: they both go into a single comment block. So to document a class, we have a comment containing @name, @class, and @description tags, plus all the normal method tags (@param and so on).

  1. /**
  2.  * @name orion.MyClass
  3.  * @class Class header summary of MyClass.
  4.  * @description One-line summary of MyClass.
  5.  * Longer description of MyClass, which can include code examples (in <code> tags),
  6.  * HTML and links to other docs (using @see). This also serves as documentation
  7.  * for the constructor function.
  8.  *
  9.  * @param {String} param1 The widget to frob.
  10.  * @param {Boolean} [param2=false] Whether the flux capacitor should be quuxed.
  11.  */
  12. function MyClass(param1, param2) {
  13.     // ...
  14. }

Methods

Because JavaScript's prototypal inheritance doesn't quite fit the class-oriented model of JSDoc, there's a few different approaches for documenting methods, depending on how your "class" is built. The general pattern for a method uses the tags:

@name
Gives name of this method. This can be fully-qualified (in which case . indicates static membership, and # indicates instance membership).
@function
Tells JSDoc we're documenting a function (otherwise it will assume a field).
@param {Type} paramName comment
Documents a required parameter. May appear multiple times.
@param {Type} [optParamName=defaultValue] comment
Documents an optional parameter, and its default value. May appear multiple times.
@returns {Type} comment
Documents the function's return value.

You may also come across these tags in the source. Note that both of these tags are made redundant by proper use of @name and @function:

@memberOf
Writing @name orion.MyClass#foo is equivalent to combining @name foo and @memberOf orion.MyClass.prototype
@methodOf
Equivalent to combining @memberOf and @function.
Per-instance method

If you're injecting methods into each instance of your class, a method doc comment looks like this:

  1. function ServiceTracker() {
  2.     /**
  3.      * Returns service references to the services that are being tracked.
  4.      * @name orion.ServiceTracker#getServiceReferences
  5.      * @function
  6.      * @returns {orion.serviceregistry.ServiceReference[]} References to all services that
  7.      * are being tracked by this ServiceTracker.
  8.      */
  9.     this.getServiceReferences = function() {
  10.         // ...
  11.     };
  12. }
Prototype method (mixin lends)

This is for when your class mixes in (or directly uses) an object literal for its prototype. We tag the object with @lends, causing JSDoc to add all the literal's fields into the class prototype. Our method docs can then omit the @function and @name tags, because JSDoc already knows the containing class and the type of member.

  1. function ServiceTracker() {
  2.     // ...
  3. }
  4. ServiceTracker.prototype = /** @lends orion.MyClass.prototype */ {
  5.     /**
  6.      * Called to customize a service object being added to this ServiceTracker.
  7.      * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
  8.      * @returns {Object} The service object to be tracked for the given service reference.
  9.      */
  10.     addingService: function(serviceRef) {
  11.         // ...
  12.     }
  13.     //, ... Additional prototype methods can go here.
  14. };
Prototype method (assignment)

When assigning methods directly to our class's prototype, JSDoc falls over somewhat, and can't infer the containment or the member type. So we need to explicitly place each method into the class using @name, and function-ify it with @function:

  1. function ServiceTracker() {
  2. }
  3. /**
  4.  * Called to customize a service object being added to this ServiceTracker.
  5.  * @name orion.ServiceTracker#addingService
  6.  * @function
  7.  * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
  8.  * @returns {Object} The service object to be tracked for the given service reference.
  9.  */
  10. ServiceTracker.prototype.addingService = function(serviceRef) {
  11.     // ...
  12. };
Static methods

To indicate that a method is static, make it a member of the class itself, rather than the class's prototype:

  1. /**
  2.  * @name orion.Deferred.all
  3.  * @function
  4.  */
  5.  function all(promises) {
  6.      // ...
  7.  }

JSDoc also has a @static tag, but you probably won't need it.

Types

One helpful feature of JSDoc is types, which can appear inside the curly braces of a @param or @return tag, and a few other places as well. When the JSDoc parser recognizes a reference to a type, it turns it into a clickable hyperlink in the HTML documentation, which is very nice.

A type can be a fully-qualified class name, or a global JavaScript object like Boolean, String, Function, etc. The Orion source generally uses the capitalized object wrapper names to refer to primitive types: for example we say String rather than string, Boolean rather than boolean, and so on.

JSDoc also understands array types: append [] after the name of any type to indicate that it's an array.

 /**
  * @param {orion.Promise[]} promises
  */

To indicate that a param or return type takes one of a set of types, join the types with a |:

 /**
  * @param {String|Element} elementOrDomId
  */

When a type can be "anything", we represent it as {Object}.

Fields

JSLint

While most validation problems raised by JSLint are helpful, there are some situations where they need to be ignored. In these cases, add a /*jslint ... */ comment block near the top of your code that turns off the problems. A sample jslint block might look like:

/*jslint forin:true regexp:false sub:true*/

Here's a list of common warnings and how to deal with them:

["foo"] is better written in dot notation.
This warning is raised when code uses the [] operator to access a constant property name. It's good practice to use the . operator instead. When changing the code is unfeasible (for example, when dealing with output from the Orion string externalization plugin), use sub:true to disable this warning.
Bad line breaking before {something}
Use laxbreak:true to disable this warning. Be very careful breaking around a return statement, however: JavaScript's semicolon insertion can produce unexpected results.
Insecure '^'.
This warning is raised on regular expressions that use a negated character set like [^A-Z]. Use regexp:false to disable the warning.
The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype
While this is generally good advice, sometimes an unfiltered loop is desirable. Use forin:true to disable this warning.
Statement body should be inside '{ }' braces.
JSLint raises this warning when you write a block-style statement (like if, else, for, while) without enclosing the statements inside the block in curly braces { }. There's no way to disable this warning, so it's best to just add the curly braces and carry on.
'{variable}' is already defined.
This warning is raised when you define the same variable more than once within a function. Usually this results from a misunderstanding of JavaScript's scoping rules. In JavaScript, var is scoped to the enclosing function, not the enclosing block. When two blocks define the same variable, it is redundant and may mislead readers as to the underlying language semantics. To eliminate this warning (and make your code clearer), move the variable definition outside the blocks.

✘ Don't redefine a variable inside a block:

  1. if (condition) {
  2.     var x = 'foo';
  3. } else {
  4.     var x = 'bar'; // 'x' is already defined
  5. }

✔ Define the variable outside the block instead:

  1. var x;
  2. if (condition) {
  3.     x = 'foo';
  4. } else {
  5.     x = 'bar';
  6. }

It's OK to define a variable inside a block if the variable is not referenced anywhere else in the function.

JavaScript Formatting

  • The Orion codebase isn't totally consistent in its formatting style. When changing a file, try to maintain the same formatting style that the rest of the file uses.
  • Please, do not use the JavaScript code formatter provided by Eclipse's JavaScript Development Tools (JSDT).
  • Feel free to fix sloppy or unclear formatting when you find it, but please keep this work in a separate commit from any functional changes.

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.

HTML Fragments and Templates

When writing a visual component, it's often convenient to define a fragment of HTML that serves as the component's template. There are several ways to go about this. This section discusses when you might want to use the different approaches, and what our long term direction is.

Where is the fragment defined?

A fragment is a little chunk of HTML that describes a component. There are a few choices in the mechanics of where a fragment is defined.

  • A fragment could be defined in js code as a string. Consider this snippet from the dialog prototype:
  1. Dialog.prototype.TEMPLATE = '' +               
  2.   '<div class="dialog" role="dialog">' + //$NON-NLS-0$
  3.         '<div class="dialogTitle layoutBlock"><span id="title" class="dialogTitleText layoutLeft"></span></div>' + //$NON-NLS-0$
  4.         '<div class="layoutBlock"><hr/></div>' + //$NON-NLS-0$
  5.         '<div id="dialogContent" class="dialogContent layoutBlock"></div>' + //$NON-NLS-1$ //$NON-NLS-0$
  6.         '<div id="buttons" class="dialogButtons"></div>' + //$NON-NLS-1$ //$NON-NLS-0$
  7.   '</div>'; //$NON-NLS-0$

An advantage of this approach when using Orion to edit fragments is keeping the fragment close to the JavaScript code that is manipulating it or making assumptions about its structure. This is, of course, a tooling issue (not having split editors, or popping up references, etc.) The disadvantage is the noise created by concatenation, $NON-NLS markings, etc. This kind of code also tempts the developer to directly concatenate NLS message strings into the content, and that is a security risk (described later).

  • A fragment can be defined in an HTML file that is brought into the JavaScript module via a requirejs text! module. For example, the common page footer is defined in a footer.html file like this:
  1. <footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo">
  2.         <!-- Requires some js code to bind the NLS variables  -->
  3.         <div class="footerBlock">${Orion is in Beta. Please try it out but BEWARE your data may be lost.}</div>
  4.         <div class="footerRightBlock">
  5.                 <a href="http://wiki.eclipse.org/Orion/FAQ" target="_blank">${FAQ}</a>
  6.                 <a href="https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Orion&version=1.0" target="_blank">${Report a Bug}</a>|
  7.                 <a href="http://www.eclipse.org/legal/privacy.php" target="_blank">${Privacy Policy}</a>|
  8.                 <a href="http://www.eclipse.org/legal/termsofuse.php" target="_blank">${Terms of Use}</a>|
  9.                 <a href="http://www.eclipse.org/legal/copyright.php" target="_blank">${Copyright Agent}</a>
  10.         </div>
  11. </footer>

Javascript code would refer to the fragment by the requireJS module name.

  1. define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/footer.html'],
  2.   function(messages, require, FooterFragment) {
  3.  
  4.     // excerpt from common page code
  5.     var footer = document.getElementById("footer"); //$NON-NLS-0$
  6.     if (footer) {
  7.         footer.innerHTML = FooterFragment;
  8.     }
  9.   });

A fragment can also be defined inside a template element. Orion provides a shim for the draft HTML Templates API that lets you define template elements. Templates are a promising approach for the following reasons:

    • If you use the fragment many times on a page, using a template means it will be parsed only once.
    • Once parsed, you can make common, dynamic modifications to the template (binding variable values, etc.) only once.
    • The DOM structure behind a template is not associated with the document until a node is actually cloned. This means that if the template references network resources (such as an image URL), there will be no attempt to download the image until the template is actually cloned into a node and placed in the document.

If we used a template for the footer fragment, it would look like this instead. (Using a template for a footer that appears once is not necessarily a good idea, but this is shown to illustrate the difference in approach:)

  1. <template id="footerTemplate">
  2.    <footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo">
  3.         <!-- Requires some js code to bind the NLS variables  -->
  4.         <div class="footerBlock">${Orion is in Beta. Please try it out but BEWARE your data may be lost.}</div>
  5.         <div class="footerRightBlock">
  6.                 <a href="http://wiki.eclipse.org/Orion/FAQ" target="_blank">${FAQ}</a>
  7.                 <a href="https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Orion&version=1.0" target="_blank">${Report a Bug}</a>|
  8.                 <a href="http://www.eclipse.org/legal/privacy.php" target="_blank">${Privacy Policy}</a>|
  9.                 <a href="http://www.eclipse.org/legal/termsofuse.php" target="_blank">${Terms of Use}</a>|
  10.                 <a href="http://www.eclipse.org/legal/copyright.php" target="_blank">${Copyright Agent}</a>
  11.         </div>
  12.    </footer>
  13. </template>

The JavaScript would look like this:

  1. define(['i18n!orion/nls/messages', 'require', 'orion/HTMLTemplates-shim' 'template!orion/banner/footer.html'],  // note use of template! not text!
  2.   function(messages, require) {
  3.  
  4.     var template = document.getElementById("footerTemplate");
  5.     var footer = document.getElementById("footer"); //$NON-NLS-0$
  6.     if (footer) {
  7.         footer.appendChild(template.content.cloneNode());
  8.     }
  9.   });

The example above assumes that we have a requirejs plugin for parsing html files and building a template element. (Bug 395402). Until we have such a plugin, the template would have to be marked up in the page itself.

Inserting text fragments

As mentioned above, a template element is preferred for fragments repeated many times, or to delay/reduce the network traffic associated with associating a fragment with a document. However, our tooling for letting library modules bring in templates is not complete here. If you are using text fragments in library modules, or you have a "one-off" fragment, it is reasonable to insert the fragment into the DOM without using a template element. Suppose you have a text fragment you want to insert into the document. (You could have created this text fragment in the JavaScript as a string or imported as a text!myFragment module. It doesn't matter.)

To get it into the document you have several approaches:

innerHTML

If you have a node that is exclusive to the fragment, you can use the innerHTML of the node. The original footer example showed this.

  1. define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/footer.html'],
  2.   function(messages, require, FooterTemplate) {
  3.  
  4.     // excerpt from common page code
  5.     var footer = document.getElementById("footer"); //$NON-NLS-0$
  6.     if (footer) {
  7.         footer.innerHTML = FooterTemplate;
  8.     }
  9.   });

Of course, if there were any other nodes inside the footer DOM node, they would be removed.

createContextualFragment

Another approach, useful when you are adding a fragment to a parent and don't want to remove other child nodes, is to create a contextual fragment and then add it into the DOM as you would any other node. For example, this code adds a banner fragment as the first child of the document body.

  1. define(['i18n!orion/nls/messages', 'require', 'text!orion/banner/banner.html'],
  2.   function(messages, require, BannerFragment) {
  3.       var range = document.createRange();
  4.       var parent = document.body;
  5.       range.selectNode(parent);
  6.       var headerFragment = range.createContextualFragment(BannerFragment);
  7.  
  8.       if (parent.firstChild) {
  9.           parent.insertBefore(headerFragment, parent.firstChild);
  10.       } else {
  11.           parent.appendChild(headerFragment);
  12.       }
  13.   });

Security and Data Binding

Often, you'll want to bind some data to a fragment. For example, you might want to describe some text using its i18n/NLS message key name, which comes from a plug-in. Or perhaps the text in the template depends on some JSON property on the server. Regardless of how you created your fragment, you'll need to ensure that these strings are not bound directly as text in a fragment. They should be inserted as text nodes in the DOM. This restriction is necessary so that plugins or other external sources cannot insert arbitrary HTML into the page.

Do not concatenate variable strings into a fragment and insert into the document. Instead, a data binding approach should be used to refer to the values by their key. Strings can be bound to a fragment using the processTextNodes function in 'orion/webui/littlelib'. This utility allows you to refer to the strings in your template using variables, and DOM API will be used later to bind your string to the text node in the HTML. You may have noticed in the Orion footer example, we use ${variable} syntax in the markup. The user of such a fragment is responsible for providing the correct data binding object for processing the DOM elements.

  1. define(['i18n!orion/nls/messages', 'require', 'orion/webui/littlelib','text!orion/banner/footer.html'],
  2.   function(messages, require, lib, FooterTemplate) {
  3.  
  4.     // excerpt from common page code
  5.     var footer = lib.node("footer"); //$NON-NLS-0$
  6.     if (footer) {
  7.         footer.innerHTML = FooterTemplate;
  8.         // do the i18n string substitutions
  9.         lib.processTextNodes(footer, messages);
  10.     }
  11.   });

This data binding approach can be used for any object, not just NLS message objects. When the DOM tree text nodes are processed, any nodes that have the ${variable} syntax will be considered to describe a property name. The text nodes containing the variables will be replaced with nodes containing matching property value found in the bound object. If no matching property is found, the original text remains.

CSS

We use the Orion editor to author .css files. Orion provides a built-in CSSLint validator which flags potential problems in your stylesheets.

CSS file structure

For each Orion page {page}.html, there is generally an associated {page}.css file in the same folder. The page CSS is for that page only, and should not be reused. Any reusable styling for common visual components should go in the bundles/org.eclipse.orion.client.ui/web/css/ folder.

Any stylesheets required by your page CSS should be loaded using @import. These @imports can be inlined by a CSS optimizer like RequireJS's. Do not @import external stylesheets (http://...), as those cannot be inlined.

Style tips

Use DOM structure selectors with caution

CSS provides a number of powerful selectors that act upon the DOM structure, like the type, descendent, and child selectors.

While these can be helpful, their overuse produces a page whose style is brittle and tightly coupled to the DOM structure, such that simple refactorings (adding a wrapper DIV around child elements, for example) completely break the page.

✘ A rule with many DOM selectors is brittle:

  1. .mytable > div > div > label > span {
  2.     font-weight: bold;
  3. }

✔ Something like this is better:

  1. .mytable span.emphasis {
  2.     font-weight: bold;
  3. }

Don't use unqualified tag selectors

The type selector matches any element with the given HTML tag name. In complex pages, these are prone to matching too much. They also hinder refactoring, as your stylesheet cannot safely be made reusable, and a reader of either the HTML or CSS has a difficult time determining just which elements you intend to match. Instead, consider a class selector.

✘ Don't use selectors that match an entire class name:

  1. div {
  2.     background-color: red;
  3. }

✔ Instead, create a class selector that targets only the elements you want:

  1. div.myclass {
  2.     background-color: red;
  3. }

The html and body elements are exceptions.

Avoid the ID selector

While necessary in the DOM, IDs should be avoided in stylesheets. IDs are the DOM equivalent of singletons, and targeting them prevents your styles from being reused on other elements. IDs should be regarded as an internal property of the HTML (or JavaScript), not an exported symbol to be styled. Instead of an ID, apply a class name to the DOM element you're trying to style, and use a class selector.

✘ Instead of this:

  1. #content-title {
  2.     text-shadow: white 0 1px 2px;
  3. }

✔ …Use a class:

  1. .content-title {
  2.     text-shadow: white 0 1px 2px;
  3. }

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:

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

Back to the top