Difference between revisions of "JavaScript coding conventions"

From PKP Wiki
Jump to: navigation, search
(Recommend @constructor, @private and @protected because Eclipse understands them.)
m (Add link to the Google Closure project)
(28 intermediate revisions by 4 users not shown)
Line 5: Line 5:
 
Only deviations from the Google style guide will be documented here.
 
Only deviations from the Google style guide will be documented here.
  
= JsDoc Comments =
+
== JsDoc Comments ==
  
We use JsDoc comments as outlined in the Google style guide with but we name the variable name directly after the @param tag and the type without curly braces.
+
We use class/member-level JsDoc comments as outlined in the Google style guide but we replace the file-level annotations by our usual annotations (@file, @brief, etc.).
  
 
Generally we encourage inline comments. These help readers of your code to understand the logic without having to understand every line of code. It's also a good practice to write inline comments before you write code so that you first think about the logic and semantics you want to implement and then about the syntax.
 
Generally we encourage inline comments. These help readers of your code to understand the logic without having to understand every line of code. It's also a good practice to write inline comments before you write code so that you first think about the logic and semantics you want to implement and then about the syntax.
  
= Visibility =
+
For the moment being we do not use curly braces ({}) around the object declared with <code>@extends</code>. This is for better tool support. The build script will introduce the braces for compilation.
  
We use the @constructor, @protected and @private markers that Google recommends and Eclipse understands.
+
== Whitespace ==
  
We group members by visibility and class (static) vs. object (instance) membership:
+
We generally use whitespace exactly as outlined in Google's JavaScript style guide except for indentation which we do with tabs rather than spaces.
* private static class variables (start with underscore)
+
* private instance variabes (start with underscore)
+
* protected instance variables (discouraged)
+
* public static class variables or instance variables are not allowed!
+
* public static methods
+
* public methods
+
* protected static methods
+
* protected methods
+
* private static methods (start with underscore)
+
* private methods (start with underscore)
+
  
Sections are divided with a three line // style comment indicating the class member category.
+
= Object Oriented Programming in JS =
  
An underscore at the start of the name marks a class member private. You are not supposed to use such a member outside scope even if the visibility is not enforced by closure.
+
== Inheritance ==
  
Protected class members are named like public methods but they must not be used outside of the inheritance hierarchy of the current class.
+
There are many ways to implement inheritance in JavaScript. We chose our approach for flexibility, simplicity and memory/execution performance.
 +
 
 +
We are using a (pseudo-)classical approach of inheritance rather than the purely prototypical or mixin approach:
 +
// Define parent constructor.
 +
Parent = function() { ... };
 +
// Implement parent object.
 +
Parent.prototype.xyz = ...;
 +
 +
// Define child constructor.
 +
Child = function() { ... };
 +
 +
// Let Child inherit from Parent - memory efficient version avoiding constructor side effects.
 +
Temp = function() {};
 +
Temp.prototype = Parent.prototype;
 +
Child.prototype = new Temp();
 +
// + a bit of sugar to fix the constructor property and allow access to the parent prototype...
 +
 +
// Implement child object.
 +
Child.prototype.xyz = ...;
 +
 
 +
Compared with the 'purely' prototypical approach (<code>parent = {...}; F = function() {}; F.prototype = parent; child = new F;</code>):
 +
* It's easier to put in an intermediate object above the parent when we use constructors from the beginning.
 +
* It's easy to insert initialization code in the parent object if required at a later stage because we won't have to insert <code>new</code> everywhere in the code.
 +
* There's minimal overhead in using functions as parents if we make sure to not call the constructor (thereby avoiding potential side effects of the constructor and making a potentially big object) when letting a child inherit from the parent, see use of 'Temp' above.
 +
 
 +
Compared with the pure "mixin" approach (used in jQuery and jQueryUI):
 +
* Easy access to superclass method implementations via our custom <code>_parent</code> static variable.
 +
* Using <code>new</code> is usually faster than manually copying mixins to new objects in a loop.
 +
* You cannot use the <code>instanceof</code> operator with Mixins.
 +
* Mixins are less memory efficient than inheritance via prototypes.
 +
 
 +
== Cross-Cutting Concerns ==
 +
 
 +
