import { Widget } from '@epiphanysoft/oo';
class MyWidget extends Widget {
//
}In addition to what has already been described, the Widget class has
several (readonly) instance properties as well as a handful of useful methods.
Widget maintains the following properties to describe an instance's current life cycle
state. These are:
constructingStarts astrueand is cleared on exit from theconstructor. This will betrueduring the initial configuration phase as well as during thectorcall sequence.configuringSet totrueprior to callingconfigureand cleared afterward.destroyingSet totrueon entry todestroy()(never cleared). This will betrueduring thedtorcall sequence.destroyedSet totrueon exit fromdestroy(). This will be set totrueafter thedtormethods are called.metaA readonly reference to the class's Meta class.
Widget defines the following life cycle methods:
In addition, Widget defines these methods:
It is recommended that derived classes not implement a constructor, but instead take
advantage of the ctor method chain. To facilitate this, the
constructor of Widget takes a single Object parameter or "config object". Prior to
invoking the ctor method chain, the config object's properties are used to set the
widget's configuration properties using the configure
method.
Widget also defines a destroy method that is used to cleanup any resources that the GC
(garbage collector) won't handle. It is also recommended that derived classes not override
the destroy method, but instead implement a dtor method.
These methods ensure the following:
- The
constructingandconfiguringproperties track construction. - The
destroyinganddestroyedproperties track destruction. - The
destroymethod is harmless to call after it has been called.
The ctor method is like a constructor in that it is only called once as the instance
is being created.
class MyClass extends Widget {
ctor () {
// do constructor-like things
}
}The major difference between a ctor and a standard constructor is how ctor methods
are called. Widget ensures that all ctor implementations in the class hierarchy are
called once and in the proper order.
For example:
class MyClass extends Widget {
ctor () {
// do constructor-like things
console.log('MyClass ctor');
}
}
class MyDerived extends MyClass {
ctor () {
// NOTE: no super.ctor() call
// do more constructor-like things
console.log('MyDerived ctor');
}
}
let inst = new MyDerived();> MyClass ctor
> MyDerived ctor
The ctor implementations are called "top down" from the Widget class to the
derived-most class.
As with ctor calls, the Widget class ensures that the proper dtor calls are made.
This saves derived classes from overriding destroy and remembering to call
super.destroy() at the proper time.
For example:
class MyClass extends Widget {
dtor () {
console.log('MyClass dtor');
}
}
class MyDerived extends MyClass {
dtor () {
console.log('MyDerived dtor');
}
}
let inst = new MyDerived();
inst.destroy();> MyDerived dtor
> MyClass dtor
The dtor implementations are called "bottom up" from the derived-most class upwards to
the Widget class.
There are times when some manual involvement in the life cycle is needed. In these cases
there are the construct and destruct methods.
The construct method is called by the constructor, passing along all of its arguments.
The construct method in Widget invokes the ctor method chain.
Conversely, the destruct method is called by the destroy method in Widget. The dtor
method chain is invoked by Widget's implementation of destruct.
class MyClass extends Widget {
ctor () {
console.log('MyClass ctor');
}
dtor () {
console.log('MyClass dtor');
}
}
class MyDerived extends MyClass {
ctor () {
console.log('MyDerived ctor');
}
dtor () {
console.log('MyDerived dtor');
}
construct (config) {
console.log('My Derived before construct');
super.construct(config);
console.log('My Derived after construct');
}
destruct () {
console.log('My Derived before destruct');
super.destruct();
console.log('My Derived after destruct');
}
}
let inst = new MyDerived();
console.log('---');
inst.destroy();Produces:
> MyDerived before construct
> MyClass ctor
> MyDerived ctor
> MyDerived after construct
> ---
> MyDerived before destruct
> MyDerived dtor
> MyClass dtor
> MyDerived after destruct
This method applies the properties of a config object to the corresponding
config properties. This method is internally called by construct
in Widget.
To ensure that config property ordering is transparent, this process is not as simple as
calling Object.assign(). For example:
@define({
config: {
host: null,
port: null
}
})
class MyWidget extends Widget {
hostUpdate (value) {
this.connection.address = value + ':' + this.port;
}
}
let inst = new MyWidget({
host: '127.0.0.1',
port: 123
});In the code above, the hostUpdate method consumes not only the new value for the host
config property but also the port config property. It is therefore important that the
port config property be the correct value.
The same solution to the above also applies when setting config properties after object creation:
inst.configure({
host: '192.168.1.10',
port: 321
})See here for more information on this process.
There is a static and non-static version of this property. It holds the Meta
class instance for the corresponding Widget-derived class.