UIZE JavaScript Framework

Javascript Code Conventions

1. Introduction

This document outlines code conventions for the JavaScript language, recommended for application development using the UIZE JavaScript Framework.

As much as possible, detailed rationales are provided along with examples. The code conventions recommended in this document are designed to balance various interests, including code readability, code performance, and code size, amongst others.

2. Syntax

2.1. Always Use Single Quotes For String Literals

2.1.1. A Sensible Convention

Being a language that is used most often (yet not always) in the context of a Web browser, and being that JavaScript is often used to construct HTML snippets for output into a document, it is a sensible convention to always use single quotes for string literals in JavaScript code. This allows HTML tag attributes, in HTML that is being constructed by the JavaScript, to use double quotes - without having to worry about escaping them.

EXAMPLE

function getImgTagHtml (url,width,height) {
  return '';
}

2.1.2. Consistency For Sanity

Sticking to using the same quotes for all string literals makes it easier to refactor code, since one can do a simple text search rather than having to do a regular expression search (which is slower and requires more thought). The worst side effect of using inconsistent quotes for string literals is that a person refactoring code might assume that only one kind of quoting is used (or may be lazy), and then only finds and changes the string literals quoted in one way but misses identical strings quoted the other way, thereby breeding bugs in the code.

Now, there will come times when string literals will want to contain single quotes in their contents. To adhere to the consistency rule, these string literals should still be single quoted and the single quotes inside their contents should be escaped. Fortunately, this case doesn not arise often enough for the consistency rule to be terribly cumbersome. The alternative is to become lax about it, thereby forcing anyone doing refactoring to use regular expressions to be safe. As soon as you start having to use regular expressions for such searches, you're going to have to start escaping characters that are regular expression operators, like "(", ")", "+", "*", "?", "[", "]", "\", "^", "$", "|", ".". And what a royal pain that is!

3. Naming Conventions

3.1. Camel Case / Inner Caps

All identifiers use "Camel Case" (otherwise known as "Inner Caps").

For class names, the first letter is capitalized as well, but for all other identifiers the first letter is lower case.

EXAMPLE

MyNamespace.MyClass = Uize.Class.subclass ({
  alphastructor:function () {
    this._myPrivateInstanceProperty = 0;
  },

  instanceMethods:{
    myInstanceMethod:function () {
      ...
      ...
    }
  },

  staticMethods:{
    myStaticMethod:function () {
      ...
      ...
    }
  }
});

NO EXCEPTIONS

For identifier names - just so that there is never any confusion, doubt, or inconsistency - no exception is made for traditionally capitalized acronyms that might form parts of identifier names.

Listed below are some examples of identifiers to illustrate this point...

nodeInnerHtml
xmlHttpRequestObject
wireUi
sourceStr

By adhering to this rule, there is never any ugliness when concatenating acronyms anywhere inside identifier names.

3.2. Denoting Type

As a general rule, identifiers should be named in such a way that type should be clear from the identifier's name and/or context of use. For example, it's pretty clear from an identifier name like propertyName that it is a string, or from elementNo that it is an integer.

Hungarian notation (where a type prefix is prepended to identifier names) is generally discouraged. When it is desirable to explicitly denote type in an identifier name, code in the UIZE JavaScript Framework follows the convention of appending a type indicator such as Str (as in userInfoStr) or Obj (as in userProfileObj).

3.2.1. Strings

Wherever possible and where it is semantically correct, use the suffix "Name" (e.g. formFieldName, paramName, objectName, propertyName). It is implicit from such a suffix that the variable is a string type.

A common convention is to use the suffix "Str" (e.g. rgbColorStr).

3.2.2. Numbers

A common convention is to use the suffix "No" (e.g. elementNo) in iterator names to indicate that the identifier is an integer.

3.2.3. Booleans

Typically, Boolean identifier names contain a word (such as is, must, can, are, etc.) that indicates that the identifier is a Boolean, while still forming a readable name.

EXAMPLE

isEnabled
canBeSet
mustShow
areIdentical

Generally, as long as it is clear from its name that an identifier holds Boolean state, the name does not need a formal type denoter.

EXAMPLES

foundMatch
moveAllowed
settable
draggable

3.3. Private Identifiers

