//////////////////////////////////////////////////////////////////////////////// // // 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: * *

* * * * * *
ValueMeaning
auto(default)Create the instance the * first time it is needed.
allCreate the instance when the * application started up.
noneDo not automatically create the instance. * You must call the createInstance() method to create * the instance.
* * @default "auto" * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get creationPolicy():String { return _creationPolicy; } /** * @private */ public function set creationPolicy(value:String):void { _creationPolicy = value; if (_creationPolicy == ContainerCreationPolicy.ALL) createInstance(); } //------------------------------------ // destructionPolicy //------------------------------------ /** * @private * Storage for the destructionPolicy property. */ private var _destructionPolicy:String = "never"; [Inspectable(category="General")] /** * The destruction policy for the items. * This property determines when the 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: * *

* * * * *
ValueMeaning
never(default)Once created never destroy * the instance.
autoDestroy the instance when the override * no longer applies.
* * @default "never" * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get destructionPolicy():String { return _destructionPolicy; } /** * @private */ public function set destructionPolicy(value:String):void { _destructionPolicy = value; } //------------------------------------ // destination //------------------------------------ /** * The object relative to which the child is added. This property is used * in conjunction with the 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.

* * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get items():* { if (!_items && creationPolicy != ContainerCreationPolicy.NONE) createInstance(); return _items; } /** * @private */ public function set items(value:*):void { _items = value; } //------------------------------------ // itemsFactory //------------------------------------ /** * @private * Storage for the itemsFactory property. */ private var _itemsFactory:ITransientDeferredInstance; [Inspectable(category="General")] /** * * The factory that creates the items. * *

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.

* * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get itemsFactory():ITransientDeferredInstance { return _itemsFactory; } /** * @private */ public function set itemsFactory(value:ITransientDeferredInstance):void { _itemsFactory = value; if (creationPolicy == ContainerCreationPolicy.ALL) createInstance(); } //------------------------------------ // position //------------------------------------ [Inspectable(category="General")] /** * The position of the child in the display list, relative to the * object specified by the 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; } } } }