////////////////////////////////////////////////////////////////////////////////
//
// 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.states
{
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.IEventDispatcher;
import mx.collections.IList;
import mx.core.ContainerCreationPolicy;
import mx.core.IChildList;
import mx.core.IDeferredContentOwner;
import mx.core.ITransientDeferredInstance;
import mx.core.IVisualElement;
import mx.core.IVisualElementContainer;
import mx.core.UIComponent;
[DefaultProperty("itemsFactory")]
/**
* Documentation is not currently available.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public class AddItems extends OverrideBase
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class Constants
//
//--------------------------------------------------------------------------
/**
* Documentation is not currently available.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public static const FIRST:String = "first";
/**
* Documentation is not currently available.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public static const LAST:String = "last";
/**
* Documentation is not currently available.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public static const BEFORE:String = "before";
/**
* Documentation is not currently available.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public static const AFTER:String = "after";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function AddItems()
{
super();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var added:Boolean = false;
/**
* @private
*/
private var startIndex:int;
/**
* @private
*/
private var numAdded:int;
/**
* @private
*/
private var instanceCreated:Boolean = false;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//------------------------------------
// creationPolicy
//------------------------------------
/**
* @private
* Storage for the creationPolicy property.
*/
private var _creationPolicy:String = ContainerCreationPolicy.AUTO;
[Inspectable(category="General")]
/**
* The creation policy for the items.
* This property determines when the itemsFactory
will create
* the instance of the items.
* Flex uses this property only if you specify an itemsFactory
property.
* The following values are valid:
*
*
Value | Meaning |
---|---|
auto | (default)Create the instance the * first time it is needed. |
all | Create the instance when the * application started up. |
none | Do not automatically create the instance.
* You must call the createInstance() method to create
* the instance. |
itemsFactory
will destroy
* the deferred instances it manages. By default once instantiated, all
* instances are cached (destruction policy of 'never').
* Flex uses this property only if you specify an itemsFactory
property.
* The following values are valid:
*
*
* Value | Meaning |
---|---|
never | (default)Once created never destroy * the instance. |
auto | Destroy the instance when the override * no longer applies. |
position
property.
* This property is optional; if
* you omit it, Flex uses the immediate parent of the State
* object, that is, the component that has the states
* property, or <mx:states>
tag that specifies the State
* object.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var destination:Object;
//------------------------------------
// items
//------------------------------------
/**
* @private
* Storage for the items property
*/
private var _items:*;
[Inspectable(category="General")]
/**
*
* The items to be added.
* If you set this property, the items are created at app startup.
* Setting this property is equivalent to setting a itemsFactory
* property with a creationPolicy
of "all"
.
*
* Do not set this property if you set the itemsFactory
* property.
If you set this property, the items are instantiated at the time
* determined by the creationPolicy
property.
Do not set this property if you set the items
* property.
* This propety is the AddItems
class default property.
* Setting this property with a creationPolicy
of "all"
* is equivalent to setting a items
property.
relativeTo
property.
*
* @default AddItems.LAST
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var position:String = AddItems.LAST;
//------------------------------------
// isStyle
//------------------------------------
[Inspectable(category="General")]
/**
* Denotes whether or not the collection represented by the
* target property is a style.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var isStyle:Boolean = false;
//------------------------------------
// isArray
//------------------------------------
[Inspectable(category="General")]
/**
* Denotes whether or not the collection represented by the
* target property is to be treated as a single array instance
* instead of a collection of items (the default).
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var isArray:Boolean = false;
//------------------------------------
// vectorClass
//------------------------------------
[Inspectable(category="General")]
/**
* When the collection represented by the target property is a
* Vector, vectorClass is the type of the target. It is used to
* initialize the target property.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4.5
*/
public var vectorClass:Class;
//------------------------------------
// propertyName
//------------------------------------
[Inspectable(category="General")]
/**
* The name of the Array property that is being modified. If the destination
* property is a Group or Container, this property is optional. If not defined, the
* items will be added as children of the Group/Container.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var propertyName:String;
//------------------------------------
// relativeTo
//------------------------------------
[Inspectable(category="General")]
/**
* The object relative to which the child is added. This property is only
* used when the position
property is AddItems.BEFORE
* or AddItems.AFTER
.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var relativeTo:Object;
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Creates the items instance from the factory.
* You must use this method only if you specify a targetItems
* property and a creationPolicy
value of "none"
.
* Flex automatically calls this method if the creationPolicy
* property value is "auto"
or "all"
.
* If you call this method multiple times, the items instance is
* created only on the first call.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function createInstance():void
{
if (!instanceCreated && !_items && itemsFactory)
{
instanceCreated = true;
items = itemsFactory.getInstance();
}
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function initialize():void
{
if (creationPolicy == ContainerCreationPolicy.AUTO)
createInstance();
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function apply(parent:UIComponent):void
{
var dest:* = getOverrideContext(destination, parent);
var localItems:Array;
added = false;
parentContext = parent;
// Early exit if destination is null.
if (!dest)
{
if (destination != null && !applied)
{
// Our destination context is unavailable so we attempt to register
// a listener on our parent document to detect when/if it becomes
// valid.
addContextListener(destination);
}
applied = true;
return;
}
applied = true;
destination = dest;
// Coerce to array if not already an array, or we wish
// to treat the array as *the* item to add (isArray == true)
if (items is Array && !isArray)
localItems = items;
else
localItems = [items];
switch (position)
{
case FIRST:
startIndex = 0;
break;
case LAST:
startIndex = -1;
break;
case BEFORE:
startIndex = getRelatedIndex(parent, dest);
break;
case AFTER:
startIndex = getRelatedIndex(parent, dest) + 1;
break;
}
if ( (propertyName == null || propertyName == "mxmlContent") && (dest is IVisualElementContainer))
{
if (!addItemsToContentHolder(dest as IVisualElementContainer, localItems))
return;
}
else if (propertyName == null && dest is IChildList)
{
addItemsToContainer(dest as IChildList, localItems);
}
else if (propertyName != null && !isStyle && dest[propertyName] is IList)
{
addItemsToIList(dest[propertyName], localItems);
}
else if (vectorClass)
{
addItemsToVector(dest, propertyName, localItems);
}
else
{
addItemsToArray(dest, propertyName, localItems);
}
added = true;
numAdded = localItems.length;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function remove(parent:UIComponent):void
{
var dest:* = getOverrideContext(destination, parent);
var localItems:Array;
var i:int;
if (!added)
{
if (dest == null)
{
// It seems our override is no longer active, but we were never
// able to successfully apply ourselves, so remove our context
// listener if applicable.
removeContextListener();
}
else if (_waitingForDeferredContent)
{
// Or we were waiting around for deferred content of our target
// to be created and it never happened, so we'll stop listening
// for now.
removeCreationCompleteListener();
}
applied = false;
parentContext = null;
return;
}
// Coerce to array if not already an array, or we wish
// to treat the array as *the* item to add (isArray == true)
if (items is Array && !isArray)
localItems = items;
else
localItems = [items];
if ((propertyName == null || propertyName == "mxmlContent") && (dest is IVisualElementContainer))
{
for (i = 0; i < numAdded; i++)
{
if (IVisualElementContainer(dest).numElements > startIndex)
IVisualElementContainer(dest).removeElementAt(startIndex);
}
}
else if (propertyName == null && dest is IChildList)
{
for (i = 0; i < numAdded; i++)
{
if (IChildList(dest).numChildren > startIndex)
IChildList(dest).removeChildAt(startIndex);
}
}
else if (propertyName != null && !isStyle && dest[propertyName] is IList)
{
removeItemsFromIList(dest[propertyName] as IList);
}
else if (vectorClass)
{
var tempVector:Object = isStyle ? dest.getStyle(propertyName) : dest[propertyName];
if (numAdded < tempVector.length)
{
tempVector.splice(startIndex, numAdded);
assign(dest, propertyName, tempVector);
}
else
{
// For destinations like ArrayCollection we don't want to
// affect the vector in-place in some cases, as ListCollectionView a
// attempts to compare the "before" and "after" state of the vector
assign(dest, propertyName, new vectorClass());
}
}
else
{
var tempArray:Array = isStyle ? dest.getStyle(propertyName) : dest[propertyName];
if (numAdded < tempArray.length)
{
tempArray.splice(startIndex, numAdded);
assign(dest, propertyName, tempArray);
}
else
{
// For destinations like ArrayCollection we don't want to
// affect the array in-place in some cases, as ListCollectionView a
// attempts to compare the "before" and "after" state of the array
assign(dest, propertyName, new Array());
}
}
if (destructionPolicy == "auto")
destroyInstance();
// Clear our flags and override context.
added = false;
applied = false;
parentContext = null;
}
/**
* @private
*/
private function destroyInstance():void
{
if (_itemsFactory)
{
instanceCreated = false;
items = null;
_itemsFactory.reset();
}
}
/**
* @private
*/
protected function getObjectIndex(object:Object, dest:Object):int
{
try
{
if ((propertyName == null || propertyName == "mxmlContent") && (dest is IVisualElementContainer))
return IVisualElementContainer(dest).getElementIndex(object as IVisualElement);
if (propertyName == null && dest is IChildList)
return IChildList(dest).getChildIndex(DisplayObject(object));
if (propertyName != null && !isStyle && dest[propertyName] is IList)
return IList(dest[propertyName].list).getItemIndex(object);
if (propertyName != null && isStyle)
return dest.getStyle(propertyName).indexOf(object);
return dest[propertyName].indexOf(object);
}
catch(e:Error) {}
return -1;
}
/**
* @private
* Find the index of the relative object. If relativeTo is an array,
* search for the first valid item's index. This is used for stateful
* documents where one or more relative siblings of the newly inserted
* item may not be realized within the current state.
*/
protected function getRelatedIndex(parent:UIComponent, dest:Object):int
{
var index:int = -1;
if (relativeTo is Array)
{
for (var i:int = 0; ((i < relativeTo.length) && index < 0); i++)
{
var relativeObject:Object = getOverrideContext(relativeTo[i], parent);
index = getObjectIndex(relativeObject, dest);
}
}
else
{
relativeObject = getOverrideContext(relativeTo, parent);
index = getObjectIndex(relativeObject, dest);
}
return index;
}
private var _waitingForDeferredContent:Boolean = false;
/**
* @private
*/
protected function addItemsToContentHolder(dest:IVisualElementContainer, items:Array):Boolean
{
// If we are being asked to add more children to a deferred content owner,
// but the deferred content has yet to be created, we will defer application
// until it is safe to do so.
if (dest is IDeferredContentOwner && dest is IEventDispatcher)
{
var dco:IDeferredContentOwner= dest as IDeferredContentOwner;
if (!dco.deferredContentCreated)
{
IEventDispatcher(dest).addEventListener("contentCreationComplete", onDestinationContentCreated);
_waitingForDeferredContent = true;
return false;
}
}
if (startIndex == -1)
startIndex = dest.numElements;
for (var i:int = 0; i < items.length; i++)
dest.addElementAt(items[i], startIndex + i);
return true;
}
/**
* @private
*/
protected function addItemsToContainer(dest:IChildList, items:Array):void
{
if (startIndex == -1)
startIndex = dest.numChildren;
for (var i:int = 0; i < items.length; i++)
dest.addChildAt(items[i], startIndex + i);
}
/**
* @private
*/
protected function addItemsToArray(dest:Object, propertyName:String, items:Array):void
{
var tempArray:Array = isStyle ? dest.getStyle(propertyName) : dest[propertyName];
if (!tempArray)
tempArray = new Array();
if (startIndex == -1)
startIndex = tempArray.length;
for (var i:int = 0; i < items.length; i++)
tempArray.splice(startIndex + i, 0, items[i]);
assign(dest, propertyName, tempArray);
}
/**
* @private
*/
protected function addItemsToVector(dest:Object, propertyName:String, items:Array):void
{
var tempVector:Object = isStyle ? dest.getStyle(propertyName) : dest[propertyName];
if (!tempVector)
tempVector = new vectorClass();
if (startIndex == -1)
startIndex = tempVector.length;
for (var i:int = 0; i < items.length; i++)
tempVector.splice(startIndex + i, 0, items[i]);
assign(dest, propertyName, tempVector);
}
/**
* @private
*/
protected function addItemsToIList(list:IList, items:Array):void
{
if (startIndex == -1)
startIndex = list.length;
for (var i:int = 0; i < items.length; i++)
list.addItemAt(items[i], startIndex + i);
}
/**
* @private
*/
protected function removeItemsFromIList(list:IList):void
{
for (var i:int = 0; i < numAdded; i++)
list.removeItemAt(startIndex);
}
/**
* @private
*/
protected function assign(dest:Object, propertyName:String, value:Object):void
{
if (isStyle)
{
dest.setStyle(propertyName, value);
dest.styleChanged(propertyName);
dest.notifyStyleChangeInChildren(propertyName, true);
}
else
{
dest[propertyName] = value;
}
}
/**
* @private
* We've detected that our IDeferredContentOwnder target has created its
* content, so it's safe to apply our override content now.
*/
private function onDestinationContentCreated(e:Event):void
{
if (parentContext)
{
removeCreationCompleteListener();
apply(parentContext);
}
}
/**
* @private
* Remove our contentCreationComplete listener.
*/
private function removeCreationCompleteListener():void
{
if (parentContext)
{
parentContext.removeEventListener("contentCreationComplete", onDestinationContentCreated);
_waitingForDeferredContent = false;
}
}
}
}