| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.utils |
| { |
| |
| import flash.events.IEventDispatcher; |
| import flash.events.Event; |
| import mx.core.EventPriority; |
| import mx.binding.BindabilityInfo; |
| import mx.events.PropertyChangeEvent; |
| import mx.utils.DescribeTypeCache; |
| |
| /** |
| * The ChangeWatcher class defines utility methods |
| * that you can use with bindable Flex properties. |
| * These methods let you define an event handler that is executed |
| * whenever a bindable property is updated. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public class ChangeWatcher |
| { |
| include "../../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Creates and starts a ChangeWatcher instance. |
| * A single ChangeWatcher instance can watch one property, |
| * or a property chain. |
| * A property chain is a sequence of properties accessible from |
| * a host object. |
| * For example, the expression |
| * <code>obj.a.b.c</code> contains the property chain (a, b, c). |
| * |
| * @param host The object that hosts the property or property chain |
| * to be watched. |
| * You can use the use the <code>reset()</code> method to change |
| * the value of the <code>host</code> argument after creating |
| * the ChangeWatcher instance. |
| * The <code>host</code> maintains a list of <code>handlers</code> to invoke |
| * when <code>prop</code> changes. |
| * |
| * @param chain A value specifying the property or chain to be watched. |
| * Legal values are: |
| * <ul> |
| * <li>A String containing the name of a public bindable property |
| * of the host object.</li> |
| * |
| * <li>An Object in the form: |
| * <code>{ name: <i>property name</i>, getter: function(host) { return host[name] } }</code>. |
| * The Object contains the name of a public bindable property, |
| * and a function which serves as a getter for that property.</li> |
| * |
| * <li>A non-empty Array containing any combination |
| * of the first two options. |
| * This represents a chain of bindable properties |
| * accessible from the host. |
| * For example, to watch the property <code>host.a.b.c</code>, |
| * call the method as: <code>watch(host, ["a","b","c"], ...)</code>.</li> |
| * </ul> |
| * |
| * <p>Note: The property or properties named in the <code>chain</code> argument |
| * must be public, because the <code>describeType()</code> method suppresses all information |
| * about non-public properties, including the bindability metadata |
| * that ChangeWatcher scans to find the change events that are exposed |
| * for a given property. |
| * However, the getter function supplied when using the <code>{ name, getter }</code> |
| * argument form described above can be used to associate an arbitrary |
| * computed value with the named (public) property.</p> |
| * |
| * @param handler An event handler function called when the value of the |
| * watched property (or any property in a watched chain) is modified. |
| * The modification is signaled when any host object in the watcher |
| * chain dispatches the event that has been specified in that host object's |
| * <code>[Bindable]</code> metadata tag for the corresponding watched property. |
| * The default event is named <code>propertyChange</code>. |
| * |
| * <p>The event object dispatched by the bindable property is passed |
| * to this handler function without modification. |
| * By default, Flex dispatches an event object of type PropertyChangeEvent. |
| * However, you can define your own event type when you use the |
| * <code>[Bindable]</code> metadata tag to define a bindable property.</p> |
| * |
| * @param commitOnly Set to <code>true</code> if the handler should be |
| * called only on committing change events; |
| * set to <code>false</code> if the handler should be called on both |
| * committing and non-committing change events. |
| * Note: the presence of non-committing change events for a property is |
| * indicated by the <code>[NonCommittingChangeEvent(<event-name>)]</code> metadata tag. |
| * Typically these tags are used to indicate fine-grained value changes, |
| * such as modifications in a text field prior to confirmation. |
| * |
| * @param useWeakReference (default = false) Determines whether |
| * the reference to <code>handler</code> is strong or weak. A strong |
| * reference (the default) prevents <code>handler</code> from being |
| * garbage-collected. A weak reference does not. |
| * |
| * @return The ChangeWatcher instance, if at least one property name has |
| * been specified to the <code>chain</code> argument; null otherwise. |
| * Note that the returned watcher is not guaranteed to have successfully |
| * discovered and attached itself to change events, since none may have |
| * been exposed on the given property or chain by the host. |
| * You can use the <code>isWatching()</code> method to determine the |
| * watcher's state. |
| * |
| * @see mx.events.PropertyChangeEvent |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function watch(host:Object, chain:Object, |
| handler:Function, |
| commitOnly:Boolean = false, |
| useWeakReference:Boolean = false):ChangeWatcher |
| { |
| if (!(chain is Array)) |
| chain = [ chain ]; |
| |
| if (chain.length > 0) |
| { |
| var w:ChangeWatcher = |
| new ChangeWatcher(chain[0], handler, commitOnly, |
| watch(null, chain.slice(1), handler, commitOnly)); |
| w.useWeakReference = useWeakReference; |
| w.reset(host); |
| return w; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * Lets you determine if the host exposes a data-binding event |
| * on the property. |
| * |
| * <p>NOTE: Property chains are not supported by the <code>canWatch()</code> method. |
| * They are supported by the <code>watch()</code> method.</p> |
| * |
| * @param host The host of the property. |
| * See the <code>watch()</code> method for more information. |
| * |
| * @param name The name of the property. |
| * See the <code>watch()</code> method for more information. |
| * |
| * @param commitOnly Set to <code>true</code> if the handler |
| * should be called only on committing change events. |
| * See the <code>watch()</code> method for more information. |
| * |
| * @return <code>true</code> if <code>host</code> exposes |
| * any change events on <code>name</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function canWatch(host:Object, name:String, |
| commitOnly:Boolean = false):Boolean |
| { |
| return !isEmpty(getEvents(host, name, commitOnly)); |
| } |
| |
| /** |
| * Returns all binding events for a bindable property in the host object. |
| * |
| * @param host The host of the property. |
| * See the <code>watch()</code> method for more information. |
| * |
| * @param name The name of the property, or property chain. |
| * See the <code>watch()</code> method for more information. |
| * |
| * @param commitOnly Controls inclusion of non-committing |
| * change events in the returned value. |
| * |
| * @return Object of the form <code>{ eventName: isCommitting, ... }</code> |
| * containing all change events for the property. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function getEvents(host:Object, name:String, |
| commitOnly:Boolean = false):Object |
| { |
| if (host is IEventDispatcher) |
| { |
| // Get { eventName: isCommitting, ... } for all change events |
| // defined by host's class on prop <name> |
| var allEvents:Object = DescribeTypeCache.describeType(host). |
| bindabilityInfo.getChangeEvents(name); |
| if (commitOnly) |
| { |
| // Filter out non-committing events. |
| var commitOnlyEvents:Object = {}; |
| for (var ename:String in allEvents) |
| if (allEvents[ename]) |
| commitOnlyEvents[ename] = true; |
| return commitOnlyEvents; |
| } |
| else |
| { |
| return allEvents; |
| } |
| } |
| else |
| { |
| // If host is not IEventDispatcher, no events will be dispatched, |
| // regardless of metadata. |
| // User-supplied [Bindable("eventname")] metadata within a |
| // non-IEventDispatcher class is unlikely, |
| // but not specifically prohibited by the compiler currently. |
| return {}; |
| } |
| } |
| |
| /** |
| * Lets you determine if an Object has any properties. |
| * |
| * @param Object to inspect. |
| * |
| * @return <code>true</code> if Object has no properties. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private static function isEmpty(obj:Object):Boolean |
| { |
| for (var p:String in obj) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * Not for public use. This method is called only from the <code>watch()</code> method. |
| * See the <code>watch()</code> method for parameter usage. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function ChangeWatcher(access:Object, handler:Function, |
| commitOnly:Boolean = false, |
| next:ChangeWatcher = null) |
| { |
| super(); |
| |
| host = null; |
| name = access is String ? access as String : access.name; |
| getter = access is String ? null : access.getter; |
| this.handler = handler; |
| this.commitOnly = commitOnly; |
| this.next = next; |
| events = {}; |
| useWeakReference = false; |
| isExecuting = false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Host object. |
| * Volatile; may be reset, and will fluctuate in non-root watchers |
| * due to property value changes. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var host:Object; |
| |
| /** |
| * Name of watched property. |
| * Nonvolatile. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var name:String; |
| |
| /** |
| * Optional user-supplied getter function. |
| * Nonvolatile. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var getter:Function; |
| |
| /** |
| * User-supplied event handler function. |
| * Nonvolatile. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var handler:Function; |
| |
| /** |
| * True iff watching only committing events. |
| * Nonvolatile. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var commitOnly:Boolean; |
| |
| /** |
| * If watching a chain, this is a watcher on the next property |
| * in the chain: next.host == host[name]. |
| * Null otherwise. Nonvolatile. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var next:ChangeWatcher; |
| |
| /** |
| * Object { <event-name>: <is-committing>, ... } for current host[name]. |
| * Volatile; varies with host. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var events:Object; |
| |
| /** |
| * True while handling a change event. Used to prevent two way |
| * bindings from getting into an infinite loop. |
| */ |
| private var isExecuting:Boolean; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // useWeakReference |
| //---------------------------------- |
| |
| /** |
| * Determines whether the reference to <code>handler</code> |
| * is strong or weak. |
| * A strong reference (the default) prevents |
| * <code>handler</code> from being garbage-collected. |
| * A weak reference does not. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public var useWeakReference:Boolean; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Detaches this ChangeWatcher instance, and its handler function, |
| * from the current host. |
| * You can use the <code>reset()</code> method to reattach |
| * the ChangeWatcher instance, or watch the same property |
| * or chain on a different host object. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function unwatch():void |
| { |
| reset(null); |
| } |
| |
| /** |
| * Retrieves the current value of the watched property or property chain, |
| * or null if the host object is null. |
| * For example: |
| * <pre> |
| * watch(obj, ["a","b","c"], ...).getValue() === obj.a.b.c |
| * </pre> |
| * |
| * @return The current value of the watched property or property chain. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function getValue():Object |
| { |
| return host == null ? |
| null : |
| next == null ? |
| getHostPropertyValue() : |
| next.getValue(); |
| } |
| |
| /** |
| * Sets the handler function. |
| * |
| * @param handler The handler function. This argument must not be null. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function setHandler(handler:Function):void |
| { |
| this.handler = handler; |
| if (next) |
| next.setHandler(handler); |
| } |
| |
| /** |
| * Returns <code>true</code> if each watcher in the chain is attached |
| * to at least one change event. |
| * Note that the <code>isWatching()</code> method |
| * varies with host, since different hosts may expose different change |
| * events for the watcher's chosen property. |
| * |
| * @return <code>true</code> if each watcher in the chain is attached |
| * to at least one change event. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function isWatching():Boolean |
| { |
| return !isEmpty(events) && (next == null || next.isWatching()); |
| } |
| |
| /** |
| * Resets this ChangeWatcher instance to use a new host object. |
| * You can call this method to reuse a watcher instance |
| * on a different host. |
| * |
| * @param newHost The new host of the property. |
| * See the <code>watch()</code> method for more information. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function reset(newHost:Object):void |
| { |
| var p:String; |
| |
| if (host != null) |
| { |
| for (p in events) |
| { |
| host.removeEventListener(p, wrapHandler); |
| } |
| events = {}; |
| } |
| |
| host = newHost; |
| |
| if (host != null) |
| { |
| events = getEvents(host, name, commitOnly); |
| for (p in events) |
| { |
| host.addEventListener(p, wrapHandler, false, |
| EventPriority.BINDING, useWeakReference); |
| } |
| } |
| |
| if (next) |
| next.reset(getHostPropertyValue()); |
| } |
| |
| /** |
| * @private |
| * Retrieves the current value of the root watched property, |
| * or null if host is null. |
| * I.e. watch(obj, ["a","b","c"], ...).getHostPropertyValue() === obj.a |
| */ |
| private function getHostPropertyValue():Object |
| { |
| return host == null ? null : getter != null ? getter(host) : host[name]; |
| } |
| |
| /** |
| * @private |
| * Listener for change events. |
| * Resets chained watchers and calls user-supplied handler. |
| */ |
| private function wrapHandler(event:Event):void |
| { |
| if (!isExecuting) |
| { |
| try |
| { |
| isExecuting = true; |
| |
| if (next) |
| next.reset(getHostPropertyValue()); |
| |
| if (event is PropertyChangeEvent) |
| { |
| if ((event as PropertyChangeEvent).property == name) |
| handler(event as PropertyChangeEvent); |
| } |
| else |
| { |
| handler(event); |
| } |
| } |
| finally |
| { |
| isExecuting = false; |
| } |
| } |
| } |
| |
| } |
| |
| } |