////////////////////////////////////////////////////////////////////////////////
//
// 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
* obj.a.b.c
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 reset()
method to change
* the value of the host
argument after creating
* the ChangeWatcher instance.
* The host
maintains a list of handlers
to invoke
* when prop
changes.
*
* @param chain A value specifying the property or chain to be watched.
* Legal values are:
*
{ name: property name, getter: function(host) { return host[name] } }
.
* The Object contains the name of a public bindable property,
* and a function which serves as a getter for that property.host.a.b.c
,
* call the method as: watch(host, ["a","b","c"], ...)
.Note: The property or properties named in the chain
argument
* must be public, because the describeType()
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 { name, getter }
* argument form described above can be used to associate an arbitrary
* computed value with the named (public) property.
[Bindable]
metadata tag for the corresponding watched property.
* The default event is named propertyChange
.
*
* 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
* [Bindable]
metadata tag to define a bindable property.
true
if the handler should be
* called only on committing change events;
* set to false
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 [NonCommittingChangeEvent(<event-name>)]
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 handler
is strong or weak. A strong
* reference (the default) prevents handler
from being
* garbage-collected. A weak reference does not.
*
* @return The ChangeWatcher instance, if at least one property name has
* been specified to the chain
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 isWatching()
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.
*
* NOTE: Property chains are not supported by the canWatch()
method.
* They are supported by the watch()
method.
watch()
method for more information.
*
* @param name The name of the property.
* See the watch()
method for more information.
*
* @param commitOnly Set to true
if the handler
* should be called only on committing change events.
* See the watch()
method for more information.
*
* @return true
if host
exposes
* any change events on name
.
*
* @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 watch()
method for more information.
*
* @param name The name of the property, or property chain.
* See the watch()
method for more information.
*
* @param commitOnly Controls inclusion of non-committing
* change events in the returned value.
*
* @return Object of the form { eventName: isCommitting, ... }
* 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 true
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 watch()
method.
* See the watch()
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 { handler
* is strong or weak.
* A strong reference (the default) prevents
* handler
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 reset()
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:
* * watch(obj, ["a","b","c"], ...).getValue() === obj.a.b.c ** * @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
true
if each watcher in the chain is attached
* to at least one change event.
* Note that the isWatching()
method
* varies with host, since different hosts may expose different change
* events for the watcher's chosen property.
*
* @return true
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 watch()
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;
}
}
}
}
}