| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package mx.binding |
| { |
| |
| import mx.collections.errors.ItemPendingError; |
| import mx.core.mx_internal; |
| import flash.utils.Dictionary; |
| |
| use namespace mx_internal; |
| |
| [ExcludeClass] |
| |
| /** |
| * @private |
| */ |
| public class Binding |
| { |
| include "../core/Version.as"; |
| |
| // Certain errors are normal during binding execution, so we swallow them. |
| // 1507 - invalid null argument |
| // 2005 - argument error (null gets converted to 0) |
| mx_internal static var allowedErrors:Object = generateAllowedErrors(); |
| mx_internal static function generateAllowedErrors():Object |
| { |
| var o:Object = {}; |
| o[1507] = 1; |
| o[2005] = 1; |
| return o; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Create a Binding object |
| * |
| * @param document The document that is the target of all of this work. |
| * |
| * @param srcFunc The function that returns us the value |
| * to use in this Binding. |
| * |
| * @param destFunc The function that will take a value |
| * and assign it to the destination. |
| * |
| * @param destString The destination represented as a String. |
| * We can then tell the ValidationManager to validate this field. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function Binding(document:Object, srcFunc:Function, |
| destFunc:Function, destString:String, |
| srcString:String = null) |
| { |
| super(); |
| |
| this.document = document; |
| this.srcFunc = srcFunc; |
| this.destFunc = destFunc; |
| this.destString = destString; |
| this.srcString = srcString; |
| this.destFuncFailed = false; |
| |
| if (this.srcFunc == null) |
| { |
| this.srcFunc = defaultSrcFunc; |
| } |
| |
| if (this.destFunc == null) |
| { |
| this.destFunc = defaultDestFunc; |
| } |
| |
| _isEnabled = true; |
| isExecuting = false; |
| isHandlingEvent = false; |
| hasHadValue = false; |
| uiComponentWatcher = -1; |
| |
| BindingManager.addBinding(document, destString, this); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Internal storage for isEnabled property. |
| */ |
| mx_internal var _isEnabled:Boolean; |
| |
| /** |
| * @private |
| * Indicates that a Binding is enabled. |
| * Used to disable bindings. |
| */ |
| mx_internal function get isEnabled():Boolean |
| { |
| return _isEnabled; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set isEnabled(value:Boolean):void |
| { |
| _isEnabled = value; |
| |
| if (value) |
| { |
| processDisabledRequests(); |
| } |
| } |
| |
| /** |
| * @private |
| * Indicates that a Binding is executing. |
| * Used to prevent circular bindings from causing infinite loops. |
| */ |
| mx_internal var isExecuting:Boolean; |
| |
| /** |
| * @private |
| * Indicates that the binding is currently handling an event. |
| * Used to prevent us from infinitely causing an event |
| * that re-executes the the binding. |
| */ |
| mx_internal var isHandlingEvent:Boolean; |
| |
| /** |
| * @private |
| * Queue of watchers that fired while we were disabled. |
| * We will resynch with our binding if isEnabled is set to true |
| * and one or more of our watchers fired while we were disabled. |
| */ |
| mx_internal var disabledRequests:Dictionary; |
| |
| /** |
| * @private |
| * True as soon as a non-null or non-empty-string value has been used. |
| * We don't auto-validate until this is true |
| */ |
| private var hasHadValue:Boolean; |
| |
| /** |
| * @private |
| * This is no longer used in Flex 3.0, but it is required to load |
| * Flex 2.0.0 and Flex 2.0.1 modules. |
| */ |
| public var uiComponentWatcher:int; |
| |
| /** |
| * @private |
| * It's possible that there is a two-way binding set up, in which case |
| * we'll do a rudimentary optimization by not executing ourselves |
| * if our counterpart is already executing. |
| */ |
| public var twoWayCounterpart:Binding; |
| |
| /** |
| * @private |
| * If there is a twoWayCounterpart, hasHadValue is false, and |
| * isTwoWayPrimary is true, then the twoWayCounterpart will be |
| * executed first. |
| */ |
| public var isTwoWayPrimary:Boolean; |
| |
| /** |
| * @private |
| * True if a wrapped function call does not throw an error. This is used by |
| * innerExecute() to tell if the srcFunc completed successfully. |
| */ |
| private var wrappedFunctionSuccessful:Boolean; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * All Bindings hang off of a document for now, |
| * but really it's just the root of where these functions live. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal var document:Object; |
| |
| /** |
| * The function that will return us the value. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal var srcFunc:Function; |
| |
| /** |
| * The function that takes the value and assigns it. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal var destFunc:Function; |
| |
| /** |
| * @private |
| */ |
| mx_internal var destFuncFailed:Boolean; |
| |
| /** |
| * The destination represented as a String. |
| * This will be used so we can signal validation on a field. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal var destString:String; |
| |
| /** |
| * The source represented as a String. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 4 |
| */ |
| mx_internal var srcString:String; |
| |
| /** |
| * @private |
| * Used to suppress calls to destFunc when incoming value is either |
| * a) an XML node identical to the previously assigned XML node, or |
| * b) an XMLList containing the identical node sequence as the previously assigned XMLList |
| */ |
| private var lastValue:Object; |
| |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| private function defaultDestFunc(value:Object):void |
| { |
| var chain:Array = destString.split("."); |
| var element:Object = document; |
| var i:uint = 0; |
| |
| if (chain[0] == "this") |
| { |
| i++; |
| } |
| |
| while (i < (chain.length - 1)) |
| { |
| element = element[chain[i++]]; |
| //if the element does not exist : avoid exception as it's heavy on memory allocations |
| if (element == null) { |
| destFuncFailed = true; |
| if (BindingManager.debugDestinationStrings[destString]) |
| { |
| trace("Binding: destString = " + destString + ", error = 1009"); |
| } |
| return; |
| } |
| } |
| |
| element[chain[i]] = value; |
| } |
| |
| private function defaultSrcFunc():Object |
| { |
| return document[srcString]; |
| } |
| |
| /** |
| * Execute the binding. |
| * Call the source function and get the value we'll use. |
| * Then call the destination function passing the value as an argument. |
| * Finally try to validate the destination. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function execute(o:Object = null):void |
| { |
| if (!isEnabled) |
| { |
| if (o != null) |
| { |
| registerDisabledExecute(o); |
| } |
| return; |
| } |
| |
| if (twoWayCounterpart && !twoWayCounterpart.hasHadValue && twoWayCounterpart.isTwoWayPrimary) |
| { |
| twoWayCounterpart.execute(); |
| hasHadValue = true; |
| return; |
| } |
| |
| if (isExecuting || (twoWayCounterpart && twoWayCounterpart.isExecuting)) |
| { |
| // If there is a twoWayCounterpart already executing, that means that it is |
| // assigning something of value so even though we won't execute we should be |
| // sure to mark ourselves as having had a value so that future executions will |
| // be correct. If isExecuting is true but we re-entered, that means we |
| // clearly had a value so setting hasHadValue is safe. |
| hasHadValue = true; |
| return; |
| } |
| |
| try |
| { |
| isExecuting = true; |
| wrapFunctionCall(this, innerExecute, o); |
| } |
| catch(error:Error) |
| { |
| if (allowedErrors[error.errorID] == null) |
| throw error; |
| } |
| finally |
| { |
| isExecuting = false; |
| } |
| } |
| |
| /** |
| * @private |
| * Take note of any execute request that occur when we are disabled. |
| */ |
| private function registerDisabledExecute(o:Object):void |
| { |
| if (o != null) |
| { |
| disabledRequests = (disabledRequests != null) ? disabledRequests : |
| new Dictionary(true); |
| |
| disabledRequests[o] = true; |
| } |
| } |
| |
| /** |
| * @private |
| * Resynch with any watchers that may have updated while we were disabled. |
| */ |
| private function processDisabledRequests():void |
| { |
| if (disabledRequests != null) |
| { |
| for (var key:Object in disabledRequests) |
| { |
| execute(key); |
| } |
| |
| disabledRequests = null; |
| } |
| } |
| |
| |
| /** |
| * @private |
| * Note: use of this wrapper needs to be reexamined. Currently there's at least one situation where a |
| * wrapped function invokes another wrapped function, which is unnecessary (i.e., only the inner function |
| * will throw), and also risks future errors due to the 'wrappedFunctionSuccessful' member variable |
| * being stepped on. Leaving alone for now to minimize pre-GMC volatility, but should be revisited for |
| * an early dot release. |
| * Also note that the set of suppressed error codes below is repeated verbatim in Watcher.wrapUpdate. |
| * These need to be consolidated and the motivations for each need to be documented. |
| */ |
| protected function wrapFunctionCall(thisArg:Object, wrappedFunction:Function, object:Object = null, ...args):Object |
| { |
| wrappedFunctionSuccessful = false; |
| |
| try |
| { |
| var result:Object = wrappedFunction.apply(thisArg, args); |
| if(destFuncFailed == true) { |
| destFuncFailed = false; |
| return null; |
| } |
| wrappedFunctionSuccessful = true; |
| return result; |
| } |
| catch(itemPendingError:ItemPendingError) |
| { |
| itemPendingError.addResponder(new EvalBindingResponder(this, object)); |
| if (BindingManager.debugDestinationStrings[destString]) |
| { |
| trace("Binding: destString = " + destString + ", error = " + itemPendingError); |
| } |
| } |
| catch(rangeError:RangeError) |
| { |
| if (BindingManager.debugDestinationStrings[destString]) |
| { |
| trace("Binding: destString = " + destString + ", error = " + rangeError); |
| } |
| } |
| catch(error:Error) |
| { |
| // Certain errors are normal when executing a srcFunc or destFunc, |
| // so we swallow them: |
| // Error #1006: Call attempted on an object that is not a function. |
| // Error #1009: null has no properties. |
| // Error #1010: undefined has no properties. |
| // Error #1055: - has no properties. |
| // Error #1069: Property - not found on - and there is no default value |
| // We allow any other errors to be thrown. |
| if ((error.errorID != 1006) && |
| (error.errorID != 1009) && |
| (error.errorID != 1010) && |
| (error.errorID != 1055) && |
| (error.errorID != 1069)) |
| { |
| throw error; |
| } |
| else |
| { |
| if (BindingManager.debugDestinationStrings[destString]) |
| { |
| trace("Binding: destString = " + destString + ", error = " + error); |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @private |
| * true iff XMLLists x and y contain the same node sequence. |
| */ |
| private function nodeSeqEqual(x:XMLList, y:XMLList):Boolean |
| { |
| var n:uint = x.length(); |
| if (n == y.length()) |
| { |
| for (var i:uint = 0; i < n && x[i] === y[i]; i++) |
| { |
| } |
| return i == n; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function innerExecute():void |
| { |
| destFuncFailed = false; |
| var value:Object = wrapFunctionCall(document, srcFunc); |
| |
| if (BindingManager.debugDestinationStrings[destString]) |
| { |
| trace("Binding: destString = " + destString + ", srcFunc result = " + value); |
| } |
| |
| if (hasHadValue || wrappedFunctionSuccessful) |
| { |
| // Suppress binding assignments on non-simple XML: identical single nodes, or |
| // lists over identical node sequences. |
| // Note: outer tests are inline for efficiency |
| if (!(lastValue is XML && lastValue.hasComplexContent() && lastValue === value) && |
| !(lastValue is XMLList && lastValue.hasComplexContent() && value is XMLList && |
| nodeSeqEqual(lastValue as XMLList, value as XMLList))) |
| { |
| destFunc.call(document, value); |
| |
| if(destFuncFailed == false) { |
| // Note: state is not updated if destFunc throws |
| lastValue = value; |
| hasHadValue = true; |
| } |
| } |
| } |
| } |
| |
| /** |
| * This function is called when one of this binding's watchers |
| * detects a property change. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function watcherFired(commitEvent:Boolean, cloneIndex:int):void |
| { |
| if (isHandlingEvent) |
| return; |
| |
| try |
| { |
| isHandlingEvent = true; |
| execute(cloneIndex); |
| } |
| finally |
| { |
| isHandlingEvent = false; |
| } |
| } |
| } |
| |
| } |