UIZE JavaScript Framework

GUIDES Classes And Inheritance

1. Introduction

The UIZE JavaScript Framework implements a system for class inheritance that makes subclassing and object-oriented programming possible in JavaScript.

2. Creating Classes

UIZE's system for classes and inheritance makes it dead easy to create a class.

Class features are provided in the Uize.Class module. You can create your own class very easily, simply by calling the subclass method on the Uize.Class base class, as follows...

var Rectangle = Uize.Class.subclass ({
  stateProperties:{
    width:{value:10},
    height:{value:10}
  },
  instanceMethods:{
    displayArea:function () {
      alert (this.get ('width') * this.get ('height'));
    }
  }
});

var rectangle = Rectangle ();
rectangle.displayArea ();  // alerts "100"

var anotherRectangle = Rectangle ({width:5,height:15});
anotherRectangle.displayArea ();  // alerts "75"

2.1. Creating Further Subclasses

All subclasses that inherit from Uize.Class get a subclass static method, so you can make further subclasses just as easily...

var Rectangle = Uize.Class.subclass ({
  stateProperties:{
    width:{value:10},
    height:{value:10}
  },
  instanceMethods:{
    displayArea:function () {
      alert (this.get ('width') * this.get ('height'));
    }
  }
});

var VerboseRectangle = Uize.Class.subclass ({
  instanceMethods:{
    displayArea:function () {
      var
        width = this.get ('width'),
        height = this.get ('height')
      ;
      alert ('The areas is: ' + width * height + ' (' + width + ' x ' + height + ' )');
    }
  }
});

var rectangle = Rectangle ();
rectangle.displayArea ();  // alerts "100"

var verboseRectangle = VerboseRectangle ({width:5,height:15});
verboseRectangle.displayArea ();  // alerts "The area is: 75 (5 x 15)"

3. Creating Instances

When you create a class that calling the Uize.Class.subclass method, either on the Uize.Class base class or a subclass of it, creating instances is more convenient than regular classes.

3.1. The "new" Keyword is Optional

The Uize.Class base class implements a novel mechanism for constructors that makes the "new" keyword optional when creating instances.

Because the Uize.Class base class utilizes the "no new" mechanism, one can create instances of any Uize.Class subclass either using the new operator or not. This means that you can use the "new" keyword or not with UIZE classes (as well as your own classes), and the end result will be the same.

THIS...

var myInstance = MyClass ();

IS EQUIVALENT TO...

var myInstance = new MyClass ();

For a detailed discussion of this mechanism and how it works, consult the section The "no new" Mechanism.

3.2. State Replaces Constructor Arguments

The Uize.Class base class standardizes the constructor signature for all classes to an optional object that specifies initial state for a newly created instance.

EXAMPLE

var rectangle = Rectangle ({width:5,height:15});

This design choice focuses the design of classes on exposing anything that might affect construction through the state properties system, leading to better designed class interfaces. Being an object, the initial state object also allows state properties to be specified in any order, as well as allowing initial instance state for any and all properties to be optionally specified with sensible defaulting defined for state properties in the class' implementation. In contrast, the traditional approach to constructor arguments often suffers from increasingly complicated overloading of the constructor function as a class evolves to become richer.

4. Declaring Features

For convenience, UIZE provides a few different ways of declaring a class' features to suit different situations.

4.1. Declaring Features During Class Creation

The most concise form of declaring class features allows the features to be optionally declared in the call to the subclass method.

var MyClass = Uize.Class.subclass ({
  instanceMethods:{
    someInstanceMethod1:function () {
      // do some stuff
    },
    someInstanceMethod2:function () {
      // do some stuff
    }
  },
  staticMethods:{
    someStaticMethod1:function () {
      // do some stuff
    },
    someStaticMethod2:function () {
      // do some stuff
    }
  }
});

The Uize.Class.subclass method can optionally accept a featuresByTypeOBJ parameter, whose value should be a features declaration object, where the name of each property should be the name of a feature type, and where the value of each property should be an object containing any number of features of that type.

4.2. Using the declare Static Method

If you already have a reference to a created class and you would like to declare multiple features of different types in a single method call, you can call the MyClass.declare static on the class.

MyClass.declare ({
  instanceMethods:{
    someInstanceMethod1:function () {
      // do some stuff
    },
    someInstanceMethod2:function () {
      // do some stuff
    }
  },
  staticMethods:{
    someStaticMethod1:function () {
      // do some stuff
    },
    someStaticMethod2:function () {
      // do some stuff
    }
  }
});

4.3. Using the Feature Declaration Methods

As an alternative to using the MyClass.declare static method, you can declare features on a class using the various feature declaration static methods of the class that are inherited from the Uize.Class base class.

call the MyClass.instanceMethods method to declare instance methods
call the MyClass.staticMethods method to declare static methods
call the MyClass.stateProperties method to declare state properties
more feature declaration methods: MyClass.instanceProperties, MyClass.staticProperties, etc.

EXAMPLE

MyClass.instanceMethods ({
  someInstanceMethod1:function () {
    // do some stuff
  },
  someInstanceMethod2:function () {
    // do some stuff
  }
});

MyClass.staticMethods ({
  someStaticMethod1:function () {
    // do some stuff
  },
  someStaticMethod2:function () {
    // do some stuff
  }
});

4.4. Modifying the Class Manually

In addition to the various convenient ways that are provided for declaring class features, you can also declare features by modifying a class manually.

assign properties on the class' prototype object to define instance methods and/or properties
assign properties on the class to define static methods and/or properties

EXAMPLE

MyClass.prototype.someInstanceMethod = function () {
  // do some stuff
};

MyClass.someStaticMethod = function () {
  // do some stuff
};

While this technique is not recommended, you may encounter older UIZE code that uses this technique for declaring class features, since the code may not yet have been updated since the newer approach to feature declaration was introduced.

4.5. Features Declaration Object

Both the MyClass.subclass and MyClass.declare static methods accept a featuresByTypeOBJ parameter, which allows multiple features of multiple different feature types to be specified in a single feature declaration object and, therefore, declared in a single method call.

4.5.1. Declaring Features by Type When Creating a Class

The Uize.Class.subclass method supports a variation that lets you create a subclass, declaring multiple features by type at the time of creating a class, by supplying just a single featuresByTypeOBJ parameter.

EXAMPLE

var MySubclass = MyClass.subclass ({
  alphastructor:function () {
    // implementation here
  },
  omegastructor:function () {
    // implementation here
  },
  staticMethods:{
    staticMethod1:function () {
      // implementation here
    },
    staticMethod2:function () {
      // implementation here
    }
  },
  instanceMethods:{
    instanceMethod1:function () {
      // implementation here
    },
    instanceMethod2:function () {
      // implementation here
    }
  },
  stateProperties:{
    stateProperty1:{
      // property profile
    },
    stateProperty2:{
      // property profile
    }
  }
});

4.5.2. Declaring Features by Type for an Already Created Class

One or more features of one or more different feature types can be declared for a class after the class has already been created, by calling the Uize.Class.declare method on the class and supplying a featuresByTypeOBJ parameter.

EXAMPLE

var MyClass = Uize.Class.subclass ();