Identifiers for private and local scope properties and methods are prefixed with an underscore. This flags these identifiers for scrunching.

See the example for Camel Case / Inner Caps.

3.4. Arrays and Iteration

The convention things-thingNo-thing is used throughout the UIZE JavaScript Framework code, where "things" is an array of something, "thingNo" is an integer used in an iterator loop through the array, and "thing" is the value of the current element of the array inside the iterator implementation.

Substitute "thing" with the name of whatever type of thing you would like. For example, let's substitute "node" for "thing"...

EXAMPLE

for (var _nodeNo = -1; ++_nodeNo < _nodes.length;) {
  var _node = _nodes [_nodeNo];
  ...
}

3.5. Objects and Iteration

Similar to the convention illustrated in the Arrays and Iteration section, the convention things-thingName-thing is also used throughout the UIZE JavaScript Framework code, where "things" is an object hash, "thingName" is the key used in a for...in iterator loop through the hash, and "thing" is the value for the current key of the hash inside the iterator implementation.

Substitute "thing" with the name of whatever type of thing you would like. For example, let's substitute "widget" for "thing"...

EXAMPLE

for (var _widgetName in _widgets) {
  var _widget = _widgets [_widgetName];
  ...
}

3.6. Consistency

When you adopt a particular convention for naming something, apply that same convention to similar things. If necessary, reference back to previous code to see how you referred previously to the same kind of thing that you're currently dealing with. When you establish a language pattern in your code, it becomes easier for others to pick it up and more rapidly understand new code written by you.

3.6.1. Equivalent Names for Equivalent Things

Equivalent naming also makes your code more "portable", since code that is moved during refactoring is then more likely to be consistent / compatible in its variable naming with any new context into which it is placed. For example, whenever possible, name a local variable that will be passed as a parameter to a function the same as the name for the parameter in the function.

function myFunction1 () {
  var _date;
  myFunction2 (_date);
}

function myFunction2 (_date) {

}

This makes it easier to opt later to transplant code from a function to inline where the function was previously being called. As code changes, the number of times some set of logic is used can change. Following this convention eases the removal of a block of code into a separate function, or the replacement of the separate function's code back into a place where it was being called.

3.7. Verbose and Self-explanatory

Strive to make code self-explanatory. Ideally, the code is its own documentation. The UIZE JavaScript Framework makes extensive use of long identifier names. Private identifier names will get scrunched and become very short for the production environment, so don't worry about the impact of verbose naming on code size.

Avoid short and generic iterator names in favor of descriptive iterator names that serve to reinforce the action of the code on reading. So, for example, instead of just using i and j, use rowNo and columnNo.

Avoid arbitrary and/or obtuse abbreviation of words. So, for example, don't use bkgdClr when you could just use backgroundColor. Identifiers that are explicit and use full words emphasize their meaning in the context of the code.

3.8. Inheritance and Class Naming

A convention that is used often for UIZE subclasses is to put them under the namespace of their superclass.

So, for example, the Bar subclass of Uize.Widget is available as the static property Uize.Widget.Bar, and the Slider subclass of Uize.Widget.Bar is available as the static property Uize.Widget.Bar.Slider. So, the names used to access classes can denote their inheritance relationship. Of course, any class may, in its implementation, use multiple orthogonal helper classes, and this cannot be conveyed in its naming.

However, a module being assigned as a property on another module does not necessarily indicate an inheritance relationship, since modules can also be used as namespaces to host other modules. So, for example, the module name Uize.Data does not indicate that the Uize.Data module is a subclass of the Uize module. In fact, neither modules are class modules - the Uize module is simply serving as a namespace for the Uize.Data module. In another example, the Uize.Util.PropertyAdapter module defines a subclass of the Uize.Class module, but it's kept under the Uize.Util namespace.

3.9. Library Filenames

3.9.1. Classes and Packages

JavaScript module files that implement Classes and packages are named according to the class or package names, with the addition of the ".js" file extension.

So, for example, the Uize.Widget.Bar class is implemented in the JavaScript file Uize/Widget/Bar.js. Similarly, the Uize.Dom.Basics package is implemented in the JavaScript file Uize/Dom/Basics.js.

3.9.2. Extensions