There are certain features that are really cross-cutting concerns or that should be shared between objects independent of their inheritance hierarchy. JavaScript fortunately has very powerful mechanisms to deal with such situations.
 +
 
 +
While we do not use "mixins" to implement vertical code re-use (see inheritance above), we use them throughout to implement horizontal code re-use. Objects "declare" the aspects they implement similarly to how they declare inheritance - via a method that actually implements the mixin.
 +
 
 +
We implement cross-cutting aspects in separate objects and then introduce them into classes. We have got two mechanisms for that:
 +
# interface weaving
 +
# interceptors
 +
 
 +
=== Interfaces ===
 +
 
 +
There is no syntactical correspondence for interfaces in JavaScript but we implement them conceptually as objects and enforce them with Google's [https://developers.google.com/closure/compiler/ Closure compiler]. We have an "abstract" base object that declares the methods that go into an interface without implementing them. Then we let implementations of the interface inherit from these "abstract" objects. Please use the <code>@interface</code> and <code>@implements</code> annotations to declare interfaces.
 +
 
 +
Interfaces that share the same implementation across objects can be weaved into objects as aspects via the $.Helper.injectMixin() method which relies on our object factory to identify the right implementation of an interface and then introduces it into the target object via the mixin mechanism provided by jQuery's $.extend() function. This allows us to exchange interface implementations on runtime (e.g. for automated testing or different deployment environments) without having to change the object declarations, see the explanation of the object factory below.
 +
 
 +
If the interface implementation differs on a per-object basis so that we cannot use shared mixins then objects implement interface methods themselves.
 +
 
 +
=== Method Interception ===
 +
 
 +
We can implement method interception via the proxy that we attach to every object on instantiation. The proxy can either implement methods itself or delegate to other objects for actual interception. We'll further detail that design approach when we actually need it.
 +
 
 +
== Object Factory ==
 +
 
 +
The object factory introduces an additional indirection between client code and the objects it instantiates. Without an object factory, client code would have to be closely coupled to specific implementations of certain interfaces on object instantiation. The object factory allows us to work with logical "object names" rather than "class names". We then can control in one central place how these object names will be resolved to actual objects. The performance overhead for such an approach is minimal in JavaScript as, due to the dynamic nature of JavaScript, we can use very powerful native JavaScript functionality.
 +
 
 +
Potential use cases of such an approach are:
 +
- dynamic dependency injection (IoC)
 +
- run-time weaving of interfaces and method interceptors (AOP)
 +
- a central place to enforce restrictions and application policies on object level
 +
 
 +
This indirectly introduces more flexible possibilities for logging, security/consistency checks, dynamic patching of objects, central configuration, improved testability, working with stub implementations, etc.
 +
 
 +
== Visibility ==
 +
 
 +
All our object members are techically public.
 +
 
 +
We use the <code>@constructor</code>, <code>@protected</code> and <code>@private</code> markers that Google's JavaScript compiler understands and enforces.
 +
 
 +
We also put underscores in front of object members that are intended to be private.
 +
 
 +
Advantages over the 'closure + privileged method approach' (see e.g. Douglas Crockford, "JavaScript: The good parts") for private object members:
 +
* Reduced complexity for novice programmers who are not so familiar with the closure concept.
 +
* Approach is similar to our PHP4 compatible nomenclature for private methods in PHP.
 +
* Less probability to create memory leaks with closures, see http://code.google.com/speed/articles/optimizing-javascript.html
 +
* Better tool support (e.g. Eclipse JSDT)
 +
 
 +
 
 +
== Object vs. 'Class' members ==
 +
 
 +
There is no such thing as classes (or static methods) in JavaScript. We implement class members that do not need to be copied into each object instance (=static members) within the constructor object:
 +
SomeConstructor = function() { ... };
 +
SomeConstructor.staticMethod = function() { ... };
 +
 
 +
Thereby we make sure that on instantiation of that constructor with the <code>new</code> keyword, <code>staticMethod</code> will not be copied into the new object instance.
 +
 
 +
JavaScript doesn't have self:: or parent:: constructs. We emulate these constructs with methods automatically added to all objects retrieved from the object factory:
 +
* If you want to access a static class member you can use the following syntax:
 +
// For a static property:
 +
var myProperty = this.self('someStaticProperty');
 +