MyClass.declare ({
  alphastructor:function () {
    // implementation here
  },
  omegastructor:function () {
    // implementation here
  },
  staticMethods:{
    staticMethod1:function () {
      // implementation here
    },
    staticMethod2:function () {
      // implementation here
    }
  },
  instanceMethods:{
    instanceMethod1:function () {
      // implementation here
    },
    instanceMethod2:function () {
      // implementation here
    }
  },
  stateProperties:{
    stateProperty1:{
      // property profile
    },
    stateProperty2:{
      // property profile
    }
  }
});

4.5.3. How the Features Declaration Object is Implemented

The properties of the featuresByTypeOBJ object should correspond to the names of the various feature declaration methods supported by the class being subclassed.

When features are specified, categorized by type, in the featuresByTypeOBJ parameter, the Uize.Class.declare and Uize.Class.subclass methods will iterate over the properties of the object, attempting to call a static method of the name of each property encountered, on the class being subclassed, and passing the value of the property as the first parameter of the feature declaration method.

So, for example, if the Uize.Service.subclass method is called to create a service class, and if a featuresByTypeOBJ parameter is specified, and if a serviceMethods property exists within the featuresByTypeOBJ object, then the Uize.Service.serviceMethods static method will be called and the value of the serviceMethods property from the featuresByTypeOBJ object will be passed as the single parameter to the Uize.Service.serviceMethods method.

To illustrate this by example...

THIS...

var FileSystem = Uize.Service.subclass ({
  serviceMethods:{
    readFile:{
      async:false
    },
    writeFile:{
      async:false
    },
    getFiles:{
      async:false
    },
    getFolder:{
      async:false
    },
    // etc.
    // etc.
  }
});

...IS EQUIVALENT TO...

var FileSystem = Uize.Service.subclass ();

FileSystem.serviceMethods ({
  readFile:{
    async:false
  },
  writeFile:{
    async:false
  },
  getFiles:{
    async:false
  },
  getFolder:{
    async:false
  },
  // etc.
  // etc.
});

4.5.4. Standard Properties of the Features Declaration Object

Support for the following standard properties of the features declaration object is built into the Uize.Class base class...

alphastructor - lets you declare the alphastructor for the class
omegastructor - lets you declare the omegastructor for the class
instanceMethods - lets you declare one or more instance methods for the class
instanceProperties - lets you declare one or more instance properties for the class
staticMethods - lets you declare one or more static methods for the class
staticProperties - lets you declare one or more static properties for the class
stateProperties - lets you declare one or more state properties for instances of the class
dualContextMethods - lets you declare one or more dual context methods for the class
dualContextProperties - lets you declare one or more dual context properties for the class

4.5.5. More Feature Types for Specific Base Classes

Because of how the features declaration object is implemented, declaring multiple features, categorized by type, inherently supports new feature types introduced in subclasses.

In addition to the standard properties of the features declaration object, certain classes may introduce class-specific feature types for which new feature declaration static methods will be introduced. These new feature types will, by design, introduce additional properties that can be specified in the features declaration object when declaring features for subclasses of those classes. For more details, see the section Class-specific Feature Types.

4.5.6. Less Conventional Usages

Because of how the features declaration object is implemented, one can also do less conventional things along with declaring features in the Uize.Class.subclass and Uize.Class.declare methods.

For example, one can effectively call the Uize.Class.set static method to override the initial values of state properties that are inherited from the base class, as shown in the following example...

INSTEAD OF...

var MySliderWidgetSubclass = Uize.Widget.Bar.Slider.subclass ({
  instanceMethods:{
    // instance methods declared here
  },
  stateProperties:{
    // state properties declared here
  }
});

MySliderWidgetSubclass.set ({
  minValue:-50,
  maxValue:50
});

USE...

var MySliderWidgetSubclass = Uize.Widget.Bar.Slider.subclass ({
  instanceMethods:{
    // instance methods declared here
  },
  stateProperties:{
    // state properties declared here
  },
  set:{
    minValue:-50,
    maxValue:50
  }
});

4.6. Feature Declaration Methods

The Uize.Class module provides a number of methods that let you declare instance and/or static features of a class.

Uize.Class.declare - lets you declare one or more features of one or more different feature types for the class
Uize.Class.alphastructor - lets you declare the alphastructor for the class
Uize.Class.omegastructor - lets you declare the omegastructor for the class
Uize.Class.instanceMethods - lets you declare one or more instance methods for the class
Uize.Class.instanceProperties - lets you declare one or more instance properties for the class
Uize.Class.staticMethods - lets you declare one or more static methods for the class
Uize.Class.staticProperties - lets you declare one or more static properties for the class
Uize.Class.dualContextMethods - lets you declare one or more dual context methods for the class
Uize.Class.dualContextProperties - lets you declare one or more dual context properties for the class
Uize.Class.stateProperties - lets you declare one or more state properties for instances of the class

4.7. Declare Private or Public Features

The feature declaration methods can be used either to declare public features or private features.

In UIZE, there is no fundamental difference between private methods or properties and public methods or properties - it's all in the naming. By convention, private features are named with an "_" (underscore) prefix. This has its pros and cons, but one side effect of this is that either private or public features (or a mixture of both) can be declared using the feature declaration methods.

EXAMPLE

_class.instanceMethods ({
  _privateInstanceMethod1:function () {
    // implementation here
  },
  _privateInstanceMethod2:function () {
    // implementation here
  },
  publicInstanceMethod1:function () {
    // implementation here
  },
  publicInstanceMethod2:function () {
    // implementation here
  }
});

In the above example, one call to the Uize.Class.instanceMethods method is being used to declare the _privateInstanceMethod1 and _privateInstanceMethod2 private instance methods, along with the publicInstanceMethod1 and publicInstanceMethod2 public instance methods.

For a more in-depth discussion on implementing private features for a class, consult the section Private vs. Public.

4.8. Feature Declarations are Cumulative

All the feature declaration methods can be called as many times as desired, and calling them repeatedly is cumulative in nature.

This is useful, because it lets you break out declarations into different sections in your code if that makes your code more readable and/or manageable.

EXAMPLE

// Private Instance Methods
_class.instanceMethods ({
  _privateInstanceMethod1:function () {
    // implementation here
  },
  _privateInstanceMethod2:function () {
    // implementation here
  }
});

// ... ... ... ... ... ... ... ... ...

// Public Instance Methods
_class.instanceMethods ({
  publicInstanceMethod1:function () {
    // implementation here
  },
  publicInstanceMethod2:function () {
    // implementation here
  }
});

In the above example, the Uize.Class.instanceMethods method is being called twice - in one section to declare private instance methods, and in the other section to declare public instance methods.

4.9. Dual Context Features

Dual context class features are features that exist both on the class as well as instances of the class.

Examples of dual context features are the various event system methods. For example, the fire instance method lets you fire an instance event, while the Uize.Class.fire static method lets you fire an event on a class. Both the instance and class methods for firing events share the same underlying implementation, where the implementation may contain minor conditionalizing when executing in the instance context versus executing in the class context.

