EXPLAINERS Set Get Properties

1. Introduction

The UIZE JavaScript Framework eases state management in complex applications by implementing state oriented features, such as its set-get properties.

A cornerstone of the framework's state oriented design is its set-get property mechanism. This mechanism provides facilities for registering and managing state interfaces for classes and their instances.

1.1. State Oriented Programming

1.1.1. The Joy of Reflection

An invaluable feature, when using JavaScript in the context of a Web document, is reflection.

The reflection of an element node's attributes and style properties to the JavaScript scripting language, combined with the ability of a document to modify itself through that language, provides an intuitive and lightweight way to modify the state of the user interface, without understanding the inner working of how state change is managed by the browser's layout / rendering engine.

This is a huge load off the mind of a Web UI engineer, since undoubtedly there are any number of very clever ways in which handling state change is optimized for different scenarios, and undoubtedly there are any number of specific updater methods that are kicked off in carefully chosen order under different circumstances. The UI engineer doesn't have to care about what the browser has to do in order to synchronize the display to a changed state. The change could be minimal, or it could be very major.

1.1.2. Make It So

The UIZE JavaScript Framework adopts a similar tack, providing a state interface through its set-get properties mechanism.

In spirit, a widget class should expose its state through the set-get properties, and should register onChange handlers for these properties, as appropriate, in order to synchronize the UI to a changed state. The application developer using the widgets then does not have to worry about a suite of updater methods and understand why two should be used in a specific order in one case, but three should be used in any order in some other case (for example). We call this the "make it so" principle. You tell an instance what state it should be in, and it figures out how to get all its junk into that new state.

1.2. Benefits of the Set-get Property Mechanism

The set-get property mechanism provides a convenient system for managing set-get properties, including providing an easy way for code to...

set defaults for a class's set-get properties
register code to handle changes in the values of set-get properties
register code to conform values of set-get properties
register aliases for set-get properties to ease backwards compatibility

1.3. Set-get Properties vs. Regular Properties

It is not uncommon for the implementation of a class to utilize a combination of regular old properties and set-get properties.

Regular properties are any old properties assigned on the class or its instances. Set-get properties are specifically registered through the MyClass.registerProperties static method inherited from the Uize.Class base class.

1.3.1. Why "Set-get" Properties?

Set-get properties are named thus because they are accessed through the set and get methods, unlike regular properties that are accessed normally.

In order to set a value for a registered set-get property, the set method must be used in order to obtain the benefits of onChange handlers, conformer functions, the Change.* virtual event, and other facilities provided in the set-get property mechanism. In a nutshell, set-get properties are properties on steroids. They have more intelligence.

2. The Basics

At the most basic level, using set-get properties is pretty straightforward. Let's consider a few common operations...

2.1. Registering a Set-get Property

Set-get properties can be easily registered for a class, using the MyClass.registerProperties static method that is inherited from the Uize.Class base class, as follows...

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({_propertyName:'propertyName'});

The above statement registers the set-get property with the public name propertyName and the private name _propertyName.

2.2. Setting a Set-get Property's Value

Once a set-get property has been registered, its value can be easily set using the set instance method that is inherited from the Uize.Class base class, as follows...

var myClass = new MyClass;
myClass.set ({propertyName:'propertyValue'});

2.3. Getting a Set-get Property's Value

Once a set-get property has been registered, its value can be easily queried using the get instance method that is inherited from the Uize.Class base class, as follows...

var myClass = new MyClass;
myClass.set ({propertyName:'propertyValue'});
alert (myClass.get ('propertyName'));

In the above code, the alert statement will display the text propertyValue in the alert dialog.

2.4. Setting During Construction

Once a set-get property has been registered, its value can be easily set during construction of a new instance of your class, as follows...

var myClass = new MyClass ({propertyName:'propertyValue'});
alert (myClass.get ('propertyName'));

In the above code, the alert statement will display the text propertyValue in the alert dialog.

2.5. Now It Gets Interesting

What good is a set-get property mechanism if it doesn't facilitate something useful?

Conforming a value is one such thing that is facilitated by the set-get property mechanism of the UIZE JavaScript Framework. When registering set-get properties it is possible to register a conformer function to enforce valid values. Let's consider an example...

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    value:0
  }
});
var myClass = new MyClass ({percentLoaded:200});
alert (myClass.get ('percentLoaded'));

In the above code, the alert statement will display the text 100 in the alert dialog.

This example uses the extended form for registering a set-get property that lets you define the property's "profile" (but we'll get into that in more detail a bit later). In the example, a conformer function is registered for the percentLoaded set-get property, that conforms the property's value so that it doesn't fall outside of the range of 0 to 100. So, even though the code is trying to set the property's value to 200 during construction of an instance, the conformer conforms the value to not fall out of bounds.

Custom conformer functions are discussed in more detail in the section Conformer Function.

3. More on Setting Values

3.1. Always Use Set

Values for set-get properties should always be set using the set method.

This is the only way to ensure that conformer functions and onChange handlers registered for set-get properties are invoked, and that handlers registered for the Changed.[propertyName] Virtual Event and the Changed.* Wildcard Event are executed.

INSTEAD OF...

this._propertyName = 'propertyValue';

USE...

this.set ({propertyName:'propertyValue'});

3.2. Setting a Value Using the Private Name

It is possible, within the code module that registers a particular set-get property, to set a value for that property using the property's private name.

For example, if a module registers a set-get property with the public name propertyName and the private name _propertyName, then code within that module could set a value for that property, using either the public or private name, as follows...

this.set ({propertyName:'propertyValue'});   // using the public name
this.set ({_propertyName:'propertyValue'});  // using the private name

Both of the above statements would have the same effect, provided that this code is within the implementation for some instance method within the code module that defines the class and all its set-get properties (and not some subclass or application code that uses the class). The benefit of using the private name is that it will be scrunched down by the Scruncher, thereby reducing the scrunched code size.