Extensions to UIZE classes are named as properties of the class name, with the addition of the extension descriptor prefix "x", of the form [Class].x[Descriptor], where the descriptor denotes the type of functionality that is provided in the extension.

So, for example, the extension to the Uize.Color class that adds extra utility methods is named Uize.Color.xUtil.js.

4. Layout

4.1. Separating Code Sections

Major code sections and top level function blocks are separated using just a single blank line.

EXAMPLE

/*** Utility Functions ***/
  // ... ... ... ... ... ...
  // ... ... ... ... ... ...
  // ... ... ... ... ... ...

/*** Private Instance Methods ***/
  // ... ... ... ... ... ...
  // ... ... ... ... ... ...
  // ... ... ... ... ... ...

In the above example, the "Utility Functions" and "Private Instance Methods" comment headers denote major code sections.

4.2. Section Headings

Rather than creating a function and only calling it in one place in order to break your code into logical blocks, just use a section comment and indent all the code to which it applies and which belongs to that logical section.

4.3. Indentation

4.3.1. One Tab Per Scope Contents

Function implementations, conditional code blocks and any other multi-line brace-enclosed code blocks are indented using one tab.

EXAMPLES

function myFunction () {
  ...
  ...
}

if (myBoolean) {
  ...
  ...
} else {
  ...
  ...
}

try {
  ...
  ...
} catch (_error) {
  ...
  ...
}

4.3.2. One Tab Under Section Comments

When adding a comment line/header that is descriptive of a block of code that follows, indent that code one level under the comment. That way, when unfolding a document you can reveal the section comments first before revealing all the code in those sections.

If you start off writing an implementation from pseudo-code or descriptive bullet items, those bullet items may be well suited to becoming the section headers and then capture in the code's comments how the algorithm or solution was originally thought about and derived.

4.4. Open Brace At End Of Opening Line

Code in the UIZE JavaScript Framework follows the convention of placing the open brace at the end of the opening line, rather than on its own new line. This is not an uncommon convention and has the benefit of allowing more actual code to be seen in the same vertical space without substantially compromising the readability of overall form of the code, since the contents of the block is still indented and the closing brace lines up with the opening line.

EXAMPLE

function myFunction () {
  ...
  ...
}

4.5. Compound Var Statements

When multiple variables are to be assigned in the same area, they are consolidated into a single comma-separated compound var statement, where the actual assignment block is indented. This has the benefit of reducing the delivered code size, because there aren't repeated var keywords - something that is important to a language that is delivered to the client over HTTP.

EXAMPLE

var
  myVariable1 = "myValue1",
  myVariable2 = "myValue2",
  myVariable3 = "myValue3",
  myVariable4 = "myValue4",
  myVariable5 = "myValue5"
;

4.6. JSON Declarations

Following is an example of how indentation is used within the declaration of a JSON object (i.e. literal syntax for object creation).

EXAMPLE

{
  property1Name:property1Value,
  property2Name:property2Value,
  property3Name:[
    element1Value,
    element2Value,
    element3Value,
    {
      property1Name:property1Value,
      property2Name:property2Value,
      property3Name:property3Value,
    },
    [
      element1Value,
      element2Value,
      element3Value
    ]
  ],
  property4Value:{
    property1Name:property1Value,
    property2Name:property2Value,
    property3Name:property3Value,
  }
}

5. Structural

5.1. Encapsulate Wherever Possible

Don't be afraid of using nested functions, many levels of indentation, or creating large function bodies. The folding features of most modern editors and the high resolutions of modern displays makes this a breeze.

In many respects it is more ideal to encapsulate helper functions within the exact scope in which they are used, rather than polluting a scope level higher up. That way, anyone trying to discover the code will only encounter the code of an encapsulated helper function once they dig into the function that uses it and doesn't need to be confronted with it any sooner (progressive disclosure).

Additionally, the closure feature of JavaScript allows scope of a containing function to be available to a closed function, where typically one might pass lots of state parameters back and forth.

5.2. Single Return Point

When returning a value from a function, have a single return at the end of the function. This has the following benefits...

