/*______________ | ______ | 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.Util.Coupler Class | / / / | | / / / /| | ONLINE : http://uize.com | /____/ /__/_| | COPYRIGHT : (c)2009-2016 UIZE | /___ | LICENSE : Available under MIT License or GNU General Public License |_______________| http://uize.com/license.html */ /* Module Meta Data type: Class importance: 3 codeCompleteness: 90 docCompleteness: 100 */ /*? Introduction The =Uize.Util.Coupler= class implements a mechanism for coupling two or more instances of any =Uize.Class= subclass by synchronizing values of state properties. *DEVELOPERS:* `Chris van Rensburg` In a Nutshell The =Uize.Util.Coupler= class makes it easy to keep desired state synchronized between two or more instances. Coupled instances can be widget instances (i.e. instances of =Uize.Widget= subclasses), but they do not have to be widgets. Coupled instances do not have to be of the same class - they only need to provide the same state properties that are to be synchronized between them. An Example Using the =Uize.Util.Coupler= class is easy: all you have to do is create an instance of it, specifying which properties of which instances you would like to be coupled together. Consider the following example... EXAMPLE ...................................................................................... // create subclass of Uize.Widget.CollectionItem.Zooming, for common property values var CollectionItemZoomingSubclass = Uize.Widget.CollectionItem.Zooming.subclass (); CollectionItemZoomingSubclass.set ({ previewZoomUrl:function () { return this.get ('previewUrl').replace ('100x75','400x300'); }, zoomPower:4 }); // create some zooming collection item widgets var item0 = page.addChild ('item0',CollectionItemZoomingSubclass), item1 = page.addChild ('item1',CollectionItemZoomingSubclass), item2 = page.addChild ('item2',CollectionItemZoomingSubclass) ; // couple the alignX, alignY, inUse properties between the collection items var coupler = Uize.Util.Coupler ({ instances:[item0,item1,item2], properties:['alignX','alignY','inUse'] }); ...................................................................................... In the above example, a custom subclass of the =Uize.Widget.CollectionItem.Zooming= widget class is being created. This is done purely as a convenience, in order to capture some common state property values, and so that all instances of this subclass that are created will have those same initial values. Then, three instances of this custom subclass are added as child widgets to the page widget (which we assume in this example to already be defined). Finally, an instance of the =Uize.Util.Coupler= class is created in order to couple the three instances together by keeping the values of their =alignX=, =alignY=, and =inUse= state properties synchronized across the three of them. This has the effect of making the instance that the user is currently interacting with drive the other two instances - any instance can drive the other two. When the user rests the mouse over one of the instances, the zoom in effect will be activated for all three. And when the user moves the mouse over one in order to pan horizontally and vertically around the zoomed image, the two other instances will follow the lead of the one the user is mousing over. */ Uize.module ({ name:'Uize.Util.Coupler', superclass:'Uize.Class', builder:function (_superclass) { 'use strict'; /*** General Variables ***/ var _syncFunctions = {}; /*** Private Instance Methods ***/ function _updateCoupling () { var m = this, _wirings = m._wirings ; /*** unwire wirings from any previous coupling ***/ if (_wirings) { for (var _wiringNo = -1, _wiringsLength = _wirings.length, _wiring; ++_wiringNo < _wiringsLength;) (_wiring = _wirings [_wiringNo])._instance.unwire (_wiring._eventName,_wiring._handler) ; _wirings = m._wirings = null; } if (m._coupled) { var _instances = m._instances, _properties = m._properties ; if (_instances && _properties) { /*** synchronize all other instances to first instance ***/ Uize.callOn (_instances,'set',[_instances [0].get (_properties)]); var _propertiesLength = _properties.length, _propertiesSignature = _properties.concat ().sort () + '', _syncFunction = _syncFunctions [_propertiesSignature] ; /*** build sync function (if it's not already built), or use previously built one ***/ if (!_syncFunction) { var _functionChunks = ['if (target.UIZE_UTIL_COUPLER_driver) return;']; if (_propertiesLength > 1) { _functionChunks.push ('var properties, changedMap = eventObj.properties;'); for (var _propertyNo = -1; ++_propertyNo < _propertiesLength;) { var _propertyNameQuoted = '\'' + _properties [_propertyNo] + '\''; _functionChunks.push ( 'if (' + _propertyNameQuoted + ' in changedMap) (properties || (properties = {})) [' + _propertyNameQuoted + '] = changedMap [' + _propertyNameQuoted + '];' ); } _functionChunks.push ('if (!properties) return;'); } else { var _propertyNameQuoted = '\'' + _properties [0] + '\''; _functionChunks.push ( 'var properties = {' + _propertyNameQuoted + ':eventObj.source.get (' + _propertyNameQuoted + ')};' ); } _functionChunks.push ( 'var source = eventObj.source;', 'source.UIZE_UTIL_COUPLER_driver = 1;', 'target.set (properties);', 'delete source.UIZE_UTIL_COUPLER_driver;' ); _syncFunction = _syncFunctions [_propertiesSignature] = Function ('eventObj,target',_functionChunks.join ('')) ; } /*** wire events for coupling instances together ***/ _wirings = m._wirings = []; var _eventName = 'Changed.' + (_propertiesLength > 1 ? '*' : _properties [0]), _wireCouplingHandler = function (_controllingInstance,_controlledInstance) { var _handler = function (_event) {_syncFunction (_event,_controlledInstance)}; _wirings.push ({_instance:_controllingInstance,_eventName:_eventName,_handler:_handler}); _controllingInstance.wire (_eventName,_handler); } ; for ( var _instanceNo = -1, _instancesLength = _instances.length; ++_instanceNo < _instances.length; ) _wireCouplingHandler ( _instances [_instanceNo], _instances [(_instanceNo + 1) % _instancesLength] ) ; } } } return _superclass.subclass ({ stateProperties:{ _coupled:{ name:'coupled', onChange:_updateCoupling, value:true /*? State Properties coupled A boolean, indicating whether or not the coupling is active and the instances specified by the =instances= state property are coupled together. If this property is set to =false= after instances have already been coupled, then they will be uncoupled. They can be recoupled again later by setting the value of this property back to =true=. NOTES - the initial value is =true= */ }, _instances:{ name:'instances', onChange:_updateCoupling /*? State Properties instances An array, specifying the instances that should be coupled together and whose properties, as specified by the =properties= state property, should be kept synchronized. If the value of this property is changed after instances have already been coupled, then the previously coupled instances will be uncoupled and the newly specified instances will be coupled. NOTES - see the companion =properties= state property - the initial value is =undefined= */ }, _properties:{ name:'properties', onChange:_updateCoupling /*? State Properties properties An array, specifying the properties that should be kept synchronized across all instances, as specified in the =instances= state property. If the value of this property is changed after instances have already been coupled, then the previously coupled instances will be uncoupled and the current =instances= will be recoupled using the newly specified properties. NOTES - see the companion =instances= state property - the initial value is =undefined= */ } } }); } });