3.3. Setting a Value for a Dynamically Selected Set-get Property

Occasionally, it becomes necessary to set the value for a set-get property whose name is dynamically determined by an expression, or passed as a parameter to some method.

This can be done by using the form of the set method that takes two parameters: the name of a set-get property, and the value it should be set to.

Consider the following example...

_classPrototype.increment = function (_propertyName,_amount) {
  this.set (_propertyName,this.get (_propertyName) + _amount);
};

In the above example, an instance method has been defined that accepts the name of a set-get property as its first parameter and an increment amount as its second parameter. Once the current value for the specified property has been retrieved using the get method, it is then incremented and set using the form of the set method that takes property name and value as its two parameters. Easy.

3.4. Toggling a Set-get Property

The Uize.Class base class provides a convenient way to toggle the value of a boolean set-get property, in the form of the toggle instance method.

INSTEAD OF...

myClass.set ({propertyName:!myClass.get ('propertyName')});

USE...

myClass.toggle ('propertyName');

3.5. Setting Values for Multiple Set-get Properties

The set method lets you set values for multiple set-get properties in a single call.

The parameter passed to the set method is an object, containing values for all the set-get properties you wish to set.

INSTEAD OF...

myClass.set ({property1Name:'property1Value'});
myClass.set ({property2Name:'property2Value'});
myClass.set ({property3Name:'property3Value'});

USE...

myClass.set ({
  property1Name:'property1Value',
  property2Name:'property2Value',
  property3Name:'property3Value'
});

In the above example, values for the property1Name, property2Name, and property3Name set-get properties are being set. There is no limit to the number of set-get properties that can be set in the same call to the set method.

The performance benefits from calling the set method only once include...

you're dereferencing the instance to access the set method only once, so less processor overhead
you're calling the method's function only once, so less processor overhead
you're creating a fresh object for the property values bundle only once, so less processor overhead
any onChange handlers registered for all of the set-get properties will only get executed once for changes in any or all of the set-get properties' values, rather than for a change in each property's value (this is a more esoteric subject that is covered in more detail later on)

4. More on Getting Values

4.1. Using the Private Name

When accessing the value for a particular set-get property inside the code module that registers that property, the private name can be used.

For example, if a module registers a set-get property with the public name propertyName and the private name _propertyName, then code within that module could get the value for that property, using either the public or private name, as follows...

alert (this.get ('propertyName'));  // using the public name
alert (this._propertyName);         // using the private name

Both of the above statements would have the same effect, provided that this code is within the implementation for some instance method within the code module that defines the class and all its set-get properties (and not some subclass or application code that uses the class). The benefit of using the private name is that it will be scrunched down by the Scruncher, thereby reducing the scrunched code size.

4.2. Getting a Value for a Dynamically Selected Set-get Property

Because the get method accepts a string parameter, specifying the name of the set-get property being accessed, getting values for properties that are dynamically determined by an expression (or passed as a parameter to some method) is easy.

Consider the following example...

_classPrototype.increment = function (_propertyName,_amount) {
  this.set (_propertyName,this.get (_propertyName) + _amount);
};

In the above example, an instance method has been defined that accepts the name of a set-get property as its first parameter and an increment amount as its second parameter. The method then proceeds to increment the specified set-get property by the specified amount. Nothing special needs to be done to access the set-get property's value because the get method already takes a parameter for specifying the property's name.

4.3. Getting Values for Multiple Set-get Properties

When an array of set-get property names is supplied to the get method, the method obtains the values for the specified properties and returns them in an object, where the keys of the object are the property names and the values are, well, the values.

Let's consider an example...

myClass.set ({
  property1Name:'property1Value',
  property2Name:'property2Value',
  property3Name:'property3Value',
  property4Name:'property4Value',
  property5Name:'property5Value'
});
var values = myClass.get (['property1Name','property2Name','property3Name']);

In the above example, the values for five set-get properties are being set in the set method call. Then, the get method is being called with an array type value that specifies the names of just three of those properties, and its result is assigned to the values variable. After the code is executed, values will be an object with the contents...

{
  property1Name:'property1Value',
  property2Name:'property2Value',
  property3Name:'property3Value'
}

4.3.1. Transferring State

The multiple set-get properties form of the get method is useful for obtaining "bundles of state" that can then be transferred to other instances using the set method.

This convenience provides for some code size optimization.

INSTEAD OF...

myClass2.set ({property1Name:myClass1.get ('property1Name')});
myClass2.set ({property2Name:myClass1.get ('property2Name')});
myClass2.set ({property3Name:myClass1.get ('property3Name')});

USE...

myClass2.set (myClass1.get (['property1Name','property2Name','property3Name']));

The laborious first form is calling the get and set methods three times - one for each set-get property - in order to transfer the state of the properties from the instance myClass1 to myClass2. The more concise second form performs only one get to obtain the state for all three properties from myClass1, and then supplies that property values object to the set method in order to set those values on myClass2.

Besides just being more concise, the second form also may have performance benefits when writing classes with set-get properties that have sophisticated onChange handler code, in some cases avoiding multiple redundant updates from being triggered. The subject of onChange handlers and update optimization will be discussed in more detail later on.

4.4. Getting Values for All Set-get Properties

When no parameter is specified in the call to the get method, the method obtains the values for all the registered properties and returns them in an object, where the keys of the object are the property names and the values are the property values.

Let's consider an example...

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _property1Name:{name:'property1Name',value:'property1Value'},
  _property2Name:{name:'property2Name',value:'property2Value'},
  _property3Name:{name:'property3Name',value:'property3Value'}
});
var
  myClass = new MyClass,
  values = myClass.get ()
;

After the above code is executed, the values variable will be an object with the contents...

{
  property1Name:'property1Value',
  property2Name:'property2Value',
  property3Name:'property3Value'
}