// This is like self::$someStaticProperty in PHP.
 +
 
 +
// For a static method call.
 +
var result = this.self('someStaticMethod', arg1, arg2, ...);
 +
// This is like self::someStaticMethod(arg1, arg2, ...) in PHP.
 +
* If you want to access the parent implementation of an overridden method you can use the following:
 +
// Call overridden constructor from within the child constructor:
 +
var result = this.parent(arg1, arg2, ...);
 +
// This corresponds to parent::_constructor(arg1, arg2, ...) or parent::SuperClass(arg1, arg2, ...) in PHP.
 +
 
 +
// Call overridden implementation of an instance method:
 +
var result = this.parent('someOverriddenMethod', arg1, arg2, ...);
 +
// This is like parent::someOverriddenMethod(arg1, arg2, ...) in PHP.
 +
 
 +
 
 +
Some background and credits: Several techniques have been developed to emulate parent method implementation access. We use a method combining the performance and tool support of Google Closure's goog.base() with the terse grammar of John Resig's inheritance approach (see "additional resources" below). Our object factory / object proxy allows us to do away with both, the awkwardness of the goog.base() method calls and the performance trouble, unnecessary complexity and bad tool support of John's approach. The self() method is unique to our library and allows for 'inheritance' of static methods.
 +
 
 +
== Object member ordering ==
 +
 
 +
We group object members by visibility and singleton ('static') vs. instance membership in the following order in our object definitions:
 +
# constructor (use <code>@constructor</code> annotation)
 +
# private static properties (end with underscore, use <code>@private</code> annotation)
 +
# private instance properties (end with underscore, use <code>@private</code> annotation)
 +
# protected instance properties (discouraged, better use private variables with protected accessors/mutators, use <code>@protected</code> annotation)
 +
# no public static/instance properties - use public accessors/mutators instead!
 +
# public static methods
 +
# public methods
 +
# protected static methods (use <code>@protected</code> annotation)
 +
# protected methods (use <code>@protected</code> annotation)
 +
# private static methods (end with underscore, use <code>@private</code> annotation)
 +
# private methods (end with underscore, use <code>@private</code> annotation)
 +
 
 +
Sections are divided with a three line <code>//</code> style comment indicating the class member category.
 +
 
 +
You are obviously not supposed to use a member outside its scope even if the visibility is not enforced by closure. Please run <code>lib/pkp/tools/buildjs.sh</code> on your code to make sure that you have not overlooked anything. This is also true for protected object members. While they are named like public methods they must not be used outside of the inheritance hierarchy of the current class.
 +
 
 +
= Building and Checking JS Code =
 +
 
 +
== Build Script ==
 +
 
 +
We have developed a build script that serves several purposes:
 +
* Run checkers over the code that make sure that we implement a safe subset of JavaScript. We use JSLint and the Google Linter for that purpose. The checking process will also enforce most of our coding style rules, including some additional annotation-based type checking that is not implemented in JavaScript itself.
 +
* Join, compile and minify the source. This is necessary to reduce the number of files to be loaded on a web page as well as the overall size of the JavaScript files. It has an important performance impact on page load.
 +
 
 +
The build script is in <code>lib/pkp/tools/buildjs.sh</code>. It can be run whenever changes have been made in the code to check whether these are compatible with our coding style guide. The script also makes sure that the differences from the Google coding style guide will not come in the way. Please look at the source code for further information. The script has extensive inline documentation.
 +
 
 +
NB: Currently only files in the js/classes folder will be checked against our coding style rules. External files in lib and legacy files in functions contain too many deviations from the expected coding style for such a check being useful there. We should, however, make sure that we include at least the files in functions sooner or later.
 +
 
 +
== Switch Between Minified and Original Code ==
 +
 
 +
We now have a new parameter in <code>config.inc.php</code> that is used to switch between the minified version and the full version of the code. The parameter is called ''enable_minified''.
 +
 
 +
For production environments this parameter should always be set to "On". You can only set it to on after executing the build script.
 +
 
 +
For development environments the parameter can be switched "Off" for easier in-browser debugging, e.g. via FireBug. This allows you to step through the original source code rather than having to decipher the minified code.
 +
 
 +
== Adding New Scripts Files ==
 +
 
 +
