blob: ac35300e188ac2834088eb8b0bfb702d92440a45 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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);
}
}
}
}
}