5. More on Registering Set-get Properties

As illustrated earlier, registering set-get properties is done by calling the MyClass.registerProperties static method on your class.

5.1. Property Profiles

The parameter passed to this method should be an object, whose properties' names are the private/internal names for the set-get properties being registered, and where the value for each property is a profile for each set-get property.

The profile for a set-get property can take two forms: The Minimal Profile, and The Complete Profile.

5.2. The Minimal Profile

The minimal profile is a shorthand for registering a set-get property, where the only thing being registered is a mapping between a private name and a public name.

EXAMPLE

MyClass.registerProperties ({_propertyName:'propertyName'});

In the above example, a set-get property is being registered, with the private name _propertyName and the public name propertyName. It can be accessed in the class' implementation in an instance method as this._propertyName, set in the class' implementation in an instance method as this.set ({_propertyName:'propertyValue'}), and set outside of the class' implementation in application code in a statement such as myClass.set ({propertyName:'propertyValue'}) (where myClass is an instance of the class MyClass).

IMPORTANT

When registering set-get properties using the minimal profile, those properties cannot utilize the more advanced features of set-get properties, such as onChange handlers, conformer functions, value defaults, etc. that are available when using The Complete Profile. Without specifying an initial value for set-get properties using the value property of The Complete Profile, set-get properties will be initialized to the value undefined when creating new instances of a class.

5.3. The Complete Profile

Unlike The Minimal Profile, the complete profile lets you utilize the more advanced features of set-get properties, such as onChange handlers, conformer functions, value defaults, etc.

EXAMPLE

MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    onChange:function () {
      if (this.isWired) this.setNodeValue ('percentLoaded',this._percentLoaded);
    },
    value:0
  }
});

In the above example, a set-get property with the public name percentLoaded and the private name _percentLoaded is being registered.

The name property in the profile specifies the property's public name. A conformer function is registered that is executed each time there is an attempt to set a new value for percentLoaded, and this function's implementation ensures that the property's value does not fall outside of the valid range of 0 to 100. An onChange handler function is registered that is executed each time the value of percentLoaded changes, and this function's implementation displays the current value in a node in the document (we're assuming this code is transplanted from a widget class' implementation). Finally, the value property in the profile specifies the initial value for percentLoaded, for all freshly minted instances of MyClass.

5.4. Mixing Profile Types

For any set-get property, you can use either The Minimal Profile or The Complete Profile.

So, when registering multiple set-get properties in one call to the MyClass.registerProperties static method, you can use a combination of both profile types, as suits your needs.

EXAMPLE

MyClass.registerProperties ({
  _propertyName:'propertyName',
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    onChange:function () {
      if (this.isWired) this.setNodeValue ('percentLoaded',this._percentLoaded);
    },
    value:0
  }
});

In the above example, two set-get properties are being registered. propertyName is being registered using The Minimal Profile, while percentLoaded is being registered using The Complete Profile.

5.5. Public Name Defaulting

If a public name is not specified for a set-get property, the public name is defaulted to be same as the private name. This offers two intriguing flavors of set-get properties (discussed below).

When registering a set-get property, one specifies a mapping between its private name and its public name. With the The Minimal Profile, this is done by specifying a string value for the key that represents the property's private name. With The Complete Profile, this is done by specifying a value for the name property in the profile object. Now, if the public name is undefined, null, or an empty string, then the public name will be defaulted to the private name.

5.5.1. Naked Set-get Properties

By using an unscrunchable (effectively public) name for a set-get property's private name, and not specifying the public name so that it defaults to its unscrunchable private name, you end up with a "naked" set-get property whose value can be accessed outside of the implementation for a class (such as in application code) with a simple dereferencing.

EXAMPLE

MyClass.registerProperties ({
  prop1:null,        // private & public name is prop1 - accessible as myClass.prop1
  prop2:'',          // private & public name is prop2 - accessible as myClass.prop2
  prop3:{value:10},  // private & public name is prop3 - accessible as myClass.prop3
  prop4:'prop4',     // SILLY!!! don't specify public name here - let it default
  prop5:{            // SILLY!!! don't specify public name here - let it default
    name:'prop5',
    value:10
  }
});

In the above example, all the set-get properties have public names that are the same as their unscrunchable private names. While prop1 should still be set using the set method, as in myClass.set ({prop1:'value'}), it can be accessed using a simple dereferencing, as in myClass.prop1.

USE WITH CAUTION

This technique doesn't offer the code size benefits of scrunching, but in certain exceptional cases it may offer sufficiently compelling performance benefits where it is more desirable to avoid an extra method call in order to access a set-get property's value. Using this technique should be the exception rather than the norm. It's not recommended, but it doesn't hurt to be aware of it.

5.5.2. Private Set-get Properties

By using a scrunchable name for a set-get property's private name (this is the typical the case, of course), and by not specifying the public name so that it defaults to its private name, you end up with what is effectively a private set-get property that can only be used within the class that defines it.

EXAMPLE

MyClass.registerProperties ({
  _prop1:null,         // private & public name is _prop1 - will be scrunched
  _prop2:'',           // private & public name is _prop2 - will be scrunched
  _prop3:{value:10},   // private & public name is _prop3 - will be scrunched
  _prop4:'_prop4',     // WRONG!!! string literal '_prop4' won't be scrunched
  _prop5:{             // WRONG!!! string literal '_prop5' won't be scrunched
    name:'_prop5',
    value:10
  }
});

In the above example, the set-get properties _prop1, _prop2, and _prop3 are all private. Because their names will be scrunched, these names cannot be relied upon by application code outside of the class' implementation. Code inside the class can, however, access the properties (eg. this.set ({_prop1:'value'}) or this._prop1). The benefit of a private set-get property is that you can employ the mechanisms that come with set-get properties, such as onChange handlers and conformer functions.

IMPORTANT

