blob: c2f1a1b8998a9295a0ac3e1b3dcfda7d58c3f6de [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.collections
{
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.system.ApplicationDomain;
import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.utils.IExternalizable;
import flash.utils.getQualifiedClassName;
import mx.core.IPropertyChangeNotifier;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.PropertyChangeEvent;
import mx.events.PropertyChangeEventKind;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import mx.utils.ArrayUtil;
import mx.utils.UIDUtil;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the IList has been updated in some way.
*
* @eventType mx.events.CollectionEvent.COLLECTION_CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="collectionChange", type="mx.events.CollectionEvent")]
//--------------------------------------
// Other metadata
//--------------------------------------
[RemoteClass(alias="flex.messaging.io.ArrayList")]
[ResourceBundle("collections")]
[DefaultProperty("source")]
/**
* The ArrayList class is a simple implementation of IList
* that uses a backing Array as the source of the data.
*
* Items in the backing Array can be accessed and manipulated
* using the methods and properties of the <code>IList</code>
* interface. Operations on an ArrayList instance modify the
* data source; for example, if you use the <code>removeItemAt()</code>
* method on an ArrayList, you remove the item from the underlying
* Array.
*
* This base class will not throw ItemPendingErrors but it
* is possible that a subclass might.
*
* <pre>
* &lt;mx:ArrayList
* <b>Properties</b>
* source="null"
* /&gt;
* </pre>
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public class ArrayList extends EventDispatcher
implements IList, IExternalizable, IPropertyChangeNotifier
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Construct a new ArrayList using the specified array as its source.
* If no source is specified an empty array will be used.
*
* @param source The Array to use as a source for the ArrayList.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ArrayList(source:Array = null)
{
super();
disableEvents();
this.source = source;
enableEvents();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* Used for accessing localized Error messages.
*/
private var resourceManager:IResourceManager =
ResourceManager.getInstance();
/**
* @private
* Indicates if events should be dispatched.
* calls to enableEvents() and disableEvents() effect the value when == 0
* events should be dispatched.
*/
private var _dispatchEvents:int = 0;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// length
//----------------------------------
[Bindable("collectionChange")]
/**
* Get the number of items in the list. An ArrayList should always
* know its length so it shouldn't return -1, though a subclass may
* override that behavior.
*
* @return int representing the length of the source.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get length():int
{
if (source)
return source.length;
else
return 0;
}
//----------------------------------
// source
//----------------------------------
/**
* @private
* Storage for the source Array.
*/
private var _source:Array;
/**
* The source array for this ArrayList.
* Any changes done through the IList interface will be reflected in the
* source array.
* If no source array was supplied the ArrayList will create one internally.
* Changes made directly to the underlying Array (e.g., calling
* <code>theList.source.pop()</code> will not cause <code>CollectionEvents</code>
* to be dispatched.
*
* @return An Array that represents the underlying source.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get source():Array
{
return _source;
}
public function set source(s:Array):void
{
var i:int;
var len:int;
if (_source && _source.length)
{
len = _source.length;
for (i = 0; i < len; i++)
{
stopTrackUpdates(_source[i]);
}
}
_source = s ? s : [];
len = _source.length;
for (i = 0; i < len; i++)
{
startTrackUpdates(_source[i]);
}
if (_dispatchEvents == 0)
{
var event:CollectionEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.RESET;
dispatchEvent(event);
}
}
//----------------------------------
// uid -- mx.core.IPropertyChangeNotifier
//----------------------------------
/**
* @private
* Storage for the UID String.
*/
private var _uid:String;
/**
* Provides access to the unique id for this list.
*
* @return String representing the internal uid.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get uid():String
{
if (!_uid) {
_uid = UIDUtil.createUID();
}
return _uid;
}
public function set uid(value:String):void
{
_uid = value;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Converts an Array List to JavaScript Object Notation (JSON) format.
* Called by the JSON.stringify() method and should not be called directly.
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Apache Flex 4.12
*/
public function toJSON(s:String):*
{
return toArray();
}
/**
* Get the item at the specified index.
*
* @param index the index in the list from which to retrieve the item
* @param prefetch int indicating both the direction and amount of items
* to fetch during the request should the item not be local.
* @return the item at that index, null if there is none
* @throws ItemPendingError if the data for that index needs to be
* loaded from a remote location
* @throws RangeError if the index &lt; 0 or index &gt;= length
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getItemAt(index:int, prefetch:int = 0):Object
{
if (index < 0 || index >= length)
{
var message:String = resourceManager.getString(
"collections", "outOfBounds", [ index ]);
throw new RangeError(message);
}
return source[index];
}
/**
* Place the item at the specified index.
* If an item was already at that index the new item will replace it and it
* will be returned.
*
* @param item the new value for the index
* @param index the index at which to place the item
* @return the item that was replaced, null if none
* @throws RangeError if index is less than 0 or greater than or equal to length
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setItemAt(item:Object, index:int):Object
{
if (index < 0 || index >= length)
{
var message:String = resourceManager.getString(
"collections", "outOfBounds", [ index ]);
throw new RangeError(message);
}
var oldItem:Object = source[index];
source[index] = item;
stopTrackUpdates(oldItem);
startTrackUpdates(item);
//dispatch the appropriate events
if (_dispatchEvents == 0)
{
var hasCollectionListener:Boolean =
hasEventListener(CollectionEvent.COLLECTION_CHANGE);
var hasPropertyListener:Boolean =
hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE);
var updateInfo:PropertyChangeEvent;
if (hasCollectionListener || hasPropertyListener)
{
updateInfo = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
updateInfo.kind = PropertyChangeEventKind.UPDATE;
updateInfo.oldValue = oldItem;
updateInfo.newValue = item;
updateInfo.property = index;
}
if (hasCollectionListener)
{
var event:CollectionEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.REPLACE;
event.location = index;
event.items.push(updateInfo);
dispatchEvent(event);
}
if (hasPropertyListener)
{
dispatchEvent(updateInfo);
}
}
return oldItem;
}
/**
* Add the specified item to the end of the list.
* Equivalent to addItemAt(item, length);
*
* @param item the item to add
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addItem(item:Object):void
{
addItemAt(item, length);
}
/**
* Add the item at the specified index.
* Any item that was after this index is moved out by one.
*
* @param item the item to place at the index
* @param index the index at which to place the item
* @throws RangeError if index is less than 0 or greater than the length
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addItemAt(item:Object, index:int):void
{
const spliceUpperBound:int = length;
if (index < spliceUpperBound && index > 0)
{
source.splice(index, 0, item);
}
else if (index == spliceUpperBound)
{
source.push(item);
}
else if (index == 0)
{
source.unshift(item);
}
else
{
var message:String = resourceManager.getString(
"collections", "outOfBounds", [ index ]);
throw new RangeError(message);
}
startTrackUpdates(item);
internalDispatchEvent(CollectionEventKind.ADD, item, index);
}
/**
* @copy mx.collections.ListCollectionView#addAll()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addAll(addList:IList):void
{
addAllAt(addList, length);
}
/**
* @copy mx.collections.ListCollectionView#addAllAt()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addAllAt(addList:IList, index:int):void
{
const addListLength:int = addList.length;
if (addListLength == 0)
return;
const addedItems:Array = [];
disableEvents();
for (var i:int = 0; i < addListLength; i++)
{
var item:Object = addList.getItemAt(i);
this.addItemAt(item, i + index);
addedItems.push(item);
}
enableEvents();
if (_dispatchEvents == 0)
{
const event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.ADD;
event.location = index;
event.items = addedItems;
dispatchEvent(event);
}
}
/**
* Return the index of the item if it is in the list such that
* getItemAt(index) == item.
* Note that in this implementation the search is linear and is therefore
* O(n).
*
* @param item the item to find
* @return the index of the item, -1 if the item is not in the list.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getItemIndex(item:Object):int
{
return ArrayUtil.getItemIndex(item, source);
}
/**
* Removes the specified item from this list, should it exist.
*
* @param item Object reference to the item that should be removed.
* @return Boolean indicating if the item was removed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeItem(item:Object):Boolean
{
var index:int = getItemIndex(item);
var result:Boolean = index >= 0;
if (result)
removeItemAt(index);
return result;
}
/**
* Remove the item at the specified index and return it.
* Any items that were after this index are now one index earlier.
*
* @param index The index from which to remove the item.
* @return The item that was removed.
* @throws RangeError if index &lt; 0 or index &gt;= length.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeItemAt(index:int):Object
{
const spliceUpperBound:int = length - 1;
var removed:Object;
if (index > 0 && index < spliceUpperBound)
{
removed = source.splice(index, 1)[0];
}
else if (index == spliceUpperBound)
{
removed = source.pop();
}
else if (index == 0)
{
removed = source.shift();
}
else
{
var message:String = resourceManager.getString(
"collections", "outOfBounds", [ index ]);
throw new RangeError(message);
}
stopTrackUpdates(removed);
internalDispatchEvent(CollectionEventKind.REMOVE, removed, index);
return removed;
}
/**
* Remove all items from the list.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeAll():void
{
if (length > 0)
{
var len:int = length;
for (var i:int = 0; i < len; i++)
{
stopTrackUpdates(source[i]);
}
source.splice(0, length);
internalDispatchEvent(CollectionEventKind.RESET);
}
}
/**
* Notify the view that an item has been updated.
* This is useful if the contents of the view do not implement
* <code>IEventDispatcher</code>.
* If a property is specified the view may be able to optimize its
* notification mechanism.
* Otherwise it may choose to simply refresh the whole view.
*
* @param item The item within the view that was updated.
*
* @param property A String, QName, or int
* specifying the property that was updated.
*
* @param oldValue The old value of that property.
* (If property was null, this can be the old value of the item.)
*
* @param newValue The new value of that property.
* (If property was null, there's no need to specify this
* as the item is assumed to be the new value.)
*
* @see mx.events.CollectionEvent
* @see mx.core.IPropertyChangeNotifier
* @see mx.events.PropertyChangeEvent
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemUpdated(item:Object, property:Object = null,
oldValue:Object = null,
newValue:Object = null):void
{
var event:PropertyChangeEvent =
new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
event.kind = PropertyChangeEventKind.UPDATE;
event.source = item;
event.property = property;
event.oldValue = oldValue;
event.newValue = newValue;
if(!property)
{
stopTrackUpdates(oldValue);
startTrackUpdates(newValue);
}
itemUpdateHandler(event);
}
/**
* Return an Array that is populated in the same order as the IList
* implementation.
*
* @return An Array populated in the same order as the IList
* implementation.
*
* @throws ItemPendingError if the data is not yet completely loaded
* from a remote location
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function toArray():Array
{
return source.concat();
}
/**
* Ensures that only the source property is seralized.
* @private
*/
public function readExternal(input:IDataInput):void
{
source = input.readObject();
}
/**
* Ensures that only the source property is serialized.
* @private
*/
public function writeExternal(output:IDataOutput):void
{
output.writeObject(_source);
}
/**
* Pretty prints the contents of this ArrayList to a string and returns it.
*
* @return A String containing the contents of the ArrayList.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function toString():String
{
if (source)
return source.toString();
else
return getQualifiedClassName(this);
}
//--------------------------------------------------------------------------
//
// Internal Methods
//
//--------------------------------------------------------------------------
/**
* Enables event dispatch for this list.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function enableEvents():void
{
_dispatchEvents++;
if (_dispatchEvents > 0)
_dispatchEvents = 0;
}
/**
* Disables event dispatch for this list.
* To re-enable events call enableEvents(), enableEvents() must be called
* a matching number of times as disableEvents().
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function disableEvents():void
{
_dispatchEvents--;
}
/**
* Dispatches a collection event with the specified information.
*
* @param kind String indicates what the kind property of the event should be
* @param item Object reference to the item that was added or removed
* @param location int indicating where in the source the item was added.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function internalDispatchEvent(kind:String, item:Object = null, location:int = -1):void
{
if (_dispatchEvents == 0)
{
if (hasEventListener(CollectionEvent.COLLECTION_CHANGE))
{
var event:CollectionEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = kind;
if(kind != CollectionEventKind.RESET && kind != CollectionEventKind.REFRESH)
event.items.push(item);
event.location = location;
dispatchEvent(event);
}
// now dispatch a complementary PropertyChangeEvent
if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE) &&
(kind == CollectionEventKind.ADD || kind == CollectionEventKind.REMOVE))
{
var objEvent:PropertyChangeEvent =
new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
objEvent.property = location;
if (kind == CollectionEventKind.ADD)
objEvent.newValue = item;
else
objEvent.oldValue = item;
dispatchEvent(objEvent);
}
}
}
/**
* Called when any of the contained items in the list dispatches a
* <code>PropertyChangeEvent</code>.
* Wraps it in a <code>CollectionEventKind.UPDATE</code> object.
*
* @param event The event object for the <code>PropertyChangeEvent</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemUpdateHandler(event:PropertyChangeEvent):void
{
internalDispatchEvent(CollectionEventKind.UPDATE, event);
// need to dispatch object event now
if (_dispatchEvents == 0 && hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
{
var objEvent:PropertyChangeEvent = PropertyChangeEvent(event.clone());
var index:uint = getItemIndex(event.target);
objEvent.property = index.toString() + "." + event.property;
dispatchEvent(objEvent);
}
}
/**
* If the item is an IEventDispatcher, watch it for updates.
* This method is called by the <code>addItemAt()</code> method,
* and when the source is initially assigned.
*
* @param item The item passed to the <code>addItemAt()</code> method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function startTrackUpdates(item:Object):void
{
if (item && (item is IEventDispatcher))
{
IEventDispatcher(item).addEventListener(
PropertyChangeEvent.PROPERTY_CHANGE,
itemUpdateHandler, false, 0, true);
}
}
/**
* If the item is an IEventDispatcher, stop watching it for updates.
* This method is called by the <code>removeItemAt()</code> and
* <code>removeAll()</code> methods, and before a new
* source is assigned.
*
* @param item The item passed to the <code>removeItemAt()</code> method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function stopTrackUpdates(item:Object):void
{
if (item && item is IEventDispatcher)
{
IEventDispatcher(item).removeEventListener(
PropertyChangeEvent.PROPERTY_CHANGE,
itemUpdateHandler);
}
}
}
}