Whenever you create a new JavaScript source file you must add it to the <code>templates/common/minifiedScripts.tpl</code> file. This file has two purposes:
 +
* It will be included as a whole into the header template when <code>enable_minified</code> is switched to "Off".
 +
* It will be parsed by the build script to discover files to be minified and/or checked.
 +
 
 +
== Code Release ==
 +
 
 +
The build script must be run before we release code so that an up-to-date minified source file will be included in the released tar archive. We also should make sure that we release code with the <code>enable_minified</code> parameter already switched to "On" in <code>config.TEMPLATE.inc.php</code>.
 +
 
 +
 
 +
= The PKP JavaScript Handler Model =
 +
 
 +
== Model-View-Controller (MVC) Design ==
 +
 
 +
We implement an MVC pattern on the client side.
 +
 
 +
The guiding principles are similar to what exists in Smalltalk, Adobe Flex and other, JavaScript-based client side frameworks: The interface is made up of GUI widgets and each active widget has its own controller attached. GUI widgets can be nested. Widgets emit events when the end user interacts with them or when some other part of the system triggers them (e.g. a timeout or another widget). Events will be received and handled by the controller. The controller can access client- or server-side data and will then update the GUI in response to the end user's actions.
 +
 
 +
Note: Event-based MVC is quite different from request-based MVC (but somehow similar to AJAX MVC). In any case: please do not assume that all you know about server side MVC is necessarily transferable to the client side.
 +
 
 +
GUI widgets are HTML elements. Not all HTML elements need a handler attached but no HTML element can have more than exactly one handler attached.
 +
 
 +
Controllers are JavaScript objects that inherit from $.pkp.classes.Handler.
 +
 
 +
== Strict Encapsulation of Widgets ==
 +
 
 +
JavaScript is a very powerful language - too powerful if it's not used with care. The same is true for the jQuery library and the DOM. That's why we establish a framework that '''purposefully limits us to a small subset of JavaScript, DOM and jQuery features'''. Everything outside this subset is strongly discouraged. The framework itself makes full use of the JavaScript, DOM and jQuery power but it structures and channels how client code has access to these features to avoid some common pitfalls that come with poor code design.
 +
 
 +
Most client-side limitations result from the MVC model together with strict encapsulation of GUI widgets. This avoids undocumented dependencies between widgets which can make code maintenance a nightmare:
 +
# The handler of a GUI widget will handle all events emitted by the corresponding HTML DOM element or it's sub-elements.
 +
# No handler will handle any events that are not emitted by the GUI widget it is attached to.
 +
# We do '''not allow bubbling''' of any events outside of the GUI widget unless the event was first handled by the responsible handler and semantically re-wrapped into an event understood by the parent widget's handler!
 +
# We also '''don't allow that one handler updates the GUI of another handler'''. If one handler wants another handler to update its GUI it has to do so by triggering an event on that handler which will then be handled there.
 +
# We do '''not allow jQuery.live() or jQuery.delegate()''' event bindings! Whenever a widget is replaced by another widget its original event bindings will be completely released and the new widget will have to bind its own events.
 +
 
 +
== Event Management ==
 +
 
 +
The $.pkp.classes.Handler class implements an event dispatcher that will centrally handle and route all events emitted by its corresponding GUI widget. '''No events may ever be bound directly to a GUI widget!'''
 +
 
 +
This is necessary because we want to have a central checkpoint for events and we want to be able to garbage collect our event bindings centrally when we remove the handler. We also want to manage the context of an event handler call. The 'this' variable will always be set to the handler and not to the GUI widget that emitted the event (as is default in jQuery).
 +
 
 +
The $.pkp.classes.Handler class implements a bind() method. It binds events to the root HTML element of the GUI widget.
 +
 
 +
Whenever possible you should prefer events over callbacks! You often have a choice with jQueryUI for example. Use jQueryUI events rather than callbacks.
 +
 
 +
If you implement your own custom events then always prefix event names with 'pkp', i.e. 'pkpRemoveHandler'.
 +
 
 +
== Callbacks ==
 +
 
 +
If for some reason you really have to implement a callback rather than an event then $.pkp.classes.Handler also provides a callbackWrapper() method that can be used to correctly wrap callbacks so that they are called in the handler context rather than in the context of the calling object.
 +
 
 +