In contrast to _prop1, _prop2, and _prop3, _prop4 and _prop5 are not correctly registered as private set-get properties because the public name is specified in a string literal, which is not scrunched. So, these two set-get properties will be accessible outside the class' implementation as _prop4 and _prop5. They won't be private!

5.6. Public Aliases

Through a facility of the name property in the profile of a set-get property, multiple alternate public names can be specified for a set-get property.

Basically, when you specify the public name of a set-get property you can specify a pipe-separated list of public names, of the form...

'publicName1|publicName2|...|publicNameN'

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({_title:'title|displayTitle'});
var myClass = new MyClass;
myClass.set ({displayTitle:'HELLO'});
alert (myClass.get ('title'));

In the above example, a set-get property is being registered for the class MyClass, with the two public names title and displayTitle. When creating an instance of MyClass, a call to set the value for displayTitle is equivalent to setting the value for title, since both public names map to the same underlying set-get property. So, in the above code, the alert statement will display the text HELLO in the alert dialog.

Of course, aliases can be specified also when using The Complete Profile, as in...

MyClass.registerProperties ({
  _title:{
    name:'title|displayTitle',
    conformer:function (_value) {
      // coerce to a string, default to empty string for null or undefined
      return _value == undefined ? '' : _value + '';
    },
    value:'untitled'
  }
});

BENEFITS

One of the benefits of public aliases for set-get properties is that it eases the evolution of interfaces over time. One can officially change the name of a set-get property, while deprecating the old name and continuing to support it for some time for backwards compatibility.

5.7. Initial Value

When using the The Complete Profile form to register a set-get property, an initial value for a property can be specified using the value property of the profile.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _propertyName:'propertyName',
  _active:{
    name:'active',
    onChange:function () {
      // do something
    }
  },
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    value:0
  }
});
var myClass = new MyClass;
alert (myClass.get ('propertyName'));   // will alert the value undefined
alert (myClass.get ('active'));         // will alert the value undefined
alert (myClass.get ('percentLoaded'));  // will alert the value 0

If a set-get property is registered using The Minimal Profile, or if it is registered using the The Complete Profile but no value property is specified in the profile, then the set-get property will be set to undefined for all freshly minted instances.

5.8. onChange

When using the The Complete Profile form to register a set-get property, an onChange handler function can be registered to respond to changes in the property's value.

EXAMPLE

MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    onChange:function () {
      if (this.isWired) this.setNodeValue ('percentLoaded',this._percentLoaded);
    },
    value:0
  }
});

In the above example, the _percentLoaded set-get property is being registered. An onChange handler function is registered that is executed each time the property's value changes, and this function's implementation displays the current value in a node in the document (we're assuming this code is transplanted from a widget class' implementation).

5.8.1. Executed Even on First Change

An onChange handler registered for a set-get property is executed every time that the property's value changes - even when the property is initialized to an initial value during construction of an instance.

During construction of an instance of a class, the value of each registered set-get property starts out as undefined. If an initial value is specified for the property (using the value property of the profile), then the value is set during construction, and a change in value from undefined to the specified initial value will trigger any onChange handler registered for the property.

EXAMPLE 1

var MyClass = Uize.Class.subclass ();
function _updateArea () {
  this.set ({area:this._width * this._height});
}
MyClass.registerProperties ({
  _area:'area',
  _height:{
    name:'height',
    value:10,
    onChange:_updateArea
  },
  _width:{
    name:'width',
    value:10,
    onChange:_updateArea
  }
});
myClass = new MyClass;
alert (myClass.get ('area'));  // will alert the value 100

In the above example, the area set-get property is registered. An onChange handler registered for each of the width and height set-get properties updates the value of area to be the product of width and height. Now, even though there is no call to the set method to set values for width and height, the value of area is correct immediately after construction of the myClass instance. This is because the onChange handler for width and height is executed the first time their values change from undefined to their registered initial value of 10.

This behavior can be both a good thing and a bad thing. Without a question, it's something that needs to be kept in mind when writing onChange handler code. In the previous example, it was actually a convenient behavior. However, in some cases it may be necessary to conditionalize some code inside an onChange handler in order to make sure it's not executed too early on in the setup of an instance, such as when not every aspect of the instance is fully set up and ready/safe for the onChange code to be executed.

EXAMPLE 2

MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    onChange:function () {
      if (this.isWired) this.setNodeValue ('percentLoaded',this._percentLoaded);
    },
    value:0
  }
});

In the above example, an onChange handler function is registered that is executed each time the value of percentLoaded changes, and this function's implementation displays the current value in a node in the document (we're assuming this code is transplanted from a widget class' implementation).

In this particular case, we don't want to touch the DOM during construction of the instance and before the widget has been wired up, since this would drastically limit the flexibility of the widget class and how application code using the class could control setup of the application logic. So, in the onChange handler's implementation the code is conditionalized to check on the wired state of the instance. If the instance isn't wired yet, the DOM is not touched. Other cases like this may arise, where only certain aspects of handling a change in value of a set-get property should be enabled during construction.

5.8.2. Only Executed on Changes

An onChange handler registered for a set-get property is only executed when the value of the property changes - not on each attempt to set its value.

So, for example, repeated attempts to set a set-get property to the same value will not result in repeated execution of the onChange handler - only for the first set that changes the property's value.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    onChange:function () {alert (this._percentLoaded)},
    value:0
  }
});
var myClass = new MyClass;         // will alert the text 0
myClass.set ({percentLoaded:10});  // will alert the text 10
myClass.set ({percentLoaded:10});  // does nothing (because value doesn't change)
myClass.set ({percentLoaded:10});  // does nothing (because value doesn't change)
myClass.set ({percentLoaded:10});  // does nothing (because value doesn't change)

In the above example, there are four attempts to set the value of the percentLoaded set-get property to 10. Only the first of those results in the property's value actually changing to 10, so the onChange handler is only executed for the first of those sets, and you see the alert with the text "10" only once.