In cases where it is possible (and possibly even desirable) to share the same function between an instance method and a class method, the Uize.Class.dualContextMethods static method can be used to declare such methods in a single statement, rather than separately calling both the Uize.Class.instanceMethods and Uize.Class.staticMethods methods.

Although a less likely scenario, it is also possible to declare dual context properties using the Uize.Class.dualContextProperties static method. This method is present mainly for symmetry and consistency.

For dual context features, it is assumed that the feature is named the same on both the instance and the class. In situations where this is not the case, one should just use the separate methods for defining instance features and class features.

5. Feature Types

The UIZE class system formally supports the following feature types...

5.1. Alphastructor

The alphastructor is a constructor in the alphastructor chain for a class that is executed before the state properties for the instance are initialized.

When a subclass is created, the alphastructor chain for the class is created by copying the alphastructor chain of the superclass and then appending the alphastructor specified for the new subclass.

The alphastructor can be declared for a class in any of the following ways...

5.1.1. Declaring the Alphastructor During Subclassing

The alphastructor can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an alphastructor property whose value should be the alphastructor function.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  alphastructor:function () {
    // implementation of alphastructor
  }
});

5.1.2. Declaring the Alphastructor After Subclassing, Using MyClass.declare

The alphastructor can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an alphastructor property whose value should be the alphastructor function.

EXAMPLE

MyClass.declare ({
  alphastructor:function () {
    // implementation of alphastructor
  }
});

5.1.3. Declaring the Alphastructor After Subclassing, Using MyClass.instanceMethods

The alphastructor can be declared for a class after the class has already been created, by calling the MyClass.alphastructor method on the class.

When calling the MyClass.alphastructor method, it should be passed a single function parameter that is the alphastructor function.

EXAMPLE

MyClass.alphastructor (
  function () {
    // implementation of alphastructor
  }
);

5.2. Omegastructor

The omegastructor is a constructor in the omegastructor chain for a class that is executed after the state properties for the instance are initialized.

When a subclass is created, the omegastructor chain for the class is created by copying the omegastructor chain of the superclass and then appending the omegastructor specified for the new subclass.

The omegastructor can be declared for a class in any of the following ways...

5.2.1. Declaring the Omegastructor During Subclassing

The omegastructor can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an omegastructor property whose value should be the omegastructor function.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  omegastructor:function () {
    // implementation of omegastructor
  }
});

5.2.2. Declaring the Omegastructor After Subclassing, Using MyClass.declare

The omegastructor can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an omegastructor property whose value should be the omegastructor function.

EXAMPLE

MyClass.declare ({
  omegastructor:function () {
    // implementation of omegastructor
  }
});

5.2.3. Declaring the Omegastructor After Subclassing, Using MyClass.instanceMethods

The omegastructor can be declared for a class after the class has already been created, by calling the MyClass.omegastructor method on the class.

When calling the MyClass.omegastructor method, it should be passed a single function parameter that is the omegastructor function.

EXAMPLE

MyClass.omegastructor (
  function () {
    // implementation of omegastructor
  }
);

5.3. Instance Methods

Instance methods are functions that are assigned on the prototype object of a class.

When a subclass is created, all instance methods of the superclass are inherited by the subclass through a copy operation. Once a subclass is created, changing the inherited instance methods on the superclass has no effect on the subclass.

Instance methods can be declared for a class in any of the following ways...

5.3.1. Declaring Instance Methods During Subclassing

Instance methods can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an instanceMethods property whose value should be an object containing mappings of instance method names to implementation functions.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  instanceMethods:{
    foo:function () {
      // implementation of foo instance method
    },
    bar:function () {
      // implementation of bar instance method
    }
  }
});

5.3.2. Declaring Instance Methods After Subclassing, Using MyClass.declare

Instance methods can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an instanceMethods property whose value should be an object containing mappings of instance method names to implementation functions.

EXAMPLE

MyClass.declare ({
  instanceMethods:{
    foo:function () {
      // implementation of foo instance method
    },
    bar:function () {
      // implementation of bar instance method
    }
  }
});

5.3.3. Declaring Instance Methods After Subclassing, Using MyClass.instanceMethods

Instance methods can be declared for a class after the class has already been created, by calling the MyClass.instanceMethods method on the class.

When calling the MyClass.instanceMethods method, an instance methods declaration object should be provided, containing mappings of instance method names to implementation functions.

EXAMPLE

MyClass.instanceMethods ({
  foo:function () {
    // implementation of foo instance method
  },
  bar:function () {
    // implementation of bar instance method
  }
});

5.4. Instance Properties

Instance properties are non-function type values that are assigned on the prototype object of a class.

When a subclass is created, all instance properties of the superclass are inherited by the subclass through a clone-copy operation. Once a subclass is created, changing the inherited instance properties on the superclass has no effect on the subclass.

Instance properties can be declared for a class in any of the following ways...

5.4.1. Declaring Instance Properties During Subclassing

Instance properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an instanceProperties property whose value should be an object containing mappings of instance property names to initial values.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  instanceProperties:{
    foo:'bar',
    baz:'qux'
  }
});

5.4.2. Declaring Instance Properties After Subclassing, Using MyClass.declare

Instance properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an instanceProperties property whose value should be an object containing mappings of instance property names to initial values.

EXAMPLE

MyClass.declare ({
  instanceProperties:{
    foo:'bar',
    baz:'qux'
  }
});

5.4.3. Declaring Instance Properties After Subclassing, Using MyClass.instanceProperties

Instance properties can be declared for a class after the class has already been created, by calling the MyClass.instanceProperties method on the class.

When calling the MyClass.instanceProperties method, an instance properties declaration object should be provided, containing mappings of instance property names to initial values.

EXAMPLE

MyClass.instanceProperties ({
  foo:'bar',
  baz:'qux'
});

5.5. Static Methods

Static methods are functions that are assigned on the class.

When a subclass is created, all static methods of the superclass are inherited by the subclass through a copy operation (the exception to this is non-inheritable statics that are registered with the MyClass.nonInheritableStatics static property). Once a subclass is created, changing the inherited static methods on the superclass has no effect on the subclass.

Static methods can be declared for a class in any of the following ways...

5.5.1. Declaring Static Methods During Subclassing

Static methods can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an staticMethods property whose value should be an object containing mappings of static method names to implementation functions.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  staticMethods:{
    foo:function () {
      // implementation of foo static method
    },
    bar:function () {
      // implementation of bar static method
    }
  }
});

5.5.2. Declaring Static Methods After Subclassing, Using MyClass.declare

Static methods can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an staticMethods property whose value should be an object containing mappings of static method names to implementation functions.

EXAMPLE

MyClass.declare ({
  staticMethods:{
    foo:function () {
      // implementation of foo static method
    },
    bar:function () {
      // implementation of bar static method
    }
  }
});

5.5.3. Declaring Static Methods After Subclassing, Using MyClass.instanceMethods

Static methods can be declared for a class after the class has already been created, by calling the MyClass.staticMethods method on the class.

When calling the MyClass.staticMethods method, a static methods declaration object should be provided, containing mappings of static method names to implementation functions.

EXAMPLE

MyClass.staticMethods ({
  foo:function () {
    // implementation of foo static method
  },
  bar:function () {
    // implementation of bar static method
  }
});

