Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Orion/Coding conventions
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
- 1 JavaScript
- 1.1 Compatibility
- 1.2 General Guidelines
- 1.3 Modules
- 1.4 Globals
- 1.5 Internal data
- 1.6 Arrays
- 1.7 Objects
- 1.8 Functions
- 1.9 Creating a dictionary
- 1.10 Writing an event emitter
- 1.11 Asynchronous operations
- 1.12 XMLHttpRequest and Ajax
- 1.13 URLs
- 1.14 JS Documentation
- 1.15 Library Objects
- 1.16 Services
- 1.17 JSON
- 2 HTML
- 3 CSS
- 4 Java
- 5 Copyrights
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
.
- Use only one module per file.
- If your code depends on other modules or files, that relationship should be expressed as AMD dependencies (see below).
✔ This module depends on Deferred
and xhr
:
define(['orion/Deferred', 'orion/xhr'], function(Deferred, xhr) { // Use Deferred and xhr here. });
- Use the UMD authoring pattern if your module might need to run in non-AMD environments (like CommonJS modules or plain <script> tags).
- See
Deferred.js
for an example of a file written using UMD. - In the no-module case, your code should place its exports inside the
orion
property of the root or global object.
- See
Globals
Your code should not export any* globals. Pay close attention to the problem markers in the Orion editor. Implied globals will be flagged as an error.
✘ Code that creates an implicit global variable. Don't do this.
for (i = 0; i < 10; i++) { // ERROR: implied global `i` console.log("Hello world"); }
- If your code depends on globals (eg.
define
,require
,jQuery
) or 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*/
.
✔ Here we declare the document
and define
globals:
/*global define document*/ define([], function() { // Use document document.createElement('div'); });
- * Shims are an exception to the "no globals" rule. See
URL-shim.js
for an example.
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:
Array.isArray(value);
Looping through elements
✔ Use Array.prototype.forEach:
array.forEach(function(item) { // ... });
✘ Don't use a loop variable. It is more prone to typing errors, and hinders refactoring by polluting the containing function's scope with i
.
for (var i=0; i < array.length; i++) { // ... }
Having said that, a typical for
loop will perform faster than the forEach
function. Performance-critical code may need to use for
, but it's best to use forEach
until you've demonstrated that it's too slow for your needs.
✘ Don't treat an array like an object. Never do this:
for (var i in array) { // ... }
Searching for an element with strict-equals
✔ Use Array.prototype.indexOf
var index = array.indexOf(item); if (index !== -1) { console.log("Found it!"); }
✘ Don't do a manual search:
for (var i=0; i < array.length; i++) { if (array[i] === item) { console.log("Found it!"); break; } }
Objects
Testing equality
In comparisons, always use the strict equality ===
and strict inequality !==
operators. This makes your code's intent clear to other readers. While the nonstrict equality ==
may seem like a nice shortcut, it performs type conversions that can be surprising.
✘ For example, a nonstrict comparison against null
doesn't behave like you might expect:
if (value == null) { console.log("value is null... or is it?"); // do stuff with value }
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:
if (value !== null && typeof value === "object") { // use value as an object Object.keys(value); }
The null check is required, because otherwise null
could sneak into the Object.keys call and cause an error. (Recall that typeof null === "object"
).
Testing for definedness
When writing a function that accepts a variable number of arguments (optional parameters), we sometimes want to check if a parameter is not undefined
.
✔ Compare the parameter's type against "undefined"
:
function variadic(param) { if (typeof param === "undefined") { console.log("Called with no arguments"); } else { console.log("Called with 1 argument"); } }
But why not just compare against undefined
? Well, undefined
is actually a reference to the global window.undefined
property, not a primitive value. (Older JS engines would actually allow undefined
to be overwritten, although ES5 has thankfully prevented that.) Because undefined
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 undefined
:
✘ A broken comparison against undefined
:
function() { var undefined = true; // Never do this function wrong(param) { if (param === undefined) { console.log("param is undefined... or is it?"); } } wrong(); // does nothing wrong(true); // prints "param is undefined... or is it?" }
Looping through an object's properties
✔ Use Object.keys:
Object.keys(object).forEach(function(property) { console.log('property: ' + property); })
✘ Don't do it this way. While the code is technically correct, Object.keys
is clearer and more concise:
for (var property in object) { if (Object.prototype.hasOwnProperty.call(object, property)) { console.log('property: ' + property); } }
✘ Don't do this. It iterates through all properties in object
's prototype chain, which is almost never what you want:
for (var property in object) { console.log('property: ' + property); }
Functions
Attaching a particular this
-context to a function
✔ Use Function.prototype.bind:
var context = { myfield: "Hello world" }; var func = function() { return this.myfield; }; var boundFunction = func.bind(context); boundFunction(); // Hello world
bind
is especially useful when dealing with event listeners, which need to be passed around to addEventListener methods but retain their this
-context:
function Widget() { this.clickCount = 0; document.getElementById("myButton").addEventListener("click", this.handleClick.bind(this)); // handleClick always gets the right "this" } Widget.protoype.handleClick = function() { this.clickCount++; alert("You clicked me " + this.clickCount + " times"); };
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:
if (Object.prototype.hasOwnProperty.call(map, key)) { console.log("The key " + key + " was found!" }
✘ Don't use a truthiness check to determine key presence:
if (map[key]) { console.log("The key " + key + " was found!"); }
The truthiness check will give false negatives if the key's value happens to be falsey (like 0
or ""
).
✘ But even correctly handling falsey values won't save the []
operator. Don't do this either:
if (typeof map[key] !== "undefined") { console.log("The key " + key + " was found!"); }
While the code may appear correct, in many JS runtimes map[key]
will return a reference to the map's prototype object when key
equals the nonstandard property name __proto__
. This will again give false positives. Rather than try to work around the __proto__ special case, it's best to avoid the []
operator entirely and use hasOwnProperty.
Writing an event emitter
Use the orion/EventTarget
mixin to inject event behavior into an object. After calling attach()
, your object implements the EventTarget DOM3 interface, and can dispatch events to listeners.
define(["orion/EventTarget"], function(EventTarget) { var myobject = {}; EventTarget.attach(myobject); myobject.addEventListener("coolevent", function(event) { console.log("Listener received an event"); }); myobject.dispatchEvent({ type: "coolevent" }); // Every event must have a type. Other fields optional. });
To add event behavior to a class, call attach
inside your constructor:
function Duck(name) { this.name = name; EventTarget.attach(this); } new Duck("Howard").dispatchEvent({ type: "quack" });
Asynchronous operations
Use orion/Deferred
to manage asynchronous operations. Its API is documented here.
XMLHttpRequest and Ajax
In most cases it's convenient to use orion/xhr
, which wraps the browser's native XMLHttpRequest into a promise. The xhr API is documented here.
xhr("GET", "/index.html").then( function(xhrResult) { console.log("Got response: " + xhrResult.response); }, function(xhrResult) { console.log("An error occurred: " + xhrResult.error); });
URLs
Orion provides a shim for the W3C URL API. Load the shim in your module, and then call the window.URL
constructor. You should always use the URL methods, rather than concatenate and encode strings by yourself.
✔ Do this:
define(["orion/URL-shim"], function() { var url = new URL("http://foo.com"); url.query.set("param1", "first value"); url.query.set("param2", "second value"); var myurl = url.href; // http://foo.com/?param1=first%20value¶m2=second%20value });
✘ Don't do this:
var myurl = "http://foo.com/?param1=" + encodeURIComponent("first value") + "&" + encodeURIComponent("second value");
Consider also using the orion/URITemplate
module, which implements the URI Template spec.
JS Documentation
All JavaScript API uses 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 they represent.
The principal 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.
References:
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).
/** * @name orion.MyClass * @class Class header summary of MyClass. * @description One-line summary of MyClass. * 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 * for the constructor function. * * @param {String} param1 The widget to frob. * @param {Boolean} [param2=false] Whether the flux capacitor should be quuxed. */ function MyClass(param1, param2) { // ... }
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 the fully-qualified name of this method. Use
.
to indicate a static method,#
to indicate an instance method. - @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. They are made redundant by proper use of @name and @function, and are best avoided:
- @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, your doc comment should look something like this.
function ServiceTracker() { /** * Returns service references to the services that are being tracked. * @name orion.ServiceTracker#getServiceReferences * @function * @returns {orion.serviceregistry.ServiceReference[]} References to all services that * are being tracked by this ServiceTracker. */ this.getServiceReferences = function() { // ... }; }
Prototype method (mixin lends)
When your class uses an object literal for its prototype, you can tag the object with @lends
to tell JSDoc to add all the methods and fields into the class prototype. In this case, we don't need the fully-qualified @name
and the @function
tags, because JSDoc already knows the containing class and the type of member.
function ServiceTracker() { // ... } MyClass.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. * @returns {Object} The service object to be tracked for the given service reference. */ addingService: function(serviceRef) { // .... } //, Additional prototype methods can go here. };
Prototype method (assignment)
Static methods
Types
Any type known to the JSDoc parser can appear inside the curly braces of a @param
or @return
tag. A type can be a fully-qualified class name, or a global JavaScript object like Boolean, String, Function and so on. 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.
Fields
Library Objects
API library objects all fall under the 'orion' namespace. For example a library object for managing bookmarks might be called orion.Bookmarks.
Services
The DOM location of service objects is not defined. All services must be accessed via the service registry using the service's symbolic id. While concrete service implementations may be found in the DOM, such object names are not API and are subject to change at any time. Service symbolic ids follow the Reverse DNS naming convention (like Java packages).
JSON
Server requests and responses are represented by default as JSON objects. Object member names use title case ("Name", "ChildrenLocation", etc).
HTML
None of the HTML in Orion is API. The existence and contents of any HTML file is subject to change at any time. If you see something in Orion's HTML files that you need in your application, make your own copy.
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. Orion provides a shim for the draft HTML Templates API that helps you use templates in an efficient way.
When using a template, you typically want to clone the node structure described by the template and insert it into your document. Document fragments are also defined by simply defining a text HTML string, either in a Java file, or in an HTML file that is referenced via requirejs text! modules. There are several different ways to get from an HTML description of UI to a node. You can use template.cloneNode(), range.createContextualFragment(htmlString), or innerHTML. (Which do we recommend and when?) Regardless of technique, ensure that strings from plug-ins (such as message strings) are not included in the template. They must be inserted via DOM API so that plugins cannot attach arbitrary HTML to the page.
NLS message strings can be bound to a template using the processTextNodes function in 'orion/webui/littlelib'. This allows you to refer to the strings in your template using variables, and DOM API will be used later to bind your string to your HTML. For example, here is the html for the orion page footer. Note the use of the ${variable} syntax in the text.
<footer id="footerContent" class="layoutBlock footerLayout" role="contentinfo"> <!-- Requires some js code to bind the NLS variables --> <div class="footerBlock">${Orion is in Beta. Please try it out but BEWARE your data may be lost.}</div> <div class="footerRightBlock"> <a href="http://wiki.eclipse.org/Orion/FAQ" target="_blank">${FAQ}</a> <a href="https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Orion&version=1.0" target="_blank">${Report a Bug}</a>| <a href="http://www.eclipse.org/legal/privacy.php" target="_blank">${Privacy Policy}</a>| <a href="http://www.eclipse.org/legal/termsofuse.php" target="_blank">${Terms of Use}</a>| <a href="http://www.eclipse.org/legal/copyright.php" target="_blank">${Copyright Agent}</a> </div> </footer>
The user of such a fragment is responsible for using the appropriate message object and binding it.
define(['i18n!orion/nls/messages', 'require', 'orion/webui/littlelib','text!orion/banner/footer.html'], function(messages, require, lib, FooterTemplate) { // excerpt from common page code var footer = lib.node("footer"); //$NON-NLS-0$ if (footer) { footer.innerHTML = FooterTemplate; // do the i18n string substitutions lib.processTextNodes(footer, messages); }
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:
.mytable > div > div > label > span { font-weight: bold; }
✔ Something like this is better:
.mytable span.emphasis { font-weight: bold; }
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:
div { background-color: red; }
✔ Instead, create a class selector that targets only the elements you want:
div.myclass { background-color: red; }
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:
#content-title { text-shadow: white 0 1px 2px; }
✔ …Use a class:
.content-title { text-shadow: white 0 1px 2px; }
Java
Java code on the server follows the standard Eclipse Naming Conventions. The project short name is 'orion', so all server bundles and packages start with org.eclipse.orion.
Provisional API packages use the x-internal or x-friends manifest attribute, and do not have internal as a segment in the package name. Use of PDE API tools is highly recommended to ensure you are using supported API.
Copyrights
The Orion server source code is licensed under the Eclipse Public License. The standard Eclipse copyright header should be used for these source files.
The Orion client code (HTML, CSS, JavaScript), is licensed under both the Eclipse Public License and the Eclipse Distribution License. Here is a sample copyright header to use in JavaScript source files:
/******************************************************************************* * Copyright (c) <date> <contributor name> and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). * * Contributors: <contributor name> - initial API and implementation ******************************************************************************/