3.0 beta 2

Objects

Objects allow to define and access properties, handle events and perform data binding operations.

Creating and initialising objects

Objects are created using a constructor function and operator new.

var obj = new AX.Object();

Functions AX.create(), AX.clone() also return new objects. In addition, new objects can be created implicitly when accessing an object property or an element of an object collection.

Operator 'new'

It is common to create and initialize an object in one step.

var obj = new AX.Button({
    id: 'button1',
    width: 200,
    height: 30,
    text: 'New Document'
});

This is equivalent to creating a blank object and then calling set(config) method.

AX.create()

Often it is a good idea storing component configuration externally as a JSON structure and loading it on demand. To make it fully dynamic, the component type (i.e. constructor name/alias) can be part of the config. In this case use special component property and create the object with AX.create(config).

var obj = AX.create({
    component: 'button',
    id: 'button1',
    width: 200,
    height: 30,
    text: 'New Document'
});

Assigning config to properties

AX.create() is also called internally by the property setter when assigned value contains component property. In the next sample the panel will have new okButton property, which will be assigned AX.Button() object configured with text and command values.

var panel = new AX.Panel({

    okButton: {
        component: 'button', // calls AX.Button() constructor
        text: 'OK',
        command: 'submit'
    }
});

As constructor and property methods all accept promises, the whole component can be created on the fly with one simple step.

// loading component config with jQuery AJAX
var config = $.getJSON(url); 

// creating panel content dynamically
var panel = new AX.Panel(config);

In this scenario the component customization ('behavior' part) could done via dynamically attached mixins.

AX.clone()

One special case of creating an object is when a quick copy of another object is needed. This happens when accessing a property which was assigned a child object at the prototype level. Here we cannot just return a child object which belongs to the prototype, otherwise all instances of this class will be modifying the same instance of the child object. Instead we have to make a copy (clone).

ActiveWidgets does this by creating a blank object and assigning an original object as its prototype. This way all properties and methods of the original object are accessible through the prototype chain. So the new object appears like the copy of the original one and no actual copying of properties/methods is taking place. In the strict sense the new object is not a copy but an instance of a subclass (we assign the prototype to the constructor, which is generated automatically).

var prototype = new AX.Button({
    text: 'shared'
});

var button1 = AX.clone(prototype), // The clones are separate objects, but they share (inherit)
    button2 = AX.clone(prototype); // all the properties/methods/events from the prototype

While this may sound complicated, the result is a dramatic increase in speed while working with large object trees. Instead of deep-copying the whole tree up front, we only clone child objects on the first access. So the large part of the component tree will stay un-modified at the prototype level, or even several levels deep in the prototype chain.

Defining classes

To define a new class use extend method of the parent class constructor.

// create a new class constructor
var MyPanel = AX.Panel.extend(function(){

    // configure class prototype
    this.set({
        border: true,
        background: true,
        frame: true,
        shadow: true
    });

    this.on('click', function(){
        // ...
    });
});

// create an object instance of the new class
var panel = new MyPanel({
    width: 500,
    height: 200
});

The single argument of the extend method should be a setup function, which will be called to configure the prototype of the new class. This is where you create new properties, configure existing ones, add new methods and attach event handlers.

The goal is to apply all changes to the prototype (once) and then do as little as possible when the new object instance is created (while running the constructor function). The extend returns automatically-generated new class constructor, which normally just calls set method passing the constructor arguments.

Split the class into multiple files

Sometimes it might be necessary to do class initialization in multiple steps (for example, split the large class into multiple files). This can be done using include method of the constructor, which just runs extra initialization code and combines it with the one provided into extend call.

// create class
var MyClass = AX.Object.extend(function(){

});

// continue initialization
MyClass.include(function(){

});

Alias

The method alias allows to attach an alternative (short) name to the class constructor. It can be used in JSON config component field as constructor reference.

MyClass.alias('my-class');

var obj = AX.create({
    component: 'my-class',
    width: 500,
    height: 200
});

Multiple inheritance (mixins)

While it makes sense to include common functionality into base classes, it is better to keep optional or rarely used features as separate modules. ActiveWidgets provides an easy way to combine multiple classes into one. With this appoach you may define a separate mini class for each feature, and then mix them in any required combination.

var panel = new AX.Panel({

    // add optional features
    use: ['title', 'toolbar', 'status'],

    // added by AX.Title class
    title: 'Composite panel', 

    // added by AX.Toolbar
    toolbar: [item1, item2, item3],

    // added by AX.Status
    status: 'ok'
});

Mixing is done by calling use method with the class name/alias as an argument. You can also give an array of names to mix multiple classes at once. If use field is present in config argument of the constructor or set method, it will be processed before other fields, so you can add a feature and configure it in the same call.

Defining mixin is the same as defining any other class. You call parent class extend method providing setup function, then add an alias to the constructor.

// define new class
AX.Title = AX.Component.extend(function(){

    // define new property 'title' as an instance of the AX.Bar class
    this.set('title', new AX.Bar());
});

// add an alias
AX.Title.alias('title');


// create new object
var panel = new AX.Panel({

    // add properties, methods of the AX.Title class
    use: 'title',

    // configure 'title' properties (AX.Bar)
    title: {
        height: 20,
        border: true,
        text: 'My Panel'
    }
});