5.6. Static Properties

Static properties are non-function type values that are assigned on the class.

When a subclass is created, all static properties of the superclass are inherited by the subclass through a clone-copy operation (the exception to this is non-inheritable statics that are registered with the MyClass.nonInheritableStatics static property). Once a subclass is created, changing the inherited static properties on the superclass has no effect on the subclass.

Static properties can be declared for a class in any of the following ways...

5.6.1. Declaring Static Properties During Subclassing

Static properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain a staticProperties property whose value should be an object containing mappings of static property names to initial values.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  staticProperties:{
    foo:'bar',
    baz:'qux'
  }
});

5.6.2. Declaring Static Properties After Subclassing, Using MyClass.declare

Static properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain a staticProperties property whose value should be an object containing mappings of static property names to initial values.

EXAMPLE

MyClass.declare ({
  staticProperties:{
    foo:'bar',
    baz:'qux'
  }
});

5.6.3. Declaring Static Properties After Subclassing, Using MyClass.staticProperties

Static properties can be declared for a class after the class has already been created, by calling the MyClass.staticProperties method on the class.

When calling the MyClass.staticProperties method, a static properties declaration object should be provided, containing mappings of static property names to initial values.

EXAMPLE

MyClass.staticProperties ({
  foo:'bar',
  baz:'qux'
});

5.7. State Properties

State properties are instance properties with special features that, among other things, ties them into the event system so that changes in their values can be easily observed.

When a subclass is created, all state properties of the superclass are inherited by the subclass through a clone-copy operation. Once a subclass is created, changing the inherited state properties on the superclass has no effect on the subclass. For a detailed discussion of state properties and the various features supported for them, consult the State Properties guide.

State properties can be declared for a class in any of the following ways...

5.7.1. Declaring State Properties During Subclassing

State properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain a stateProperties property whose value should be an object containing mappings of state property names to property profiles.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  stateProperties:{
    width:{
      conformer:function (value) {return Uize.constrain (value,1,50)},
      value:10
    },
    height:{
      conformer:function (value) {return Uize.constrain (value,1,50)},
      value:10
    }
  }
});

5.7.2. Declaring State Properties After Subclassing, Using MyClass.declare

State properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain a stateProperties property whose value should be an object containing mappings of state property names to property profiles.

EXAMPLE

MyClass.declare ({
  stateProperties:{
    width:{
      conformer:function (value) {return Uize.constrain (value,1,50)},
      value:10
    },
    height:{
      conformer:function (value) {return Uize.constrain (value,1,50)},
      value:10
    }
  }
});

5.7.3. Declaring State Properties After Subclassing, Using MyClass.instanceProperties

State properties can be declared for a class after the class has already been created, by calling the MyClass.stateProperties method on the class.

When calling the MyClass.stateProperties method, a state properties declaration object should be provided, containing mappings of state property names to property profiles.

EXAMPLE

MyClass.stateProperties ({
  width:{
    conformer:function (value) {return Uize.constrain (value,1,50)},
    value:10
  },
  height:{
    conformer:function (value) {return Uize.constrain (value,1,50)},
    value:10
  }
});

5.8. Dual Context Methods

Dual context methods are functions that are assigned on the class as well as on the prototype object of the class.

Because dual context methods are essentially just combined static and instance methods, all dual context methods of a superclass are inherited by a subclass through the copy operations used when inheriting static and instance methods. The exception to this is a dual context method where the static method is registered with the MyClass.nonInheritableStatics static property as being a non-inheritable static. Once a subclass is created, changing the inherited dual context methods on the superclass has no effect on the subclass.

Dual context methods can be declared for a class in any of the following ways...

5.8.1. Declaring Dual Context Methods During Subclassing

Dual context methods can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an dualContextMethods property whose value should be an object containing mappings of dual context method names to implementation functions.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  dualContextMethods:{
    foo:function () {
      // implementation of foo dual context method
    },
    bar:function () {
      // implementation of bar dual context method
    }
  }
});

5.8.2. Declaring Dual Context Methods After Subclassing, Using MyClass.declare

Dual context methods can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an dualContextMethods property whose value should be an object containing mappings of dual context method names to implementation functions.

EXAMPLE

MyClass.declare ({
  dualContextMethods:{
    foo:function () {
      // implementation of foo dual context method
    },
    bar:function () {
      // implementation of bar dual context method
    }
  }
});

5.8.3. Declaring Dual Context Methods After Subclassing, Using MyClass.dualContextMethods

Dual context methods can be declared for a class after the class has already been created, by calling the MyClass.dualContextMethods method on the class.

When calling the MyClass.dualContextMethods method, a dual context methods declaration object should be provided, containing mappings of dual context method names to implementation functions.

EXAMPLE

MyClass.dualContextMethods ({
  foo:function () {
    // implementation of foo dual context method
  },
  bar:function () {
    // implementation of foo dual context method
  }
});

5.9. Dual Context Properties

Dual context properties are non-function type values that are assigned on the class as well as on the prototype object of the class.

Because dual context properties are essentially just combined static and instance properties, all dual context properties of a superclass are inherited by a subclass through the clone-copy operations used when inheriting static and instance properties. The exception to this is a dual context property where the static property is registered with the MyClass.nonInheritableStatics static property as being a non-inheritable static. Once a subclass is created, changing the inherited dual context properties on the superclass has no effect on the subclass.

Dual context properties can be declared for a class in any of the following ways...

5.9.1. Declaring Dual Context Properties During Subclassing

Dual context properties can be declared for a class when the class is created, by using the optional featuresByTypeOBJ parameter when calling the Uize.Class.subclass static method.

The features declaration object should contain an dualContextProperties property whose value should be an object containing mappings of dual context property names to initial values.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  dualContextProperties:{
    foo:'bar',
    baz:'qux'
  }
});

5.9.2. Declaring Dual Context Properties After Subclassing, Using MyClass.declare

Dual context properties can be declared for a class after the class has already been created, by calling the MyClass.declare method on the class.

The features declaration object passed to the MyClass.declare method should contain an dualContextProperties property whose value should be an object containing mappings of dual context property names to initial values.

EXAMPLE

MyClass.declare ({
  dualContextProperties:{
    foo:'bar',
    baz:'qux'
  }
});

5.9.3. Declaring Dual Context Properties After Subclassing, Using MyClass.dualContextProperties

Dual context properties can be declared for a class after the class has already been created, by calling the MyClass.dualContextProperties method on the class.

When calling the MyClass.dualContextProperties method, a dual context properties declaration object should be provided, containing mappings of dual context property names to initial values.

EXAMPLE

MyClass.dualContextProperties ({
  foo:'bar',
  baz:'qux'
});

6. Class-specific Feature Types

In addition to the standard feature types that are built into the Uize.Class base class, any class can introduce new class-specific feature types for which new feature declaration static methods will be introduced to allow features of those new types to be declared by subclasses of the class.

6.1. Service Methods, as an Example

For instance, the Uize.Service base class introduces the feature type of a service method and provides the Uize.Service.serviceMethods static method for declaring service methods.

