Extend 2.0

Developer Manual

AuthorSebastien Pierre <sebastien@ivy.fr>
Revision12-Oct-2007

Extend 2.0 is an evolution of the Extend 1.0 which implements a flexible class-based OOP layer on top of the JavaScript prototype object model.

Extend 2.0 allows you to write nice and clean classes in JavaScript, features introspection and class meta-information, is mature and fully-tested, and used as a base for our wonderful Sugar language runtime.

1.The problem

JavaScript being a prototype-based object language, it does not offer (in standard) a “class-based” OOP layer. This can become a problem when you're writing medium to large system, where prototypes start to be cumbersome to manage.

The famous JavaScript Prototype library provides a very basic way to create classes, but does not provide any inheritance between classes. Extensions such as ExtendClass and ExtendClassFurther tried to add sub-classing, but fail to have a flexible and reliable super-equivalent, which makes advanced JavaScript application development difficult.

Moreover, neither of these extensions allow meta-information specification to classes (such as class name, methods, parent class, etc), while this information is very useful for debugging and introspection.

Additionally, common cases such as handing an object method to a callback that may alter the this is not supported (jQuery callback do that).

2.Introducing Extend

Extend 2.0 is a simplified, cleaner version of the Extend 1.X library, with the same design goals:

  • Traditional class-based OOP layer for JavaScript
  • Notion of class attributes (shared), methods and class methods (operations)
  • Introspection API to list attributes, methods, operations
  • Makes the difference between what is inherited and what is not inherited
  • Easy wrapping of object methods to be safely given as callbacks

To start using Extend, simply add the following line to your HTML head section:

<script type="text/javascript" src="http://www.ivy.fr/extend/extend.js"></script>

Once you've included the script, you can create classed by using the 'Extend.Class' function:

var A = Extend.Class({
  name:"A",
  methods:{
    helloWorld:function(){
      return "Hello, World !";
    }
  }
});
alert(new A().helloWorld());

The Extend.Class takes the following parameters, given in a dictionary:

  • name (required): a string representing the class name. In case you want the class name to reflect a package hierarchy (like ui.widget.InputField), you can use dots to join module names and class name.
  • parent: the parent class (if any) of this class.
  • initialize: the constructor function for this class.
  • properties: a dictionary mapping instance declaring attributes (properties) (and their optional default values).
  • methods: a dictionary mapping method names to functions implementing the methods, where the this will refer to the current instance.
  • shared: a dictionary mapping class attributes names to values. Class attributes default values are inherited by sub-classes, and can be accessed directly (like A.foo if foo is a class attribute of A)
  • operations: a dictionary mapping class methods (operation) names to functions. The this in these functions will refer to the class object, as returned by Extend.Class.

In methods, you this will (obviously) point to the current instance. If you want to access the method foo defined in class A, when you are in class B subclass of A, you can do:

this.getSuper(A).foo()

or use the faster option:

this.A_foo()

where A is the name you gave to the class A.

3.Extend API

Extend offers a set of methods that can are available to Extend classes and instances created from Extend classes. These methods range from simple introspection (what is the class of an object, what is this class name, is this an object an instance of this class, etc) to more complex things (safely wrapping a method for callback, listing inherited methods, etc).

3.1.Extend API

Extend.Class({...})
Declares and returns a new class according to the class description given as argument. This description was explained in the previous section.
Extend.getClass(name:String)
Returns the class with the given absolute name, if any.
Extend.getClasses()
Returns a dictionary that maps declared class names to actual class instances. Extend acts like a global namespace where non-anonymous declared classes are registered.
Extend.getChildrenOf(aClass)
Returns a dictionary that maps declared class names to actual class instances of classes that inherit from the given class (aClass). This excludes the given class from the result.

3.2.Object API

The object API defines the methods which are available to instances (objects) which were created (instantiated) from a class defined using Extend.