Similarly, if a conformer function is defined for the set-get property, then multiple attemps to set different values could still result in the value changing only once, if, for example, the registered conformer function has the effect of constraining the value of the property to a range.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    onChange:function () {alert (this._percentLoaded)},
    value:0
  }
});
var myClass = new MyClass;          // will alert the text 0
myClass.set ({percentLoaded:100});  // will alert the text 100
myClass.set ({percentLoaded:200});  // does nothing (because value stays 100)
myClass.set ({percentLoaded:300});  // does nothing (because value stays 100)
myClass.set ({percentLoaded:400});  // does nothing (because value stays 100)

In the above example, there are four attempts to set the value of the percentLoaded set-get property. Only the first of those results in the property's value actually changing to 100, so the onChange handler is only executed for the first of those sets, and you see the alert with the text "100" only once. The other three set attempts all result in the value being constrained to the upper limit of 100 by the conformer function, and so they produce no change in the property's value.

5.8.3. Called as Instance Method

An onChange handler registered for a set-get property is called as a method on the instance to which the set-get property belongs, so it can access other state and methods for the instance using the this keyword, including the current value for the property for which the handler is registered.

EXAMPLE

var MyClass = Uize.Class.subclass ();
function _updateArea () {
  this.set ({area:this._width * this._height});
}
MyClass.registerProperties ({
  _area:'area',
  _height:{
    name:'height',
    value:10,
    onChange:_updateArea
  },
  _width:{
    name:'width',
    value:10,
    onChange:_updateArea
  }
});

In the above example, the _updateArea function is registered as the onChange handler for both the width and height set-get properties. Even though it is just a function and not registered as an instance method for the class (public or private), it is nevertheless called as a method of the instance whose set-get properties' values are changing. This is useful, because the handler code can then access state and methods for the instance. In this particular example, the handler is able to access the updated values for the width and height set-get properties in order to calculate a new value for the area set-get property.

Being aware of this behavior, you can avoid certain redundancies in your code. For example, you can provide direct references to instance methods in your set-get property profiles, rather than redundant anonymous wrapper functions that merely call the methods.

INSTEAD OF...

MyClass.registerProperties ({
  _title:{
    name:'title',
    onChange:function () {this._updateUiTitle ()}
  }
});

USE...

MyClass.registerProperties ({
  _title:{
    name:'title',
    onChange:_classPrototype._updateUiTitle
  }
});

NOTE: The variable _classPrototype is a common convention for a captured reference to a class' prototype object. In this case, _classPrototype would be equivalent to MyClass.prototype.

5.9. Conformer Function

When using the The Complete Profile form to register a set-get property, a conformer function can be registered to ensure that the value for the property is always conformed to be a valid value.

Normally, whatever value is specified for a set-get property when calling the set method is the new value that gets set for that property. However, specifying the optional conformer allows you to register a function that should be executed before the set-get property's value actually changes, providing an opportunity to constrain or correct the value, or to abort the set so that the property's value remains unchanged.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    value:0
  }
});
var myClass = new MyClass ({percentLoaded:200});
alert (myClass.get ('percentLoaded'));

In the above code, the alert statement will display the text 100 in the alert dialog.

In the example, a conformer function is registered for the percentLoaded set-get property, that conforms the property's value so that it doesn't fall outside of the range of 0 to 100. So, even though the code is trying to set the property's value to 200 during construction of an instance, the conformer conforms the value to not fall out of bounds.

IN A NUTSHELL

When you register a conformer function for a set-get property, that function gets executed each time there is an attempt to set a value for that property. Your conformer function should expect to receive one parameter, being the new value that is being attempted to be set. Your conformer function should do what it needs to in order to conform the new value, and then return the conformed value as its result. The conformer function is called as a method on the instance to which the set-get property belongs, so it can access other state and methods for the instance using the this keyword, including the current value for the property being set.

5.9.1. The Conformer and onChange Handlers

Your conformer function is executed before the value of the set-get property is changed.

If the conformer function results in the value not changing, then any onChange handler function that you registered for the property will not get executed.

Consider the following example...

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    onChange:function () {alert (this._percentLoaded)},
    value:0
  }
});
var myClass = new MyClass;          // will alert the text 0
myClass.set ({percentLoaded:200});  // will alert the text 100
myClass.set ({percentLoaded:200});  // does nothing (because value doesn't change)
myClass.set ({percentLoaded:200});  // does nothing (because value doesn't change)

Like the previous example, the percentLoaded set-get property has a conformer function registered that ensures that its value does not fall outside of the range of 0 to 100. Additionally, an onChange handler function is registered that alerts the property's new value, each time it changes.

Now, there are three calls to set the value of percentLoaded to 200. Because the initial value of the property (as specified in the value property of the profile) is 0, the first set will result in the value 200 being constrained to 100, the percentLoaded set-get property will be set to this value, and the onChange handler will produce an alert dialog with the text 100. However, the subsequent two calls to the set method will not cause the onChange handler to be executed, because on each occasion the value 200 will be conformed to 100, there will be an attempt to set the property's value to 100, and there will be no change in its value because its value was already conformed to 100 by the first set.

5.9.2. Abort a Set

A conformer function can be used to validate a new value and effectively abort the set if a new value fails validation. In such cases, a failed validation will result in the set-get property remaining unchanged.

The way this is accomplished is for the conformer function to return the current value for the set-get property in the event that the new value fails validation. Because the conformer function registered for a set-get property is called as a method on the instance to which the property belongs, the conformer can access other state and methods for the instance using the this keyword, including the current value for the property being set.

EXAMPLE

var _emailRegExp =
  /^(([A-Za-z0-9])|(\w[-\w\.\+]*[\w\+]))@([-A-Za-z0-9]+\.)+[A-Za-z]{2,4}$/