So, inherently, service methods can be declared in the featuresByTypeOBJ parameter along with other feature types that were introduced in the Uize.Class base class (instance methods, instance properties, static methods, static properties, state properties, etc.), simply by specifying a serviceMethods property in the featuresByTypeOBJ object.

6.2. Your Own Feature Types

You can introduce your own new feature types, and these feature types will get the benefits of the generalized system for declaring features

Quite simply, if you implement a new base class of which multiple different subclasses will be created, and you define a static method that allows developers to declare features of a new feature type that is introduced in your base class, then features of that type can be declared in the Uize.Class.subclass and Uize.Class.declare methods.

Consider the following example...

EXAMPLE

var ClassWithStateMonitors = Uize.Class.subclass ({
  staticMethods:{
    stateMonitors:function (monitorsByProperty) {
      for (var property in monitorsByProperty) {
        var monitor = monitorsByProperty [property];
        // code to register monitor for state property
      }
    }
  }
});

var Rectangle = ClassWithStateMonitors.subclass ({
  stateProperties:{
    width:{
      conformer:function (value) {return Uize.constrain (value,1,50)},
      value:10
    },
    height:{
      conformer:function (value) {return Uize.constrain (value,1,50)},
      value:10
    }
  },
  stateMonitors:{
    width:function () {
      // monitoring code for width state property
    },
    height:function () {
      // monitoring code for height state property
    }
  }
});

In the above, very hypothetical example, the class ClassWithStateMonitors is being created and a stateMonitors static method is declared for it. This method is a feature declaration method for a new "state monitor" feature type. Nevermind what this feature does - it's a hypothetical example, after all.

Now, given this new ClassWithStateMonitors class, we can now create subclasses by calling ClassWithStateMonitors.subclass, and for any subclasses of it that we create, we can declare any number of "state monitor" type features by specifying a stateMonitors property in the featuresByTypeOBJ parameter that is passed to the ClassWithStateMonitors.subclass method.

In our hypothetical Rectangle subclass, we're declaring "state monitors" for the width and height state properties. Once again, let's not worry about what this mysterious "state monitor" feature does - the point is that we've created a new feature type at a particular class in the class hierarchy, and all classes that descend from that class can now declare features of that new type.

For concrete examples of class-specific feature types, you can refer to the Uize.Service and Uize.Widget.V2 classes. The Uize.Service class introduces a serviceMethods feature type for the benefit of its subclasses, while the Uize.Widget.V2 class introduces the cssBindings and htmlBindings feature types.

7. Constructor Mechanism

When an instance of a UIZE class is created, construction involves three phases: the alphastructor phase, the state property initialization phase, and the omegastructor phase.

7.1. Alphastructor

The alphastructor is a function that is executed before the state properties for an instance are initialized, and before the omegastructor function is executed.

An alphastructor is declared by specifying a value for the alphastructor property of the feature declaration object when calling the superclass' subclass method.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  alphastructor:function () {
    this._someArray = [];
  }
});

Code inside the alphastructor can set up private "scaffolding" type properties that need to be already in existence by the time any of the state properties are initialized. This allows onChange handlers for the state properties to count on certain foundation having been built.

Because of when it is executed, code inside the alphastructor function cannot rely on the instance's state properties being initialized to the values passed to the constructor.

EXAMPLE

var
  MyClass = Uize.Class.subclass ({
    alphastructor:function () {
      alert (this.get ('foo'));
    }
  }),
  myClass = new MyClass ({foo:'bar'})
;

In this example, when the new instance of MyClass is created with the value 'bar' specified for its foo state property, the alert inside the alphastructor will display the value undefined. This is by design and is an important nuance to take note of.

7.1.1. Alphastructor Chain

.

7.2. Omegastructor

The omegastructor is a function that is executed after the alphastructor function is executed, and after the state properties for an instance are initialized.

7.2.1. Omegastructor Chain

.

8. Inheritance

The Uize.Class base class provides a mechanism for inheriting features declared for the class being subclassed, including the alphastructor, omegastructor, static methods, static properties, instance methods, instance properties, state properties, etc.

The inheritance system that is implemented in the Uize.Class base class overcomes some of the weaknesses of a superficial prototype-based approach to inheritance.

8.1. Overriding Inherited Features

Features that are inherited from a superclass can be overridden in exactly the same way as they are declared.

8.1.1. Overriding Instance Methods and Properties

Overriding instance methods and properties of a superclass is easy. It's just a matter of re-assigning.

EXAMPLE

var MyClass = Uize.Class.subclass ({      // create a class with an instance method
  instanceMethods:{
    foo:function () {alert ('bar')}
  }
});

var MySubclass = MyClass.subclass ({      // create subclass and override inherited instance method
  instanceMethods:{
    foo:function () {
      alert ('foo');
      MyClass.doMy (this,'foo');  // call superclass' version of foo instance method
    }
  }
});

var myClassInstance = new MyClass;        // create an instance of the class
myClassInstance.foo ();                   // call foo method on instance of class

var mySubclassInstance = new MySubclass;  // create an instance of the subclass
mySubclassInstance.foo ();                // call foo method on instance of subclass

In this example, the foo instance method of the subclass has been overrided so that it first alerts the text "foo" and then calls the foo method implementation from the superclass and alerts the text "bar".

Typically, the code that implements the methods of a subclass is near (or in the same scope as) the code that creates the subclass, so the subclass knows its superclass. When you start digging into subclass modules, you will encounter this reference to the superclass in the form of the _superclass variable (named such by convention).

8.1.2. Overriding Static Methods and Properties

Overriding static methods and properties of a superclass is easy. It's just a matter of re-assigning.

EXAMPLE

var MyClass = Uize.Class.subclass ({  // create a class with a static method
  staticMethods:{
    myStaticMethod:function () {alert ('foo')}
  }
});

var MySubclass = MyClass.subclass ({  // create subclass, overriding inherited static method
  staticMethods:{
    myStaticMethod:function () {alert ('bar')}
  }
});

myClass.myStaticMethod ();            // call myStaticMethod on MyClass
mySubclass.myStaticMethod ();         // call myStaticMethod on MySubclass

In this example, MySubclass is a subclass of MyClass, and both classes have a static method myStaticMethod. By subclassing MyClass, MySubclass inherits the implementation of myStaticMethod from MyClass. Re-assigning the myStaticMethod property of MySubclass overrides the inherited implementation without affecting MyClass. So, calling myStaticMethod on MyClass will product the text "foo" in an alert dialog, while calling myStaticMethod on MySubclass will product the text "bar" in an alert dialog.

8.1.2.1. Calling a Subclass Version of a Static Method

One may implement a class in such a way that a static method is intended to be overrided by a subclass. And in such cases, one may also wish the superclass' implementation to be guaranteed to always use the subclass' version of the static method.

Calling a subclass' version of a static method can be done in two ways, depending on whether the code is in the implementation for an instance method or for a static method of the superclass.

8.1.2.1.1. The Instance Method Case

Within an instance method's implementation, one can use the Class instance property to reference the actual class of the instance - even in the superclass' code - as in...

