//////////////////////////////////////////////////////////////////////////////// // // 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; use namespace mx_internal; [ExcludeClass] /** * @private */ public class Watcher { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function Watcher(listeners:Array = null) { super(); this.listeners = listeners; } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * The binding objects that are listening to this Watcher. * The standard event mechanism isn't used because it's too heavyweight. */ protected var listeners:Array; /** * @private * Children of this watcher are watching sub values. */ protected var children:Array; /** * @private * The value itself. */ public var value:Object; /** * @private * Keep track of cloning when used in Repeaters. */ protected var cloneIndex:int; //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @private * This is an abstract method that subclasses implement. */ public function updateParent(parent:Object):void { } /** * @private * Add a child to this watcher, meaning that the child * is watching a sub value of ours. */ public function addChild(child:Watcher):void { if (!children) children = [ child ]; else children.push(child); child.updateParent(this); } /** * @private * Remove all children beginning at a starting index. * If the index is not specified, it is assumed to be 0. * This capability is used by Repeater, which must remove * cloned RepeaterItemWatchers (and their descendant watchers). */ public function removeChildren(startingIndex:int):void { children.splice(startingIndex); } /** * We have probably changed, so go through * and make sure our children are updated. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function updateChildren():void { if (children) { var n:int = children.length; for (var i:int = 0; i < n; ++i) { children[i].updateParent(this); } } } /** * @private */ private function valueChanged(oldval:Object):Boolean { if (oldval == null && value == null) return false; var valType:String = typeof(value); // The first check is meant to catch the delayed instantiation case // where a control comes into existence but its value is still // the equivalent of not having been filled in. // Otherwise we simply return whether the value has changed. if (valType == "string") { if (oldval == null && value == "") return false; else return oldval != value; } if (valType == "number") { if (oldval == null && value == 0) return false; else return oldval != value; } if (valType == "boolean") { if (oldval == null && value == false) return false; else return oldval != value; } return true; } /** * @private */ protected function wrapUpdate(wrappedFunction:Function):void { try { wrappedFunction.apply(this); } catch(itemPendingError:ItemPendingError) { // The parent's value is not yet available. This is being ignored for now - // updateParent() will be called when the parent has a value. value = null; } catch(rangeError:RangeError) { // The parent's value is not yet available. This is being ignored for now - // updateParent() will be called when the parent has a value. value = null; } catch(error:Error) { // Certain errors are normal when executing an update, 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 // Error #1507: - invalid null argument. // We allow any other errors to be thrown. if ((error.errorID != 1006) && (error.errorID != 1009) && (error.errorID != 1010) && (error.errorID != 1055) && (error.errorID != 1069) && (error.errorID != 1507)) { throw error; } } } /** * @private * Clone this Watcher and all its descendants. * Each clone triggers the same Bindings as the original; * in other words, the Bindings do not get cloned. * * This cloning capability is used by Repeater in order * to watch the subproperties of multiple dataProvider items. * For example, suppose a repeated LinkButton's label is * {r.currentItem.firstName} {r.currentItem.lastName} * where r is a Repeater whose dataProvider is * [ { firstName: "Matt", lastName: "Chotin" }, * { firstName: "Gordon", lastName: "Smith" } ] * The MXML compiler emits a watcher tree (one item of _watchers[]) * that looks like this: * PropertyWatcher for "r" * PropertyWatcher for "dataProvider" * RepeaterItemWatcher * PropertyWatcher for "firstName" * PropertyWatcher for "lastName" * At runtime the RepeaterItemWatcher serves as a template * which gets cloned for each dataProvider item: * PropertyWatcher for "r" * PropertyWatcher for "dataProvider" * RepeaterItemWatcher (index: null) * PropertyWatcher for "firstName" (value: null) * PropertyWatcher for "lastName" (value: null) * RepeaterItemWatcher (index: 0) * PropertyWatcher for "firstName" (value: "Matt") * PropertyWatcher for "lastName" (value: "Chotin") * RepeaterItemWatcher (index: 1) * PropertyWatcher for "firstName" (value: "Gordon") * PropertyWatcher for "lastName" (value: "Smith") */ protected function deepClone(index:int):Watcher { // Clone this watcher object itself. var w:Watcher = shallowClone(); w.cloneIndex = index; // Clone its listener queue. if (listeners) { w.listeners = listeners.concat(); } // Recursively clone its children. if (children) { var n:int = children.length; for (var i:int = 0; i < n; i++) { var clonedChild:Watcher = children[i].deepClone(index); w.addChild(clonedChild); } } // Return the cloned tree of watchers. return w; } /** * @private * Clone this watcher object itself, without cloning its children. * The clone is not connec * Subclasses must override this method to copy their properties. */ protected function shallowClone():Watcher { return new Watcher(); } /** * @private */ public function notifyListeners(commitEvent:Boolean):void { if (listeners) { var n:int = listeners.length; for (var i:int = 0; i < n; i++) { listeners[i].watcherFired(commitEvent, cloneIndex); } } } } }