ensures that the visual structure of the code reflects the logical conditional structure of the code, something that is more pertinent with more complex conditional structures
makes it easier to start debugging a function by first of all alerting the value before it is returned, rather than having to place tracing statements in all the possible places where the code could return from the function
less of a dead end for extending the code, since in order to add any additional code that should execute before returning from the function, you would either have to add the some code before every return, or unravel all the return statements and refactor the code to use a single return anyway

INSTEAD OF...

function getAnimalNameFromCode (animalCode) {
  if (animalCode == 105693) {
    return "pig";
  } else if (animalCode == 958697) {
    return "dog";
  } else if (animalCode == 452856) {
    return "aardvark";
  } else if (animalCode == 396410) {
    return "elephant";
  } else {
    return null;
  }
}

USE...

function getAnimalNameFromCode (animalCode) {
  var animalName = null;
  if (animalCode == 105693) {
    animalName = "pig";
  } else if (animalCode == 958697) {
    animalName = "dog";
  } else if (animalCode == 452856) {
    animalName = "aardvark";
  } else if (animalCode == 396410) {
    animalName = "elephant";
  }
  return animalName;
}

5.3. Resolve Conditionals Into Expressions Whenever Possible

Avoid using cascading if...else statements when simply setting a value for a single Boolean variable. Instead, resolve the conditional logic down to a single expression using Boolean operators and the ternary conditional operator. In order to maintain readability, span the expression over multiple lines and use indentation to denote the conditional structure, in a similar manner as one would obtain visual structure when indenting a complex if...else block.

5.4. Don't Protect Unless It's Really Worthwhile

Don't put in place a lot of protection code to handle each and every conceivable misuse of your code. Don't check for a multitude bad conditions and return friendly error messages for cases that are likely to be application bugs in any event. There is simply not the room for that. Assume that the developer is using the code correctly. If not, they will have to debug using debugging software or any other means at their disposal. Only put in dedicated error handling for likely runtime scenarios that are not likely to be the result of bad application code.

6. Interfaces

6.1. Start Narrow

Make your interfaces only as wide as they need to be in order to meet the initial objectives, unless you feel really certain that a feature you expose that won't be used immediately is very likely to be used in the future. When in doubt, don't expose it. Your code can always be refactored in the future to widen the interface.

Making features public prematurely runs the risk of use of immaturely developed code that then creates backwards compatibility headaches in the future when you have a better idea of how something should be implemented. Keeping as much as possible internal and private offers the maximum freedom to refactor implementations without disrupting users of an interface.

6.2. Parameter Objects

For public methods that can accept large parameter sets, or parameter sets that seem likely to be extended in the future, accept a single parameters object as a parameter. This allows the user of the interface to set values for the parameters by name. This has many benefits...

each time a method is used in this way, the meaning of the parameters is reinforced in the mind of the user
looking at and understanding code is much easier, since the parameters used in a call are named in the call
the order of the parameter declaration is no longer important
new parameters can be added to the function without having to worry about how putting the new parameter in the "right spot" might break all existing code, since there is no longer an order
any parameter can be made optional, with commonsense defaulting when it is not set
naming of parameters is then functionally very similar to attributes of HTML tags, a model of thinking that is very familiar to Web developers and therefore to developers who approach an API that deals with dynamic Web-based application

IN DECLARATION

_class.instanceMethods ({
  myInstanceMethod:function (_params) {
    ...
    ...
    if (_params.param1Name) {
      ...
      ...
    }
    ...
    ...
  }
});

IN CALLING

myObjectInstance.myInstanceMethod ({
  param1Name:"param1Value",
  param2Name:"param2Value",
  ...
  ...
  paramNName:"paramNValue"
});

6.3. Write Classes to be Multi-instance

Avoid implementing classes in such a way that there can't be multiple instances co-existing in a fully functional manner in the same context - even if it seems unlikely that there will be multiple instances. If multiple instances can't be created in the same context, it's not a class and should be implemented instead as a package.

6.4. Don't Access Globals From a Class

When implementing a class, avoid referencing global variables in the page and thereby creating implicit (and likely to be forgotten about) dependencies between a class and its host context. Avoid creating the requirement that a particular global variable be set in a page. Instead, provide a configuration interface using static or instance properties or methods of the class and make it the burden of the page to configure the class or an instance of the class.