var MyClass = Uize.Class.subclass ({
  instanceMethods:{
    myInstanceMethod:function () {
      this.Class.myStaticMethod (); // call myStaticMethod static method on subclass
    }
  }
});

Now, in the case of an instance of MyClass, the myStaticMethod static method will be called on MyClass. However, in the case of an instance of MySubclass (that is a subclass of MyClass), the myStaticMethod static method will be called on MySubclass, even though the myInstanceMethod instance method is implemented by MyClass. If MySubclass didn't override the implementation inherited from MyClass, then there will be no difference in the outcome.

Let's take a look at an example...

EXAMPLE

var MyClass = Uize.Class.subclass ({      // create a class
  staticMethods:{
    myStaticMethod:function () {
      alert ('MyClass');
    }
  },
  instanceMethods:{
    myInstanceMethod:function () {
      this.Class.myStaticMethod ();
    }
  }
});

var MySubclass = MyClass.subclass ({      // subclass the class, overriding static method
  staticMethods:{
    myStaticMethod:function () {
      alert ('MySubclass');
    }
  }
});

var mySubclassInstance = new MySubclass;  // create instance of MySubclass
mySubclassInstance.myInstanceMethod ();   // call myInstanceMethod

In this example, MyClass implements the static method MyClass.myStaticMethod. Now, MySubclass overrides the implementation of myStaticMethod inherited from MyClass. The implementation of myInstanceMethod (inherited from MyClass) uses the Class property to get a reference to the actual class of an instance on which the method is being called. So, when this method is called on the instance of MySubclass named mySubclassInstance, the overrided form of myStaticMethod (implemented by MySubclass) is called, resulting in the text "MySubclass" being displayed in the alert dialog - not the text "MyClass".

8.1.2.1.2. The Static Method Case

Within a static method's implementation, one can use the this keyword to reference the actual class - even in the superclass' code - as in...

MyClass.myStaticMethod = function () {
  this.myOtherStaticMethod ();
};

Let's take a look at an example...

EXAMPLE

MyClass = Uize.Class.subclass ({         // create a class with two static methods
  staticMethods:{
    myStaticMethod:function () {
      this.myOtherStaticMethod ();
    },
    myOtherStaticMethod:function () {
      alert ('MyClass');
    }
  }
});

MySubclass = MyClass.subclass ({         // create subclass, overriding inherited static method
  staticMethods:{
    myOtherStaticMethod:function () {
      alert ('MySubclass');
    }
  }
});

MySubclass.myStaticMethod ();            // call inherited static method

In this example, MyClass implements the two static methods MyClass.myStaticMethod and MyClass.myOtherStaticMethod. The implementation of MyClass.myStaticMethod wants to always call the subclass' version of myOtherStaticMethod. So, instead of calling it as MyClass.myOtherStaticMethod (), it calls it as this.myOtherStaticMethod ().

Now, if myStaticMethod is called on MyClass, then this will be a reference to MyClass. However, if myStaticMethod is called on MySubclass, then this will be a reference to MySubclass. Consequently, using this guarantees that the subclass' version will be called. In this example, MySubclass overrides the implementation of myOtherStaticMethod inherited from MyClass, so the statement MySubclass.myStaticMethod () will result in the text "MySubclass" being displayed in the alert dialog - not the text "MyClass".

8.1.3. Overriding State Property Values

The initial value for a state property inherited from a superclass can be overrided by calling the MyClass.set static method that is inherited from the Uize.Class base class.

The initial value for a state property is declared by specifying the value property in the profile for the state property, as follows...

EXAMPLE

var MyClass = Uize.Class.subclass ({   // create a class with a foo state property
  stateProperties:{
    _foo:{
      name:'foo',
      onChange:function () {
        // code to handle a change in foo's value
      },
      value:'bar'
    }
  }
});

var _myClassInstance = new MyClass;    // create instance of MyClass
alert (_myClassInstance.get ('foo'));  // alert value of foo state property

In the above example, the text "bar" will be displayed in the alert dialog.

Now, the initial value for the foo state property inherited from MyClass can be overrided in a subclass, as follows...

EXAMPLE

var MyClass = Uize.Class.subclass ({   // create a class with a foo state property
  stateProperties:{
    _foo:{
      name:'foo',
      onChange:function () {
        // code to handle a change in foo's value
      },
      value:'bar'
    }
  }
});

var MySubclass = MyClass.subclass ({   // subclass the class, overriding initial value for foo
  set:{foo:'NOT BAR'}
});

var _mySubclassInstance = new MySubclass;  // create instance of MySubclass
alert (_mySubclassInstance.get ('foo'));   // alert value of foo state property

In this example, we have now created a subclass of MyClass named MySubclass. The new subclass inherits the foo state property, along with its initial value of 'bar' that is declared in MyClass. When creating the MySubclass subclass, we use the set declaration to change the initial value of this state property to 'NOT BAR'. This is equivalent to calling MySubclass.set, but we can tuck it neatly into the subclassing statement. Now, the fresh instance of MySubclass that is created, named _mySubclassInstance, will have the initial value 'NOT BAR' for its foo state property, and the alert dialog will display this text.

8.2. Features that All Classes Inherit

As part of the foundation for developing classes in UIZE, the Uize.Class base class provides its subclasses with numerous inherited systems, such as the event system, state properties system, instance creation system, subclassing system, and conditions and derivations.

9. Advanced Topics

9.1. Private vs. Public

By convention, private methods and properties - both instance and static - are distinguished from public methods and properties by prefixing their names with an underscore.

EXAMPLE

var MyClass = Uize.Class.subclass ({
  instanceMethods:{
    _myPrivateInstanceMethod:function () {  // private instance method
      // do stuff
    },
    myPublicInstanceMethod:function () {    // public instance method
      // do stuff
    }
  },

  staticMethods:{
    _myPrivateStaticMethod:function () {    // private static method
      // do stuff
    },
    myPublicStaticMethod:function () {      // public static method
      // do stuff
    }
  }
});

Technically, such private methods and properties are not thoroughly private and can be accessed as public methods or properties. However, because their names will be changed by the Scruncher when the code is scrunched to minimize its size, one cannot reliably access these methods and properties. This is a kind of "soft privacy".

IMPORTANT

When creating subclasses, it is important not to access methods or properties defined in the superclass with private names. The code will work when it is unscrunched, but there is no guarantee what your subclass will be accessing when all the code is scrunched.

9.2. Non-inheritable Statics

Unless otherwise specified, all static features - methods as well as properties - of a Uize.Class subclass are inherited in turn by their subclasses.

There are times, however, when there is no compelling reason for a static feature of a class to be inherited by its subclasses. In fact, in some cases too many inherited static features can just clutter subclasses in the class hierarchy with cruft that they neither need nor care about. In such cases, the inheritance system of the UIZE JavaScript Framework provides a way to "register" static features of a class as being non-inheritable. This is done using the MyClass.nonInheritableStatics static property.

9.2.1. The MyClass.nonInheritableStatics Static Property

The MyClass.nonInheritableStatics static property is a lookup object, automatically created for a class, in which you can register the static features (methods or properties) of the class that should not be inherited when that class is subclassed.

