SOURCE CODE: Uize.Widget.Options
/*______________
| ______ | U I Z E J A V A S C R I P T F R A M E W O R K
| / / | ---------------------------------------------------
| / O / | MODULE : Uize.Widget.Options Class
| / / / |
| / / / /| | ONLINE : http://www.uize.com
| /____/ /__/_| | COPYRIGHT : (c)2004-2010 UIZE
| /___ | LICENSE : Available under MIT License or GNU General Public License
|_______________| http://www.uize.com/license.html
*/
/*ScruncherSettings Mappings="=c" LineCompacting="TRUE"*/
/* Module Meta Data
type: Class
importance: 6
codeCompleteness: 90
testCompleteness: 0
docCompleteness: 100
*/
/*?
Introduction
The =Uize.Widget.Options= class manages state for a group of option buttons, with support for dynamically rebuilding the UI when the values set changes.
*DEVELOPERS:* `Chris van Rensburg`, `Jan Borgersen`, `Ben Ilegbodu`, original code donated by `Zazzle Inc.`
The =Uize.Widget.Options= module defines the =Uize.Widget.Options= widget class, a subclass of =Uize.Widget=.
###
- for value and values, discuss specific requirements for when the values are objects
- document child widgets
- document implied nodes
*/
Uize.module ({
name:'Uize.Widget.Options',
required:'Uize.Widget.Button',
builder:function (_superclass) {
/*** Variables for Scruncher Optimization ***/
var _undefined;
/*** Class Constructor ***/
var
_class = _superclass.subclass (
null,
function () {
/*** Private Instance Properties ***/
this._lastValueNo = -1;
this._totalOptionChildButtons = 0;
}
),
_classPrototype = _class.prototype
;
/*** Private Instance Methods ***/
_classPrototype._updateValueNo = function () {
var _this = this;
_this.set ({_valueNo:_this.getValueNoFromValue (_this._value)});
_this.updateUi ();
};
/*** Public Instance Methods ***/
_classPrototype.forAll = function (_function) {
for (
var _valueNo = -1, _valuesLength = this._values.length, _children = this.children;
++_valueNo < _valuesLength;
)
if (_function (_children ['option' + _valueNo],_valueNo) === false) break;
;
/*?
Instance Methods
forAll
An iterator method that is provided as a convenience and that lets you iterate over all the option buttons for the instance.
SYNTAX
..................................
myInstance.forAll (iterationFUNC);
..................................
The function specified in the =iterationFUNC= parameter will be called on each iteration and can expect to receive two parameters for the current iteration: an object reference to the option button widget, and an integer representing the index of the option. The function does not need to return any value. However, if the function wishes to terminate the iteration, it can return the boolean value =false=.
EXAMPLE
.....................................................................
myOptionsWidget.forAll (
function (optionButton,optionNo) {
optionButton.set ({enabled: optionNo % 2 ? 'inherit' : false});
}
);
.....................................................................
In the above example, the =forAll= instance method is being used to iterate over all the option buttons in order to disable every second option button - not a terribly practical example, but it illustrates the usage.
*/
};
_classPrototype.getValueNoFromValue = function (_value) {
var _values = this._values;
return (
_values.length
? (
typeof _values [0] == 'object'
? _class.findRecordNo (_values,{name:_value})
: _class.indexIn (_values,_value,false,false)
)
: -1
);
/*?
Instance Methods
getValueNoFromValue
Returns an integer, representing the index of the specified value in the =values= set. If the specified value is not one of the values in the =values= set, then this method returns =-1=.
SYNTAX
...........................................................
valueNoINT = myInstance.getValueNoFromValue (valueANYTYPE);
...........................................................
*/
};
_classPrototype.updateUi = function () {
var _this = this;
if (_this.isWired && _this._valueNo != _this._lastValueNo) {
function _setOptionSelected (_optionNo,_selected) {
_optionNo >= 0 &&
_class.callOn (_this.children ['option' + _optionNo],'set',[{selected:_selected}])
;
}
_setOptionSelected (_this._lastValueNo,false);
_setOptionSelected (_this._lastValueNo = _this._valueNo,true);
}
};
_classPrototype.wireUi = function () {
var _this = this;
if (!_this.isWired) {
_this._valueNo = -1;
var
_optionWidgetClass = _this._optionWidgetClass || Uize.Widget.Button,
_optionWidgetProperties = _this._optionWidgetProperties,
_values = _this._values,
_valuesLength = _this._totalOptionChildButtons = _values.length,
_restoreValueTimeout, _tentativeValueTimeout
;
function _restoreValue () {
_restoreValueTimeout = null;
_this.set ({
_tentativeValue:_this._value,
_tentativeValueNo:_this._valueNo
});
}
function _clearTentativeValueTimeouts () {
_restoreValueTimeout && clearTimeout (_restoreValueTimeout);
_tentativeValueTimeout && clearTimeout (_tentativeValueTimeout);
}
function _setupOption (_valueNo) {
var _value = _values [_valueNo];
if (typeof _value == 'object') _value = _value.name;
function _setValue () {
_this.set (
_this._setValueOnMouseover
? {_value:_value}
: {_tentativeValue:_value,_tentativeValueNo:_valueNo}
);
}
_this.addChild ('option' + _valueNo,_optionWidgetClass,_optionWidgetProperties)
.wire (
'*',
function (_event) {
if (_event.name == 'Click') {
_this.fire ({name:'Before Value Change',value:_value,valueNo:_valueNo}).cancel ||
_this.set ({_value:_value})
/*?
Instance Events
Before Value Change
This event fires just as an option button is clicked, but before the =value= set-get property for the instance is updated.
This event offers the handler the opportunity to cancel the value change. The event contains a "value" property (which is the new value that would be set) and a "valueNo" property (which is the index of the new value that would be set). To cancel the set action, the handler can set the event object's "cancel" property to =true=. The handler can inspect the "value" and "valueNo" properties of the event to determine if the value change should be permitted.
*/
;
_this.fire (_event);
} else if (_event.name == 'Over') {
_clearTentativeValueTimeouts ();
_this._tentativeRestTime
? (_tentativeValueTimeout = setTimeout (_setValue,_this._tentativeRestTime))
: _setValue ()
;
} else if (_event.name == 'Out') {
_clearTentativeValueTimeouts ();
_restoreValueTimeout = setTimeout (_restoreValue,250);
}
_this.fire ({
name:'Option Event',
value:_value,
childEvent:_event
/*?
Instance Events
Option Event
Fires each time an event fires for one of the option button child widgets.
When this event fires, the event object will have a "value" property whose value corresponds to the value associated with the option, as well as a "childEvent" property that carries the event object associated with the option button event.
*/
});
}
)
;
}
for (var _valueNo = -1; ++_valueNo < _valuesLength;)
_setupOption (_valueNo)
;
/*** seed root node references for buttons, if possible (performance optimization) ***/
/* NOTE:
This is a performance optimization that relies on the fact that in many typical cases, the HTML for the option buttons will be child nodes of the root node. In such cases, iterating through and seeding the root node references for all the option buttons is more efficient than leaving it up to the button widgets to get their root node by id - especially for large options sets.
*/
if (_valuesLength) {
var _optionsNode = _this.getNode ();
if (_optionsNode) {
for (
var
_childNodeNo = -1,
_childNode,
_childNodeId,
_child,
_childNodes = _optionsNode.childNodes || [],
_childNodesLength = _childNodes.length,
_children = _this.children,
_idPrefix = _this.get ('idPrefix'),
_idPrefixLength = _idPrefix.length
;
++_childNodeNo < _childNodesLength;
) {
if (
(_childNodeId = (_childNode = _childNodes [_childNodeNo]).id) &&
!_childNodeId.indexOf (_idPrefix) &&
(_child = _children [_childNodeId.slice (_idPrefixLength + 1)])
)
_child.set ({nodeMap:{'':_childNode,shell:null,bed:null}})
;
}
}
}
_superclass.prototype.wireUi.call (_this);
_this._updateValueNo ();
}
};
/*** Register Properties ***/
_class.registerProperties ({
_optionWidgetClass:'optionWidgetClass',
/*?
Set-get Properties
optionWidgetClass
An object reference to a widget class that should be used for the option buttons.
By default, the =Uize.Widget.Button= class is used for the option buttons. However, in some cases you may wish to use a class with richer functionality for the option buttons. You can create such a class by subclassing the =Uize.Widget.Button= class. If the widget class to be used for the option buttons is *not* a subclass of =Uize.Widget.Button=, then it will at least have to provide an equivalent interface in order to work with the =Uize.Widget.Options= class.
NOTES
- when this property is set to =null= or left =undefined=, then the =Uize.Widget.Button= class will be used for option buttons
- see the companion =optionWidgetProperties= set-get property
- the initial value is =undefined=
*/
_optionWidgetProperties:'optionWidgetProperties',
/*?
Set-get Properties
optionWidgetProperties
An object, specifying values for set-get properties that should be used when creating option button child widgets.
The =optionWidgetProperties= property lets you specify values for set-get properties that will be common to all option button widgets that are created. This can be useful in cases where you are using the =optionWidgetClass= set-get property to use a widget class other than =Uize.Widget.Button= for the option buttons, and where that other widget class may provide further configurability through additional set-get properties beyond what the =Uize.Widget.Button= class provides. In such cases, option button instances that are created for a particular =Uize.Widget.Options= instance can be configured for that instance by specifying set-get property values through the =optionWidgetProperties= property.
NOTES
- see the companion =optionWidgetClass= set-get property
- the initial value is =false=
*/
_setValueOnMouseover:'setValueOnMouseover',
/*?
Set-get Properties
setValueOnMouseover
A boolean, indicating whether or not the value of the =value= set-get property should be set when the user rests the mouse over an option, instead of the values of the =tentativeValue= and =tentativeValueNo= set-get properties.
When this property is set to =true=, the =value= set-get property will be set when the user rests the mouse over an option. When this property is set to =false=, =null=, =0=, or left =undefined=, then the values of the =tentativeValue= and =tentativeValueNo= properties will be set.
When =setValueOnMouseover= is set to =true=, the user will not be required to click in order for a selection to be made. In such a case, if the =tentativeRestTime= property is set to =0=, then the =value= property will be set immediately on mousing over an option. When =tentativeRestTime= is set to a value greater than =0=, then the user will have to rest the mouse on the option for the amount of time specified by the =tentativeRestTime= property before the =value= property is set. Of course, the user can still click immediately after mousing over the option to expedite selection.
NOTES
- the initial value is =undefined=
*/
_tentativeRestTime:{
name:'tentativeRestTime',
value:0
/*?
Set-get Properties
tentativeRestTime
An integer, representing the time (in milliseconds) that the user must remain moused over an option button before the =tentativeValue= set-get property will be set to the value corresponding to that button.
NOTES
- the special value of =0= indicates that the =tentativeValue= mechanism should be disabled
- the initial value is =0=
*/
},
_tentativeValue:{
name:'tentativeValue',
value:null
/*?
Set-get Properties
tentativeValue
A value of any type, that represents the current value that is tentatively being set / considered by the user.
NOTES
- the initial value is =null=
- whenever the =value= set-get property is set, this property is also set to the same value
- in "resting" state (ie. when the user is not interacting with the widget), this property will have the same value as the =value= set-get property
*/
},
_tentativeValueNo:{
name:'tentativeValueNo',
value:-1
/*?
Set-get Properties
tentativeValueNo
An integer, representing the index of the current =tentativeValue= in the =values= set.
NOTES
- the initial value is =-1=
- when no value is selected, this property is set to =-1=
- this property is read-only
*/
},
_value:{
name:'value',
onChange:function () {
var _this = this;
_this._updateValueNo ();
_this.set ({_tentativeValueNo:_this._valueNo,_tentativeValue:_this._value});
},
value:null
/*?
Set-get Properties
value
A simple type value (string, boolean, or number), that should either match one of the values in an array of simple type values specified by the =values= set-get property, or should match the name property of one of the objects in an array of object type values specified by the =values= set-get property.
NOTES
- the initial value is =null=
- whenever this property is set, the =tentativeValue= set-get property is set to the same value
*/
},
_valueNo:{
name:'valueNo',
value:-1
/*?
Set-get Properties
valueNo
An integer, representing the index of the current =value= in the =values= set.
NOTES
- the initial value is =-1=
- when no value is selected, this property is set to =-1=
- this property is read-only
*/
},
_values:{
name:'values',
onChange:function () {
var _this = this;
if (_this.isWired) {
for (
var _valueNo = -1, _totalOptionChildButtons = _this._totalOptionChildButtons || 0;
++_valueNo < _totalOptionChildButtons;
)
_this.removeChild ('option' + _valueNo)
;
_this.unwireUi ();
_this.get ('html') != _undefined && _this.set ({built:false});
_this.insertOrWireUi ();
}
},
value:[]
/*?
Set-get Properties
values
An array of simple type values (string, boolean, or number) or objects, representing the value set for the widget.
EXAMPLE 1
................................................................
myOptions.set ({values:['orange','avocadoPear','sweetPotato']});
................................................................
In the above example, the =values= for =myOptions= is being set to an array of strings. In order to select the sweet potato value, one would use the statement =myOptions.set ({value:'sweetPotato'})=.
EXAMPLE 2
....................................
myOptions.set ({
values:[
{
name:'orange',
displayName:'Orange',
category:'fruit'
},
{
name:'avocadoPear',
displayName:'Avocado Pear',
category:'fruit'
},
{
name:'sweetPotato',
displayName:'Sweet Potato',
category:'vegetable'
}
]
});
....................................
When an array of objects is specified for the =values= set-get property, each object should contain a =name= property. Then, when setting a value for the =value= set-get property, the object from the =values= array will be selected whose =name= property matches the value of the =value= set-get property. In the above example, the =values= for =myOptions= is being set to an array of objects. In order to select the sweet potato value now, one would use the statement =myOptions.set ({value:'sweetPotato'})=.
NOTES
- if this property is changed once the widget is already wired, then the widget will be unwired and then wired again
- the initial value is =[]= (an empty array)
*/
}
});
return _class;
}
});