;
MyClass.registerProperties ({
  _emailAddress:{
    name:'emailAddress',
    conformer:function (_value) {
      return (
        typeof _value == 'string' && (_value == '' || _emailRegExp.test (_value))
          ? _value
          : this._emailAddress
      );
    },
    value:''
  }
});

In the above example, the emailAddress set-get property is being registered. The value of this property should be a well formed e-mail address. It may also be empty / unset, which is its initial value as specified in the value property of its profile. The conformer function first tests that a new value being set is a string. Then, it tests that the string is either empty or, if not, it uses a regular expression to determine if the non-empty value is a correctly formatted e-mail address.

If the validation succeeds, the conformer returns the new value as its result. If validation fails, then the conformer returns the current value. Returning the current value results in the set-get property's value not changing, so this is effectively a way to abort a set.

5.9.3. More Uses of Conformers

There are any number of ways that you can conform a value, and you can basically do anything you please inside a conformer function to produce a conformed value.

That said, there are a number of typical ways that it is useful to conform the value for a set-get property.

5.9.3.1. Conform to a Range

A common use of a conformer is to keep the value of a set-get property within a desired range.

EXAMPLE 1

MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    value:0
  }
});

In the above example, the conformer for the percentLoaded set-get property keeps the value of this property within the range of 0 to 100.

EXAMPLE 2

MyClass.registerProperties ({
  _minWidth:{
    name:'minWidth',
    value:0
  },
  _maxWidth:{
    name:'maxWidth',
    value:Infinity
  },
  _width:{
    name:'width',
    conformer:function (_value) {
      return Uize.constrain (_value,this._minWidth,this._maxWidth);
    },
    value:0
  }
});

In the above example, the conformer for the width set-get property keeps the value of this property within a legal range, as defined by the minWidth and maxWidth set-get properties. Initially, the minimum width is 0 and he maximum width is Infinity (ie. there is no upper limit).

5.9.3.2. Conform to a Valid Values Set

A conformer function can be used to ensure that the value of a set-get property does not stray outside of a set of values that are valid for the property.

EXAMPLE 1

var _validTimeUnits =
  {ms:1,seconds:1,minutes:1,hours:1,days:1,weeks:1,months:1,years:1}
;
MyClass.registerProperties ({
  _timeUnit:{
    name:'timeUnit',
    conformer:function (_value) {
      return _validTimeUnits [_value] ? _value : this._timeUnit;
    },
    value:'hours'
  }
});

In the above example, the conformer for the timeUnit set-get property makes sure that a value being set for this property falls within the valid time units ms, seconds, minutes, days, weeks, months, and years. When a new value is set, the _validTimeUnits hash lookup object is used by the conformer to see if the new value is in the set of valid time units. If so, the new value is returned. If an invalid time unit value is specified, then the current value is returned so that the set has no effect.

EXAMPLE 2

MyClass.registerProperties ({
  _fruit:{
    name:'fruit',
    conformer:function (_value) {
      return Uize.isIn (this._availableFruits,_value) ? _value : this._fruit;
    }
  },
  _availableFruits:{
    name:'availableFruits',
    value:[]
  }
});

In the above example, the fruit set-get property has a companion availableFruits set-get property that allows a set of valid values to be specified for the fruit set-get property. The conformer for fruit uses the Uize.isIn static method to determine if the new value being set falls within the list of valid values specified by the availableFruits set-get property. If so, the new value is returned. If an invalid value is specified, then the current value is returned so that the set has no effect

EXAMPLE 3

MyClass.registerProperties ({
  _fruit:{
    name:'fruit',
    conformer:function (_value) {
      return this._availableFruits [Uize.indexIn (this._availableFruits,_value,0)];
    }
  },
  _availableFruits:{
    name:'availableFruits',
    value:[]
  }
});

In a variation on the previous example, a failure in validating a new value being set for the fruit set-get property results in it being defaulted to the first value from the values set specified in the availableFruits set-get property.

5.9.3.3. Enforce Type

A conformer function can be used to ensure that the value being set for a set-get property always conforms to a desired type.

ENFORCE STRING TYPE

MyClass.registerProperties ({
  _title:{
    name:'title',
    conformer:function (_value) {return _value + ''}
    value:''
  }
});

In the above example, the conformer for the title set-get property is coercing the type of the value being set for it to a string. By concatenating an empty string, JavaScript automatically invokes the valueOf Intrinsic Method for the new value. Essentially, adding an empty string has the effect of coercing the value to a string without altering the value. With such a conformer, the value being set for the title set-get property can be of type string, number, boolean, or an object that implements a valueOf Intrinsic Method (such as an instance of a Uize.Class subclass that implements the value set-get property).

ENFORCE NUMBER TYPE

MyClass.registerProperties ({
  _width:{
    name:'width',
    conformer:function (_value) {return +_value}
    value:0
  }
});

In the above example, the conformer for the width set-get property is coercing the type of the value being set for it to a number. By prepending the plus operator, JavaScript automatically invokes the valueOf Intrinsic Method for the new value, thereby coercing the value to a number without altering the value. With such a conformer, the value being set for the width set-get property can be of type string, number, boolean, or an object that implements a valueOf Intrinsic Method (such as an instance of a Uize.Class subclass that implements the value set-get property).

ENFORCE BOOLEAN TYPE - 1

MyClass.registerProperties ({
  _active:{
    name:'active',
    conformer:function (_value) {return !!_value}
    value:false
  }
});

In the above example, the conformer for the active set-get property is coercing the type of the value being set for it to a boolean. By prepending the two not operators, the value is coerced to a boolean without altering the true equivalency of the original value. With such a conformer, the value being set for the active set-get property can be of type string, number, boolean, object, or function. Any non-empty string will be turned into true, and an empty string will be turned into false. Any non-zero number will be turned into true, and 0 or NaN will be turned into false. Any object or function will be turned into true, and null or undefined will be turned into false.