Each property of the MyClass.nonInheritableStatics lookup object represents a single static feature of the class that should not be inherited by subclasses, where the name of each property should be the name of a static feature (excluding the module name), and the value of each property should be a truthy value (such as true, 1, 'foo', [], {}, etc.). After a class has been created, non-inheritable statics can be registered for that class by assigning properties to the class' MyClass.nonInheritableStatics static property, as shown in the example below...

EXAMPLE

MyClass = Uize.Class.subclass ({
  staticMethods:{
    someUtilityFunction:function () {
      // do something of great utility
    }
  }
});
MyClass.nonInheritableStatics.someUtilityFunction = 1;

MyClassSubclass = MyClass.subclass ();
alert (MyClassSubclass.someUtilityFunction); // alerts the text "undefined"

In the above example, the MyClass.someUtilityFunction static method of the class MyClass has been registered as a non-inheritable static. This is done by the statement MyClass.nonInheritableStatics.someUtilityFunction = 1. Now, when the MyClassSubclass class is created by calling the MyClass.subclass method, the new subclass that is created does not get the someUtilityFunction static feature. Therefore, the alert statement displays the text "undefined" in the alert dialog.

9.2.1.1. nonInheritableStatics is a Non-inheritable Static

When a class is created, the MyClass.nonInheritableStatics static property is automatically initialized on that class to a fresh object with the value {nonInheritableStatics:1}.

This initial mapping means that the MyClass.nonInheritableStatics static property is, itself, not inheritable by subclasses - subclasses get their own fresh object. So, in our example, when the MyClassSubclass subclass is created, its fresh MyClassSubclass.nonInheritableStatics property does not have an entry for the someUtilityFunction static feature, because it does not have that static feature and the contents of the MyClass.someUtilityFunction object is not inherited by the MyClassSubclass class.

9.2.2. All Types of Statics Can Be Non-inheritable

All types of static features of a class can be registered as non-inheritable - methods as well as properties.

Registering static properties as non-inheritable is accomplished in the same way as for static methods. Consider the following example...

EXAMPLE

MyClass = Uize.Class.subclass ({
  staticProperties:{
    someStaticProperty:'foo'
  }
});
MyClass.nonInheritableStatics.someStaticProperty = 1;

MyClassSubclass = MyClass.subclass ();
alert (MyClassSubclass.someStaticProperty); // alerts the text "undefined"

In the above example, the alert dialog would display the text "undefined", because the MyClass.someStaticProperty static property of the class MyClass has been registered as non-inheritable, so it is not inherited by the MyClassSubclass class that is created by subclassing MyClass.

9.2.3. Utility Functions Need Not Be Inheritable

A good case for making a static method non-inheritable is if it is really just a utility function that happens to be stashed in a class' namespace.

Static methods that are really just utility or helper functions don't care about the context on which they are called. They just happen to be called on a class as a context because they are assigned as properties on a class.

Take, for example, the Uize.Fade.celeration static method of the Uize.Fade class. You may be calling it on the Uize.Fade context, but this utility function's implementation doesn't even reference the this keyword - it just doesn't care about the context on which it is called. You could, in fact, assign this static method to a local variable and then call it using that local variable, and it would behave in just the same way as if it were being called on the class. Consider the following example...

EXAMPLE

var celeration = Uize.Fade.celeration;
alert (celeration (.5,.5) (.3));  // alerts the text ".18"

In the above example, our local variable celeration is a function reference. When that local variable is used to call the function in order to generate an acceleration/deceleration curve function, everything works just fine. The alert statement alerts the result of calling the generated curve function with the progress amount of .3, yielding the curve-adjusted blend amount of .18. Because the Uize.Fade.celeration static method doesn't operate on a Uize.Fade subclass or need a Uize.Fade subclass as its context when it is called, there's no compelling reason for it to be inheritable, and so it is not.

9.2.4. When Statics Should Be Inheritable

Whenever a static method or property is intended to be unique to its class context, and it is also desirable for that method or property to be inherited by subclasses, then it should not be registered as non-inheritable.

Examples of some static methods in the Uize.Class base class that are inheritable are the Uize.Class.set, Uize.Class.get, Uize.Class.toggle, Uize.Class.fire, Uize.Class.wire, Uize.Class.subclass, and Uize.Class.stateProperties methods. All of these methods act on the class context. So, for example, calling Uize.Class.subclass is different to calling Uize.Widget.subclass. The Uize.Class.subclass method, which is inherited by all Uize.Class subclasses, uses the context of the class on which it is called. Calling the Uize.Class.subclass method returns a subclass of Uize.Class, while calling the Uize.Widget.subclass method returns a subclass of Uize.Widget - even though both of these methods reference the same function for their implementation (in other words, the statement alert (Uize.Class.subclass == Uize.Widget.subclass) would alert the text "true").

9.2.5. Inheritability of Static Features is Noted in Module References

All static features of modules of the UIZE JavaScript Framework are noted as being either inheritable or non-inheritable in the reference documentation for those features.

The inheritability of the static features of a module is noted in the IMPLEMENTATION INFO notes of the reference documentation for the features in the module's reference. So, for example, if you went to the reference documentation for the Uize.Class.set static method of the Uize.Class base class, you would see it noted that the feature is inherited by subclasses. In contrast, if you went to the reference documentation for the Uize.Fade.blendValues static method of the Uize.Fade class, you would see it noted that the feature is not inherited by subclasses.

9.3. Singletons

The UIZE JavaScript Framework provides support for singletons in the form of the Uize.Class.singleton static method that is implemented in the Uize.Class base class and is inherited by all Uize.Class subclasses.

9.3.1. Every Class Supports Singletons

Because singleton support is implemented in the Uize.Class base class, a singleton for any Uize.Class subclass can be created by calling the singleton static method on the class.

EXAMPLE

var fileSystem = Uize.Services.FileSystem.singleton ();

In the above example, the Uize.Services.FileSystem class is a subclass of the Uize.Service class, which is itself a subclass of the Uize.Class base class. Therefore, a singleton can be created using the Uize.Services.FileSystem.singleton static method that is inherited from Uize.Class.

9.3.2. A Single Singleton

As expected with singletons, multiple separate calls to the singleton static method will return a reference to the same singleton.

EXAMPLE

var
  fileSystemSingleton1 = Uize.Services.FileSystem.singleton (),
  fileSystemSingleton2 = Uize.Services.FileSystem.singleton ()
;
alert (fileSystemSingleton1 === fileSystemSingleton2);  // alerts "true"

In the above example, both the fileSystemSingleton1 and fileSystemSingleton2 variables are set to be a singleton of the Uize.Services.FileSystem class. Therefore, when their values are compared using a strict equality, they are found to be equal.

9.3.3. Compelling Use Cases for Singletons

A typical and compelling use case for the singleton feature is with service classes, where one typically wants to share a single instance of a service class amongst multiple disparate users of the service.

In the case of service classes, one may instantiate and set up the singleton of a service in the environment (which will likely be a Web page for services that are to run in Web applications). Then, in other code that is to run in the environment and use the service, the singleton of the service can simply be obtained and used - the service doesn't need to be initialized by all the users because the same shared singleton of the service class is initialized and set up in the environment.