This approach should be '''used with care''' because the callbackWrapper() method creates a closure that points back to the handler. If you bind a callback returned by the callbackWrapper() method then you always have to make sure that the reference to the callback is deleted when you remove the handler from the GUI element. If you stick to the encapsulation rules, this should not be too difficult. But watch out!
 +
 
 +
== Data Items ==
 +
 
 +
If you need to keep data then please keep it in handler properties and '''do not use jQuery's data() cache'''. This will keep the data() namespace clean. The handler framework will make sure that the handler object including your data will be properly garbage collected when you remove the handler. Keep in mind that adding data to the DOM element is like defining global variables which is not encouraged.
 +
 
 +
== Binding Handlers to HTML elements ==
 +
 
 +
We implement our own jQuery plug-in to bind PKP widget handlers to HTML elements.
 +
 
 +
The binding is done via usual jQuery selectors:
 +
$('some selector').pkpHandler('$.some.handler.Name', {...handler options...});
 +
 
 +
'''This is the only bit of JavaScript we allow in Smarty templates at all!''' If you feel the need to add anything more than this to your templates then you probably have a design error somewhere.
 +
 
 +
= Standard Widget Controller Library =
 +
 
 +
Please see our dedicated [[JavaScript widget controllers|JavaScript library wiki page]] that documents all widget controllers.
 +
 
 +
= Additional Resources =
 +
 
 +
== Recommended Books ==
 +
 
 +
* Ross Harmes and Dustin Diaz, "Pro JavaScript Design Patterns"
 +
* Douglas Crockford, "JavaScript: The Good Parts"
 +
 
 +
== Recommended Articles ==
 +
 
 +