ENFORCE BOOLEAN TYPE - 2

var _trueValues = {1:1,true:1,yes:1,on:1,enabled:1,active:1};
MyClass.registerProperties ({
  _showTitle:{
    name:'showTitle',
    conformer:function (_value) {return !!_trueValues [_value + '']}
    value:false
  }
});

In the above example, the conformer for the showTitle set-get property is coercing the type of the value being set for it to a boolean. Unlike the previous example, this conformer turns certain specific values into true and all other values into false. The number value 1, string value '1', boolean value true, string value 'true', and the string values 'on', 'enabled', and 'active' are all turned into true.

An empty string is concatenated to the new value being set so that JavaScript invokes the valueOf Intrinsic Method for the value, allowing the value to be an object that implements a valueOf Intrinsic Method (such as an instance of a Uize.Class subclass that implements the value set-get property). After coercing the value to a string, the _trueValues hash lookup object is used to test if the value is in the set of values considered equivalent to true.

5.9.3.4. Limit Length of String

A conformer function can be used to ensure that a string set-get property's value never exceeds a desired maximum length.

EXAMPLE

MyClass.registerProperties ({
  _title:{
    name:'title',
    conformer:function (_value) {
      _value += '';   // coerce to a string value, invoking valueOf
      return _value.length > 50 ? _value.slice (0,50) : _value;
    },
    value:''
  }
});

In the above example, the conformer for the title set-get property limits the length of its value to a maximum of fifty characters. The value is first coerced to a string by concatenating an empty string.

5.9.3.5. Enforce Case

A conformer function can be used to ensure that a string set-get property's value is always in a desired case (eg. lowercase, or uppercase).

EXAMPLE

MyClass.registerProperties ({
  _filename:{
    name:'filename',
    conformer:function (_value) {return (_value + '').toLowerCase ()},
    value:''
  }
});

In the above example, the conformer for the filename set-get property uses the toLowerCase string method to force the case of any new value to be lowercase. The value is first coerced to a string by concatenating an empty string.

5.9.3.6. Locking a Set-get Property

A conformer function can be used to provide a locking facility for a set-get property, so that it is unchangeable through the set method while it is locked.

Locking can be based on some internal state of the instance to which the property belongs, or the state of some other dedicated locking set-get property.

EXAMPLE

MyClass.registerProperties ({
  _title:{
    name:'title',
    conformer:function (_value) {
      return this._titleLocked ? this._title : _value + '';
    },
    value:''
  },
  _titleLocked:{
    name:'titleLocked',
    value:false
  }
});

In the above example, the conformer for the title set-get property only allows this property's value to be changed when the companion titleLocked set-get property is set to false.

6. Changed Events

The JavaScript Event System implemented in the UIZE JavaScript Framework implements events that allow code to watch on changes in the value of any and all set-get properties.

6.1. Changed.[propertyName] Virtual Event

The Changed.[propertyName] virtual event lets you watch on changes in value for specified set-get properties.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    value:0
  }
});
var myClass = new MyClass;
myClass.wire (
  'Changed.percentLoaded',  // name of changed event for percentLoaded
  function () {alert (myClass.get ('percentLoaded'))}
);
myClass.set ({percentLoaded:10});   // will alert the text 10
myClass.set ({percentLoaded:25});   // will alert the text 25
myClass.set ({percentLoaded:66});   // will alert the text 66
myClass.set ({percentLoaded:200});  // will alert the text 100 (because of conformer)
myClass.set ({percentLoaded:300});  // does nothing (because value doesn't change)

In the above example, a handler is being registered for the changed event of the percentLoaded set-get property, which is named Changed.percentLoaded (basically, any set-get property has a changed virtual event that is named after that property, with "Changed." prefixed).

Now, the handler for this event alerts the current value for the property. So, the first four of the set statements in the example will result in the value of percentLoaded changing, which will fire the Changed.percentLoaded virtual event, whose handler will display the current value in an alert dialog. Important to note is that the last set statement will not cause the value of percentLoaded to change. That's because the conformer function constrains its value to the range of 0 to 100, and the previous attempt to set its value to 200 caused it to be constrained to the maximum in its range. Once constrained to 100, attempting to set its value to 300 wouldn't budge it any higher from 100. So, the last set statement doesn't cause the Changed.percentLoaded event to be fired, and no alert dialog.

6.2. Changed.* Wildcard Event

The Changed.* instance event is a wildcard event that is fired whenever one or more set-get properties change value as a result of a call to the set instance method.

This event will only be fired once for all set-get properties that have changed value during a call to the set method. The event object for this event will contain a properties property, which is an object indicating which set-get properties have changed value, being a mapping between the public names of set-get properties that have changed and their new values.

EXAMPLE

var
  marquee1 = page.addChild ('marquee1',Uize.Widget.Resizer.Marquee),
  marquee2 = page.addChild ('marquee2',Uize.Widget.Resizer.Marquee)
;
marquee1.wire (
  'Changed.*',
  function (eventObj) {
    var properties = eventObj.properties;
    if (
      'left' in properties || 'top' in properties ||
      'width' in properties || 'height' in properties
    )
      marquee2.set (marquee1.getCoords ())
    ;
  }
);

In the above example, two instances of the Uize.Widget.Resizer.Marquee widget class are being added as child widgets to the page widget (which we assume to already be defined). A handler is wired to the Changed.* wildcard event of the first instance, marquee1. The handler tests to see if any of the set-get properties left, top, width, or height have changed by checking if any of these keys are present in properties property of the event object for the Changed.* event. If any of these properties have changed value, the getCoords instance method is used to get the values for those properties, and those values are set on the second instance, instance2. This code would have the effect of making instance1 be the "driver" of instance2.

7. The Special value Set-get Property

The Uize.Class base class provides an implementation for the valueOf Intrinsic Method, that returns the value of the special value set-get property.