9.3.4. Singleton Scope

As a convenience, the singleton static method provides a way to optionally create singletons in a custom scope.

A scope for a singleton of a class is specified using the optional scopeSTR parameter, as follows...

EXAMPLE

var fileSystemForBuildScripts = Uize.Services.FileSystem.singleton ('buildScripts');

In the above example, a singleton of the Uize.Services.FileSystem class is being created in a scope we have decided to call "buildScripts". Now, any other build script code that wants to get a reference to this singleton can perform the same operation, specifying 'buildScripts' as the value for the scopeSTR parameter. With this kind of arrangement, other code can now use the same Uize.Services.FileSystem service class in a different scope. In different scopes, the different singletons may be set up with different service adapters and initialized differently to perform different I/O.

9.3.4.1. A Single Singleton Per Scope

The exception to the rule of a single singleton is when using the singleton scope feature.

EXAMPLE

var
  fileSystemInScopeA = Uize.Services.FileSystem.singleton ('scopeA'),
  fileSystemInScopeB = Uize.Services.FileSystem.singleton ('scopeB')
;
alert (fileSystemInScopeA === fileSystemInScopeB);  // alerts "false"

In the above example, two singletons of the Uize.Services.FileSystem class are being created: one in a scope we've called "scopeA" and the other in a scope we've called "scopeB". When the values of the fileSystemInScopeA and fileSystemInScopeB variables are compared using a strict equality, they are found to not be equal. However, if we called the singleton method multiple times for "scopeA", we would always get back the same singleton for this scope, and similarly with "scopeB".

9.3.4.2. Designing Classes to Support Singleton Scope

In order to be able to take advantage of the singleton scope feature, your classes need to be designed correctly.

In order to correctly support singletons in different scopes, your class should not store state for a singleton as statics on the class, but should instead use instance properties. Provided your classes are designed correctly, you can have multiple singletons instantiated for different scopes that you define, so that different sets of distributed code can share their own singleton instances, and for each differently scoped singleton the singleton may be initialized or set up in a different way.

9.3.4.3. Singleton Scope is Globalish

A singleton scope is effectively global for a given class (so, not global in an absolute sense, but globalish).

Therefore, if you have distributed code that is making use of the singleton feature and you are specifying a custom scope, you need to have a good degree of confidence that you won't have totally different code trying to use the same scope name for a singleton of a class where the initialization and usage of the singleton is different. This is the age old problem with things that are global in nature. The globalness of singletons is both the upside and the downside, so you should use the singleton feature sparingly.

9.3.5. Initial State for Singletons

A singleton can be created with an initial state by specifying the optional propertiesOBJ second parameter.

EXAMPLE

var fileSystemForBuildScripts = Uize.Services.FileSystem.singleton ('',{defaultEncoding:'utf8'});

In the above example, a singleton is being created for the Uize.Services.FileSystem class. Because the first parameter of the singleton method is the singleton scope, if we want to use the default scope we need to specify an empty string. The second parameter is the state for the singleton, where the value is an object that will be passed to the constructor of the class if the singleton has not yet been created for the scope, or used in a call to the set method if the singleton already exists in the scope.

9.3.6. Use the Singleton Feature Sparingly

While there are a few compelling use cases for singletons, one should nevertheless use the singleton feature sparingly and with caution.

The very advantage of singletons, which is that they can be shared by disparate pieces of code, can also become a disadvantage. Singletons can be regarded as being, in effect, globals in the scope of the class on which they are created, or the singleton scope in which they are created. This consequence brings with it the kinds of drawbacks that globals have more generally.

Singletons aren't as egregious as global variables, and the singleton scope facility goes some way to alleviating the globalish nature of singletons. Nevertheless, before resorting to using singletons, consider first if there are other alternatives. In UIZE, you will find singletons mostly advocated for use with service classes.

9.4. The "no new" Mechanism

The JavaScript new operator is optional when creating instances of Uize.Class subclasses, and you can make the new operator optional for your own object constructors using the newly added Uize.noNew static method.

9.4.1. Creating Instances of Uize Classes

Because the Uize.Class base class utilizes the "no new" mechanism, one can create instances of any Uize.Class subclass either using the new operator or not.

EXAMPLE

// this works
var mySlider1 = new Uize.Widget.Bar.Slider ({minValue:0,maxValue:100,value:50});

// Look ma, no "new"!!!
var mySlider2 = Uize.Widget.Bar.Slider ({minValue:0,maxValue:100,value:50});

9.4.2. All Uize Classes Get the Benefit

Because of the way in which the "no new" mechanism is implemented in the Uize.Class base class, any class that is derived from a Uize.Class base class using the subclass method gets the same benefit, including classes that you create for your own applications.

This means, for example, that any widget class you create by subclassing the Uize.Widget class will get the same benefit. Consider the following example...

EXAMPLE

// we create a widget class
var MyWidgetClass = Uize.Widget.subclass ();

// this works
var myWidgetClassInstance1 = new MyWidgetClass ();

// Look ma, no "new"!!!
var myWidgetClassInstance2 = MyWidgetClass ();

9.4.3. Applies for Other Uize Objects

The "no new" mechanism, that is implemented in the Uize.noNew static method, has been applied to various other Uize objects (such as the Uize.Color object) that are lightweight objects rather than full Uize.Class subclasses.

So, for example, one can create instances of the Uize.Color object or the Uize.Str.Builder object without needing to use the new operator. Consider the following example...

EXAMPLE

// this works
var fuchsia = new Uize.Color ('#ff0fff');

// Look ma, no "new"!!!
var olive = Uize.Color ('#808000');

9.4.4. Using the Uize.noNew Method

An object constructor that supports the "no new" mechanism can easily be created using the Uize.noNew static method.

In cases where you're creating Uize.Class subclasses, you don't need to worry about the Uize.noNew method because the "no new" mechanism is built right into the Uize.Class base class, so all Uize classes get the benefit. However, in cases where you're defining your own lightweight objects, you can use the Uize.noNew method to create an object constructor where the new operator is optional. Consider the following example...

EXAMPLE

// define the Food object
var Food = Uize.noNew (
  function (name,type) {
    this.name = name;
    this.type = type;
  }
);

// create an instance of Food using the new operator
var apple = new Food ('apple','fruit');
alert (apple.type);  // alerts the text "fruit"

// create an instance of Food without using the new operator
var rice = Food ('rice','grain');
alert (rice.type);  // alerts the text "grain"

What you'll notice from the above example is that the Uize.noNew method is quite simple - it takes a single parameter, which is the constructor function that initializes the new instance. This means that you can easily take an existing object constructor function and upgrade it to one that supports the "no new" mechanism by wrapping it inside a call to the Uize.noNew method, which then returns a wrapper constructor that becomes your new object constructor. Consider the following before-and-after example...

BEFORE

// must always use "new" with this constructor
function Food (name,type) {
  this.name = name;
  this.type = type;
}

AFTER

// "new" is optional with this constructor
var Food = Uize.noNew (
  function (name,type) {
    this.name = name;
    this.type = type;
  }
);

Notice that you need to assign the result of the Uize.noNew method call, and so your original constructor function no longer should have the name.