/*______________ | ______ | 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.mChildBindings Mixin | / / / | | / / / /| | ONLINE : http://uize.com | /____/ /__/_| | COPYRIGHT : (c)2014-2016 UIZE | /___ | LICENSE : Available under MIT License or GNU General Public License |_______________| http://uize.com/license.html */ /* Module Meta Data type: Mixin importance: 5 codeCompleteness: 95 docCompleteness: 10 */ /*? Introduction The =Uize.Widget.mChildBindings= mixin implements features to provide a declarative approach to binding the state properties of a widget to those of its children. *DEVELOPERS:* `Ben Ilegbodu`, original code contributed by `Zazzle Inc.` */ Uize.module ({ name:'Uize.Widget.mChildBindings', builder:function () { 'use strict'; var /*** Variables for Scruncher Optimization ***/ _undefined, _Uize = Uize, _forEach = _Uize.forEach, _isString = _Uize.isString, _isArray = _Uize.isArray, _isPlainObject = _Uize.isPlainObject, _resolveTransformer = _Uize.resolveTransformer, _pairUp = _Uize.pairUp, /*** Variables for Performance Optimzation ***/ _bindingFormatRegExp = /^([<\->]+)?(\w+)(\.(.+))?$/ ; function _syncWidgets(_sourceWidget, _sourceProperty, _destinationWidget, _destinationProperty, _valueTransformer) { var _sourceValue = _sourceWidget.get(_sourceProperty); _destinationWidget.set( _destinationProperty, _valueTransformer ? _valueTransformer(_sourceValue, _sourceWidget, _destinationWidget) : _sourceValue ); } return function (_class) { _class.declare ({ alphastructor:function () { var m = this, _addedChildren = m.addedChildren ; /* NOTE: Format of mChildBindings_bindings: { childA:{ 'propertyA/childPropertyA':function() { // set up bindings } } } */ _forEach( m.Class.mChildBindings_bindings, function(_bindingsForChild, _childName) { _addedChildren.whenever( _childName, function() { for (var _key in _bindingsForChild) _bindingsForChild[_key](m) ; } ); } ); }, staticMethods:{ childBindings:function(_bindings) { var _childBindings = this.mChildBindings_bindings; _forEach( _bindings, function(_bindingForProperty, _propertyName) { function _processBinding(_binding) { var _formatMatch = _isString(_binding) && _binding.match(_bindingFormatRegExp); if (_formatMatch) //canonicalize string _binding = { child:_formatMatch[2], property:_formatMatch[4], direction:_formatMatch[1] } ; if (_isPlainObject(_binding) && _binding.child) { var _childName = _binding.child, _childPropertyName = _binding.property || _propertyName, _direction = _binding.direction || '<->', // bi-directional is the default _directionLength = _direction.length, _valueAdapter = _binding.valueAdapter, _valueTransformerAtoB = _valueAdapter && _valueAdapter.aToB && _resolveTransformer(_valueAdapter.aToB), _valueTransformerBtoA = _valueAdapter && _valueAdapter.bToA && _resolveTransformer(_valueAdapter.bToA), _directionIsToChild = _direction.indexOf('->') == (_directionLength - 2), // parent -> child _directionIsFromChild = !_direction.indexOf('<-'), // child -> parent _directionIsTwoWay = _direction.indexOf('<->'), _widgetChangedPropertyEventName = 'Changed.' + _propertyName, _childWidgetChangedPropertyEventName = 'Changed.' + _childPropertyName ; // Construct function to be called once the child is added. It will actually create the bindings (_childBindings[_childName] = _childBindings[_childName] || {})[_propertyName + '/' + _childPropertyName] = function(m) { var _childWidget = m.children[_childName], _syncToChildEvent, _syncFromChildEvent ; function _syncToChild() { _syncWidgets(m, _propertyName, _childWidget, _childPropertyName, _valueTransformerAtoB) } function _syncFromChild() { _syncWidgets(_childWidget, _childPropertyName, m, _propertyName, _valueTransformerBtoA) } if (_directionIsToChild) { // First set child widget to have same value as widget // We don't want to do this if the binding is bi-drectional and the widget is undefined. // In that case we'd rather the child widget be the driver (_directionIsTwoWay || m.get(_propertyName) !== _undefined) && _syncToChild() ; // Then wire Changed.* handler on widget to update child widget m.wire(_syncToChildEvent = _pairUp(_widgetChangedPropertyEventName, _syncToChild)); } if (_directionIsFromChild) { // First set widget to have same value as child _syncFromChild(); // Then wire Changed.* handler on child to update widget _childWidget.wire(_syncFromChildEvent = _pairUp(_childWidgetChangedPropertyEventName, _syncFromChild)); } // Finally wire up unwire if/when the child is removed m.addedChildren.whenever( '!' + _childName, function() { if (_childWidget) { // unwire parent -> child _syncToChildEvent && m.unwire(_syncToChildEvent); // unwire child -> parent (even though child is removed, it is not necessarilly destroyed) _syncFromChildEvent && _childWidget && _childWidget.unwire(_syncFromChildEvent); // clear out our reference to the removed child widget to not potentially hang on memory that can be disposed _childWidget = undefined; } } ); }; } } _isArray(_bindingForProperty) ? _forEach(_bindingForProperty, _processBinding) : _processBinding(_bindingForProperty) ; } ); /*? Static Methods Uize.Widget.mChildBindings.childBindings . SYNTAX ......................................... MyWidgetClass.childBindings (bindingsOBJ); ......................................... VERBOSE EXAMPLE ...................................................... MyNamespace.MyWidgetClass = Uize.Widget.mChildBindings.subclass ({ childBindings:{ size:{ child:'sizeWidget', property:'value', direction:'<->' // bi-directional changes (default), valueAdapter:{ aToB:function(value) { return value * value }, bToA:function(value) { return Math.sqrt(value) } } }, value:[ // parent-to-many-children { child:'valueWidget', direction:'->', valueAdapter:{ aToB:'value * value' // via Uize.resolveValueTransformer } }, { child:'valueWidget2', direction:'<-', valueAdapter:{ bToA:'Math.sqrt(value)' // via Uize.resolveValueTransformer } } ] } }); ...................................................... SHORT-HAND EXAMPLE ...................................................... MyNamespace.MyWidgetClass = Uize.Widget.mChildBindings.subclass ({ childBindings:{ size:'<->sizeWidget.value', // bi-directional changes with "value" state proprety in "sizeWidget" child value:[ // parent-to-many-children '->valueWidget', // changes to child only '<-valueWidget2' // changes from child only ], values:'valuesWidget' // bi-directional changes with same-named state proprety in "valuesWidget" child } }); ...................................................... */ } }, staticProperties:{ mChildBindings_bindings:{} } }); }; } });