* [http://joost.zeekat.nl/constructors-considered-mildly-confusing.html Constructors considered mildly confusing], an in-depth explanation of the <code>new</code> operator together with the prototypical inheritance approach
 +
* [http://jibbering.com/faq/notes/closures/ JavaScript Closures], an in-depth explanation of closures
 +
* [http://mckoss.com/jscript/object.htm Object Oriented Programming in JavaScript], a good overview over different inheritance approaches in JavaScript - not as good as the above books, though.
 +
* [http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml Google JavaScript Style Guide], especially the parts on closures, code formatting, JavaScript types and comments (for correct JsDoc) and tips and tricks.
 +
* [http://ejohn.org/blog/simple-javascript-inheritance/ Simple JavaScript Inheritance], an all-but-simple and IMO not-so-elegant idea to implement inheritance by John Resig, jQuery creator - just to test your knowledge. ;-)

Revision as of 08:54, 21 August 2012

Google Coding Conventions

We use Google's JavaScript style guide as a starting point for our own style guide.

Only deviations from the Google style guide will be documented here.

JsDoc Comments

We use class/member-level JsDoc comments as outlined in the Google style guide but we replace the file-level annotations by our usual annotations (@file, @brief, etc.).

Generally we encourage inline comments. These help readers of your code to understand the logic without having to understand every line of code. It's also a good practice to write inline comments before you write code so that you first think about the logic and semantics you want to implement and then about the syntax.

For the moment being we do not use curly braces ({}) around the object declared with @extends. This is for better tool support. The build script will introduce the braces for compilation.

Whitespace

We generally use whitespace exactly as outlined in Google's JavaScript style guide except for indentation which we do with tabs rather than spaces.

Object Oriented Programming in JS

Inheritance

There are many ways to implement inheritance in JavaScript. We chose our approach for flexibility, simplicity and memory/execution performance.

We are using a (pseudo-)classical approach of inheritance rather than the purely prototypical or mixin approach:

// Define parent constructor.
Parent = function() { ... };
// Implement parent object.
Parent.prototype.xyz = ...;

// Define child constructor.
Child = function() { ... };

// Let Child inherit from Parent - memory efficient version avoiding constructor side effects.
Temp = function() {};
Temp.prototype = Parent.prototype;
Child.prototype = new Temp();
// + a bit of sugar to fix the constructor property and allow access to the parent prototype...

// Implement child object.
Child.prototype.xyz = ...;

Compared with the 'purely' prototypical approach (parent = {...}; F = function() {}; F.prototype = parent; child = new F;):

  • It's easier to put in an intermediate object above the parent when we use constructors from the beginning.
  • It's easy to insert initialization code in the parent object if required at a later stage because we won't have to insert new everywhere in the code.
  • There's minimal overhead in using functions as parents if we make sure to not call the constructor (thereby avoiding potential side effects of the constructor and making a potentially big object) when letting a child inherit from the parent, see use of 'Temp' above.

Compared with the pure "mixin" approach (used in jQuery and jQueryUI):

  • Easy access to superclass method implementations via our custom _parent static variable.
  • Using new is usually faster than manually copying mixins to new objects in a loop.
  • You cannot use the instanceof operator with Mixins.
  • Mixins are less memory efficient than inheritance via prototypes.

Cross-Cutting Concerns

There are certain features that are really cross-cutting concerns or that should be shared between objects independent of their inheritance hierarchy. JavaScript fortunately has very powerful mechanisms to deal with such situations.

While we do not use "mixins" to implement vertical code re-use (see inheritance above), we use them throughout to implement horizontal code re-use. Objects "declare" the aspects they implement similarly to how they declare inheritance - via a method that actually implements the mixin.

We implement cross-cutting aspects in separate objects and then introduce them into classes. We have got two mechanisms for that:

  1. interface weaving
  2. interceptors

Interfaces

There is no syntactical correspondence for interfaces in JavaScript but we implement them conceptually as objects and enforce them with Google's Closure compiler. We have an "abstract" base object that declares the methods that go into an interface without implementing them. Then we let implementations of the interface inherit from these "abstract" objects. Please use the @interface and @implements annotations to declare interfaces.

Interfaces that share the same implementation across objects can be weaved into objects as aspects via the $.Helper.injectMixin() method which relies on our object factory to identify the right implementation of an interface and then introduces it into the target object via the mixin mechanism provided by jQuery's $.extend() function. This allows us to exchange interface implementations on runtime (e.g. for automated testing or different deployment environments) without having to change the object declarations, see the explanation of the object factory below.

If the interface implementation differs on a per-object basis so that we cannot use shared mixins then objects implement interface methods themselves.

Method Interception

We can implement method interception via the proxy that we attach to every object on instantiation. The proxy can either implement methods itself or delegate to other objects for actual interception. We'll further detail that design approach when we actually need it.

Object Factory

The object factory introduces an additional indirection between client code and the objects it instantiates. Without an object factory, client code would have to be closely coupled to specific implementations of certain interfaces on object instantiation. The object factory allows us to work with logical "object names" rather than "class names". We then can control in one central place how these object names will be resolved to actual objects. The performance overhead for such an approach is minimal in JavaScript as, due to the dynamic nature of JavaScript, we can use very powerful native JavaScript functionality.

Potential use cases of such an approach are: - dynamic dependency injection (IoC) - run-time weaving of interfaces and method interceptors (AOP) - a central place to enforce restrictions and application policies on object level

This indirectly introduces more flexible possibilities for logging, security/consistency checks, dynamic patching of objects, central configuration, improved testability, working with stub implementations, etc.

Visibility

All our object members are techically public.

We use the @constructor, @protected and @private markers that Google's JavaScript compiler understands and enforces.

We also put underscores in front of object members that are intended to be private.

Advantages over the 'closure + privileged method approach' (see e.g. Douglas Crockford, "JavaScript: The good parts") for private object members:

  • Reduced complexity for novice programmers who are not so familiar with the closure concept.
  • Approach is similar to our PHP4 compatible nomenclature for private methods in PHP.
  • Less probability to create memory leaks with closures, see http://code.google.com/speed/articles/optimizing-javascript.html
  • Better tool support (e.g. Eclipse JSDT)


Object vs. 'Class' members

There is no such thing as classes (or static methods) in JavaScript. We implement class members that do not need to be copied into each object instance (=static members) within the constructor object:

SomeConstructor = function() { ... };
SomeConstructor.staticMethod = function() { ... };

Thereby we make sure that on instantiation of that constructor with the new keyword, staticMethod will not be copied into the new object instance.

JavaScript doesn't have self:: or parent:: constructs. We emulate these constructs with methods automatically added to all objects retrieved from the object factory:

  • If you want to access a static class member you can use the following syntax:
// For a static property:
var myProperty = this.self('someStaticProperty');
// This is like self::$someStaticProperty in PHP.
// For a static method call.
var result = this.self('someStaticMethod', arg1, arg2, ...);
// This is like self::someStaticMethod(arg1, arg2, ...) in PHP.
  • If you want to access the parent implementation of an overridden method you can use the following:
// Call overridden constructor from within the child constructor:
var result = this.parent(arg1, arg2, ...);
// This corresponds to parent::_constructor(arg1, arg2, ...) or parent::SuperClass(arg1, arg2, ...) in PHP.
// Call overridden implementation of an instance method:
var result = this.parent('someOverriddenMethod', arg1, arg2, ...);
// This is like parent::someOverriddenMethod(arg1, arg2, ...) in PHP.


Some background and credits: Several techniques have been developed to emulate parent method implementation access. We use a method combining the performance and tool support of Google Closure's goog.base() with the terse grammar of John Resig's inheritance approach (see "additional resources" below). Our object factory / object proxy allows us to do away with both, the awkwardness of the goog.base() method calls and the performance trouble, unnecessary complexity and bad tool support of John's approach. The self() method is unique to our library and allows for 'inheritance' of static methods.

Object member ordering

We group object members by visibility and singleton ('static') vs. instance membership in the following order in our object definitions:

  1. constructor (use @constructor annotation)
  2. private static properties (end with underscore, use @private annotation)
  3. private instance properties (end with underscore, use @private annotation)
  4. protected instance properties (discouraged, better use private variables with protected accessors/mutators, use @protected annotation)
  5. no public static/instance properties - use public accessors/mutators instead!
  6. public static methods
  7. public methods
  8. protected static methods (use @protected annotation)
  9. protected methods (use @protected annotation)
  10. private static methods (end with underscore, use @private annotation)
  11. private methods (end with underscore, use @private annotation)

Sections are divided with a three line // style comment indicating the class member category.

You are obviously not supposed to use a member outside its scope even if the visibility is not enforced by closure. Please run lib/pkp/tools/buildjs.sh on your code to make sure that you have not overlooked anything. This is also true for protected object members. While they are named like public methods they must not be used outside of the inheritance hierarchy of the current class.

Building and Checking JS Code

Build Script

We have developed a build script that serves several purposes:

  • Run checkers over the code that make sure that we implement a safe subset of JavaScript. We use JSLint and the Google Linter for that purpose. The checking process will also enforce most of our coding style rules, including some additional annotation-based type checking that is not implemented in JavaScript itself.
  • Join, compile and minify the source. This is necessary to reduce the number of files to be loaded on a web page as well as the overall size of the JavaScript files. It has an important performance impact on page load.

The build script is in lib/pkp/tools/buildjs.sh. It can be run whenever changes have been made in the code to check whether these are compatible with our coding style guide. The script also makes sure that the differences from the Google coding style guide will not come in the way. Please look at the source code for further information. The script has extensive inline documentation.

NB: Currently only files in the js/classes folder will be checked against our coding style rules. External files in lib and legacy files in functions contain too many deviations from the expected coding style for such a check being useful there. We should, however, make sure that we include at least the files in functions sooner or later.

Switch Between Minified and Original Code

We now have a new parameter in config.inc.php that is used to switch between the minified version and the full version of the code. The parameter is called enable_minified.

For production environments this parameter should always be set to "On". You can only set it to on after executing the build script.

For development environments the parameter can be switched "Off" for easier in-browser debugging, e.g. via FireBug. This allows you to step through the original source code rather than having to decipher the minified code.

Adding New Scripts Files

Whenever you create a new JavaScript source file you must add it to the templates/common/minifiedScripts.tpl file. This file has two purposes:

  • It will be included as a whole into the header template when enable_minified is switched to "Off".
  • It will be parsed by the build script to discover files to be minified and/or checked.

Code Release

The build script must be run before we release code so that an up-to-date minified source file will be included in the released tar archive. We also should make sure that we release code with the enable_minified parameter already switched to "On" in config.TEMPLATE.inc.php.


The PKP JavaScript Handler Model

Model-View-Controller (MVC) Design

We implement an MVC pattern on the client side.

The guiding principles are similar to what exists in Smalltalk, Adobe Flex and other, JavaScript-based client side frameworks: The interface is made up of GUI widgets and each active widget has its own controller attached. GUI widgets can be nested. Widgets emit events when the end user interacts with them or when some other part of the system triggers them (e.g. a timeout or another widget). Events will be received and handled by the controller. The controller can access client- or server-side data and will then update the GUI in response to the end user's actions.

Note: Event-based MVC is quite different from request-based MVC (but somehow similar to AJAX MVC). In any case: please do not assume that all you know about server side MVC is necessarily transferable to the client side.

GUI widgets are HTML elements. Not all HTML elements need a handler attached but no HTML element can have more than exactly one handler attached.

Controllers are JavaScript objects that inherit from $.pkp.classes.Handler.

Strict Encapsulation of Widgets

JavaScript is a very powerful language - too powerful if it's not used with care. The same is true for the jQuery library and the DOM. That's why we establish a framework that purposefully limits us to a small subset of JavaScript, DOM and jQuery features. Everything outside this subset is strongly discouraged. The framework itself makes full use of the JavaScript, DOM and jQuery power but it structures and channels how client code has access to these features to avoid some common pitfalls that come with poor code design.

Most client-side limitations result from the MVC model together with strict encapsulation of GUI widgets. This avoids undocumented dependencies between widgets which can make code maintenance a nightmare:

  1. The handler of a GUI widget will handle all events emitted by the corresponding HTML DOM element or it's sub-elements.
  2. No handler will handle any events that are not emitted by the GUI widget it is attached to.
  3. We do not allow bubbling of any events outside of the GUI widget unless the event was first handled by the responsible handler and semantically re-wrapped into an event understood by the parent widget's handler!
  4. We also don't allow that one handler updates the GUI of another handler. If one handler wants another handler to update its GUI it has to do so by triggering an event on that handler which will then be handled there.
  5. We do not allow jQuery.live() or jQuery.delegate() event bindings! Whenever a widget is replaced by another widget its original event bindings will be completely released and the new widget will have to bind its own events.

Event Management

The $.pkp.classes.Handler class implements an event dispatcher that will centrally handle and route all events emitted by its corresponding GUI widget. No events may ever be bound directly to a GUI widget!

This is necessary because we want to have a central checkpoint for events and we want to be able to garbage collect our event bindings centrally when we remove the handler. We also want to manage the context of an event handler call. The 'this' variable will always be set to the handler and not to the GUI widget that emitted the event (as is default in jQuery).

The $.pkp.classes.Handler class implements a bind() method. It binds events to the root HTML element of the GUI widget.

Whenever possible you should prefer events over callbacks! You often have a choice with jQueryUI for example. Use jQueryUI events rather than callbacks.

If you implement your own custom events then always prefix event names with 'pkp', i.e. 'pkpRemoveHandler'.

Callbacks

If for some reason you really have to implement a callback rather than an event then $.pkp.classes.Handler also provides a callbackWrapper() method that can be used to correctly wrap callbacks so that they are called in the handler context rather than in the context of the calling object.

This approach should be used with care because the callbackWrapper() method creates a closure that points back to the handler. If you bind a callback returned by the callbackWrapper() method then you always have to make sure that the reference to the callback is deleted when you remove the handler from the GUI element. If you stick to the encapsulation rules, this should not be too difficult. But watch out!

Data Items

If you need to keep data then please keep it in handler properties and do not use jQuery's data() cache. This will keep the data() namespace clean. The handler framework will make sure that the handler object including your data will be properly garbage collected when you remove the handler. Keep in mind that adding data to the DOM element is like defining global variables which is not encouraged.

Binding Handlers to HTML elements

We implement our own jQuery plug-in to bind PKP widget handlers to HTML elements.

The binding is done via usual jQuery selectors:

$('some selector').pkpHandler('$.some.handler.Name', {...handler options...});

This is the only bit of JavaScript we allow in Smarty templates at all! If you feel the need to add anything more than this to your templates then you probably have a design error somewhere.

Standard Widget Controller Library

Please see our dedicated JavaScript library wiki page that documents all widget controllers.

Additional Resources

Recommended Books

  • Ross Harmes and Dustin Diaz, "Pro JavaScript Design Patterns"
  • Douglas Crockford, "JavaScript: The Good Parts"

Recommended Articles