isClass()
Tells if the given object is class or not. This returns false
getClass()
Returns the class object associated with this instance. You can use the class object to access class operations, class attributes, etc.
getMethod(name:String)
In JavaScript, if you give a method as a callback by simply doing object.method, then you may have problems when the caller changes the this argument of the method. Using getMethod will ensure that the this is preserved. It's actually the equivalent of doing 'this.getClass().bindMethod(this, name)'.
getCallback(name:String)
The getCallback method is similar to getMethod, except that it will add an extra argument which is the this used when invoking the method. Libraries such as jQuery change the this of callbacks given to events such as click or focus to the DOM node that received the event rather than the instance to which the method is bound. Using getMethod insulates you from this change, but you also lose the reference to the DOM node that received the event (the event target). When using getCallback, you'll have the target as an additional argument.
getSuper(c:Class)
Returns a proxy that will use the current object as state, but where every operation defined in the proxy will use the implementation defined in the given class.
isInstance(c:Class)
Returns true if this instance is an instance of the given class, which must be either the class of this instance, or an ancestor of this instance class.

3.3.Class API

The class API defines the methods which are available for class objects resulting from the use of the Extend API.

isClass
Tells if the given object is class or not. This returns true
getName
Returns the class name, as given when creating the class.
getParent
Returns the parent class for this class, or undefined.
hasInstance(o:Object)
Tells if the given object is an instance of this class. This also includes the parent classes.
isSubclassOf(c:Class)
Tells if this class is a subclass of the given class. This also takes into account the parent classes.
listMethods(own:Boolean=True, inherited:Boolean=True)
Returns a dictionary that maps methods names to unbound methods, including by default the own methods and the inherited methods. Settings these two parameters to either true or false will allow to return the desired subset.
listOperations(own:Boolean=True, inherited:Boolean=True)
Same as listMethods, but with class operations.
listShared(own:Boolean=True, inherited:Boolean=True)
Same as listMethods, but with class attributes (“shared”).
listProperties(own:Boolean=True, inherited:Boolean=True)
Same as listMethods, but with instance attributes (“properties”).
getOperation(name:String)
Returns the class operation with the given name wrapped so that the this will be preserved even if you use operation.apply(other_object, arguments) (pretty much like bindMethod).
bindMethod(o:Object, name:String)
Returns the method named name when it is bound to the given object. This method can be safely given as a callback, and even if the this is changed in a method.apply(other_object, arguments), it will be preserved.
proxyWithState(o:Object)
Returns an object that will have the same operations and attributes defined in this class, but will use the given object as state (as this or self). Doing o getClass() getParent() proxyWithState(o) is the equivalent of creating a super keyword where you can invoke methods from the parent.

4.Examples

Step 1: Create a new class

var Shape = Extend.Class(
  name:"Shape",
  initialize:function(){
    this.points = [];
  }
  methods:{
    addPoint:function(p){
      this.points.push(p);
    }
    getPoints:function(){
      return this.points;
    }
  }
});

Step 2: Create an instance of your class, and do stuff

my_shape = new Shape();
my_shape.addPoint([0,0]);
my_shape.addPoint([1,0]);
console.log(my_shape.getPoints().toSource());

Step 3: Create a subclass

var Rectangle = Extend.Class(
  name:"Rectangle",
  parent:"Shape",
  initialize:function(){
    this.points = [];
  }
  methods:{
    addPoint:function(p){
      this.points.push(p);
    }
    getPoints:function(){
      return this.points;
    }
  }
});

Using callbacks with jQuery

var MyWidget = Extend.Class({
  name:"MyWidget",
  methods:{
    onClick:function(target){
      $(target).css({background:yello});
    }
})

var w = new MyWidget()
$("#my-widget").click( w.getCallback("onClick") )
JavaScript Gotchas, Sébastien Pierre, June 2007 tech note
Object-Oriented Programmming in JavaScript, Mike Moss, January 2006 article
Prototype-based Programming Wikipedia Article, wikipedia
Java Reflection API tutorial
Extend Class, Prototype library extension to add subclassing wiki page
Extend Class Further, adding Ruby-like OO features to Prototype wiki page