The valueOf Intrinsic Method is invoked automatically by Javascript in certain contexts in order to convert an object to a value, such as when using an object reference in an expression. If you register the special value set-get property for your class, then you will be able to use a reference to an instance of your class in string and mathematical expressions, and your instance reference will be automatically converted to the value of the value set-get property in such expressions. This is a handy shorthand that allows you to use a reference to a class instance as a proxy for its value in expressions.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _value:{
    name:'value',
    value:0
  }
});
var myClass = new MyClass ({value:123);
alert (+myClass);               // will alert the text 123
alert (myClass + '');           // will alert the text 123
alert (myClass.valueOf ());     // will alert the text 123
alert (myClass.get ('value'));  // will alert the text 123

In the above example, the special value set-get property is being registered for the class MyClass. The initial value for this property is 0, as specified in the value property of its profile (don't confuse the special value set-get property with the value property of a set-get property's profile that lets you specify the initial value for a property).

As illustrated in this example, the value of the special value set-get property of the instance myClass can be accessed as: +myClass (coercing its type to number), myClass + '' (coercing its type to string), myClass.valueOf () (leaves type unchanged), or myClass.get ('value') (leaves type unchanged). Of the various ways that the value set-get property can be accessed, using the get method is the least efficient. So, using the other techniques might be helpful in very heavily hit code that calls for hardcore performance optimization measures. As with other set-get properties, the value of the special value set-get property can be set using the set method, as in myClass.set ({value:'newValue'}).

7.1. Watching For Value Changes

If a value set-get property is registered for a class, then application code can watch on changes in the value for an instance by registering a handler for the Change.value virtual event of the instance.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _value:{
    name:'value',
    value:0
  }
});
var myClass = new MyClass;
myClass.wire ('Changed.value',function () {alert (+myClass)});
myClass.set ({value:10});  // will alert the text 10

In the above code, a handler is registered for the Changed.value event of the instance myClass. Setting the value set-get property to 10 causes its value to change from its initial value of 0 (as specified in the value property of its profile), thereby firing the Changed.value virtual event, and the handler for this event alerts the current value by prepending the plus ("+") operator to the instance reference, invoking the valueOf Intrinsic Method.

7.2. Any Class Can Implement Value Interface

The valueOf Intrinsic Method is implemented in the Uize.Class base class, so it is inherited by all subclasses of Uize.Class, including Uize.Widget and all subclasses of Uize.Widget, and all their subclasses, etc.

To take advantage of this facility, all you need to do is register a set-get property with the public name value.

7.3. Value as an Alias

By using the Public Aliases mechanism, it is possible to name a set-get property something other than value while still leveraging the valueOf Intrinsic Method provision. Simply register two public names for the property: your desired name, and the special name value as an alias.

EXAMPLE

var MyClass = Uize.Class.subclass ();
MyClass.registerProperties ({
  _percentLoaded:{
    name:'percentLoaded|value',
    conformer:function (_value) {return Uize.constrain (_value,0,100)},
    value:0
  }
});
var myClass = new MyClass ({percentLoaded:10});
alert (myClass.get ('percentLoaded'));  // will alert the text 10
alert (myClass.get ('value'));          // will alert the text 10
alert (+myClass);                       // will alert the text 10

In the above example, the percentLoaded set-get property is being registered. Even though this property might represent the primary value for instances of the class, it is desirable for the property's name to be percentLoaded (rather than the more generic value) for general usability/understandability of the class' interface. However, it would be nice to still be able to get the benefit of the valueOf Intrinsic Method. Not a problem. This is accomplished by naming the set-get property both percentLoaded AND value. Notice how the three last statements in the example all produce the same output.

8. Advanced Topics

8.1. Derived Set-get Properties

Cases can arise where it is desirable to register a set-get property whose value is derived from the values of one or more other set-get properties, and that acts as a kind of state summary for the instance.

EXAMPLE

var MyClass = Uize.Class.subclass ();
function _updateArea () {
  this.set ({area:this._width * this._height});
}
MyClass.registerProperties ({
  _area:'area',
  _height:{
    name:'height',
    value:10,
    onChange:_updateArea
  },
  _width:{
    name:'width',
    value:10,
    onChange:_updateArea
  }
});
myClass = new MyClass;
alert (myClass.get ('area'));         // will alert the value 100

myClass.set ({width:5});
alert (myClass.get ('area'));         // will alert the value 50

myClass.set ({height:5});
alert (myClass.get ('area'));         // will alert the value 25

myClass.set ({width:20,height:20});
alert (myClass.get ('area'));         // will alert the value 400

In the above example, the area set-get property is being registered. This property is not intended to be set by an application, since its value is derived in the class' implementation from the values of two other set-get properties registered for the class: width and height. An onChange handler function is registered in the profile for each of the width and height set-get properties. If the value of either (or both) of these properties changes, then the _updateArea function is called as a method on the instance, and this function sets a new value for the area set-get property that is the product of the width and height values.

8.2. Read-only Set-get Properties

There is no formal mechanism within the UIZE JavaScript Framework for declaring a set-get property as being read-only.

There might be occasions when it's desirable to expose some instance state through a set-get property - with all the benefits that come from using set-get properties - without it being intended for application code to ever set the property's value. But the Uize.Class base class cannot distinguish between a set-get property's value being set from within the class' implementation, or from some application code using an instance of the class.

In the event that a set-get property is intended only to reflect state that is maintained within a class' implementation, the property should just be documented as intended for read-only use in the documentation for the class. Incorrect setting of the property could cause code to fail, but in the rather open JavaScript language any number of other, more damaging things could be done by application code to trip up a class' implementation.

In short, read-only set-get properties are more of a convention / design pattern than anything else. The many classes of the UIZE JavaScript Framework are certainly replete with examples of weakly "enforced" read-only properties.