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 (likeui.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 thethiswill 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 (likeA.fooiffoois a class attribute ofA)
operations: a dictionary mapping class methods (operation) names to functions. Thethisin these functions will refer to the class object, as returned byExtend.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 thethisargument of the method. UsinggetMethodwill ensure that the this is preserved. It's actually the equivalent of doing 'this.getClass().bindMethod(this, name)'. -
getCallback(name:String) - The
getCallbackmethod is similar togetMethod, except that it will add an extra argument which is thethisused when invoking the method. Libraries such as jQuery change thethisof callbacks given to events such asclickorfocusto the DOM node that received the event rather than the instance to which the method is bound. UsinggetMethodinsulates you from this change, but you also lose the reference to the DOM node that received the event (the event target). When usinggetCallback, 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
trueif 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
ownmethods and theinheritedmethods. Settings these two parameters to eithertrueorfalsewill 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
thiswill be preserved even if you useoperation.apply(other_object, arguments)(pretty much likebindMethod). -
bindMethod(o:Object, name:String) - Returns the method named
namewhen it is bound to the given object. This method can be safely given as a callback, and even if thethisis changed in amethod.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
thisorself). Doingo getClass() getParent() proxyWithState(o)is the equivalent of creating asuperkeyword 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") )