| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.utils.getQualifiedClassName; |
| import mx.events.CollectionEvent; |
| import mx.events.CollectionEventKind; |
| import mx.events.PropertyChangeEvent; |
| import mx.events.PropertyChangeEventKind; |
| import mx.managers.ISystemManager; |
| import mx.managers.SystemManager; |
| import mx.resources.IResourceManager; |
| import mx.resources.ResourceManager; |
| import mx.utils.IXMLNotifiable; |
| import mx.utils.XMLNotifier; |
| 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 |
| //-------------------------------------- |
| |
| [ExcludeClass] |
| |
| [ResourceBundle("collections")] |
| |
| /** |
| * @private |
| * A simple implementation of IList that uses a backing XMLList. |
| * No ItemPendingErrors since the data is always local. |
| */ |
| public class XMLListAdapter extends EventDispatcher implements IList, IXMLNotifiable |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Construct a new XMLListAdapter using the specified XMLList as its source. |
| * If no source is specified an empty XMLList will be used. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function XMLListAdapter(source:XMLList = null) |
| { |
| super(); |
| |
| disableEvents(); |
| this.source = source; |
| enableEvents(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Used for accessing localized Error messages. |
| */ |
| private var resourceManager:IResourceManager = |
| ResourceManager.getInstance(); |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // length |
| //---------------------------------- |
| |
| /** |
| * The number of items in this list. |
| * |
| * @return -1 if the length is unknown while 0 means no items |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get length():int |
| { |
| return source.length(); |
| } |
| |
| //---------------------------------- |
| // source |
| //---------------------------------- |
| |
| private var _source:XMLList; |
| |
| /** |
| * The source XMLList for this XMLListAdapter. |
| * Any changes done through the IList interface will be reflected in the |
| * source XMLList. |
| * If no source XMLList was supplied the XMLListAdapter will create one |
| * internally. |
| * Changes made directly to the underlying XMLList (e.g., calling |
| * <code>delete theList[someIndex]</code> will not cause <code>CollectionEvents</code> |
| * to be dispatched. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get source():XMLList |
| { |
| return _source; |
| } |
| |
| public function set source(s:XMLList):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 : XMLList(<></>); |
| |
| // get a seed UID. XML seems to have problems cleaning up |
| // UIDs, so this makes UIDs more similar and uses less |
| // memory |
| seedUID = UIDUtil.createUID(); |
| uidCounter = 0; |
| |
| len = _source.length(); |
| for (i = 0; i < len; i++) |
| { |
| startTrackUpdates(_source[i], seedUID + uidCounter.toString()); |
| uidCounter++; |
| } |
| |
| if (_dispatchEvents == 0) |
| { |
| var event:CollectionEvent = |
| new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| event.kind = CollectionEventKind.RESET; |
| dispatchEvent(event); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * 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. If the list is shorter than |
| * the specified index it will grow to accomodate the new item. |
| * |
| * @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 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function addItemAt(item:Object, index:int):void |
| { |
| var message:String; |
| |
| if (index < 0 || index > length) |
| { |
| message = resourceManager.getString( |
| "collections", "outOfBounds", [ index ]); |
| throw new RangeError(message); |
| } |
| |
| if (!(item is XML) && !(item is XMLList && item.length() == 1)) |
| { |
| message = resourceManager.getString( |
| "collections", "invalidType"); |
| throw new Error(message); |
| } |
| |
| setBusy(); |
| |
| if (length > 0) |
| { |
| var newSource:XMLList = new XMLList(); |
| |
| for (var i:uint = 0; i <= (length); i++) |
| { |
| if (i < index) |
| { |
| newSource[i] = source[i]; |
| } |
| else if (i == index) |
| { |
| newSource[i] = item; |
| } |
| else if (i > index) |
| { |
| newSource[i] = source[i-1]; |
| } |
| } |
| |
| source = newSource; |
| } |
| else |
| { |
| source[index] = item; |
| } |
| |
| startTrackUpdates(item, seedUID + uidCounter.toString()); |
| uidCounter++; |
| |
| if (_dispatchEvents == 0) |
| { |
| var event:CollectionEvent = |
| new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| event.kind = CollectionEventKind.ADD; |
| event.items.push(item); |
| event.location = index; |
| dispatchEvent(event); |
| } |
| |
| clearBusy(); |
| } |
| |
| /** |
| * 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 < 0 or index >= 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]; |
| } |
| |
| /** |
| * Return the index of the item if it is in the list such that |
| * getItemAt(index) == item. Note: unlike IViewCursor.findXXX |
| * <code>getItemIndex</code> cannot take a representative object, it is |
| * searching for an exact match. |
| * |
| * @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 |
| { |
| if (item is XML && source.contains(XML(item))) |
| { |
| var len:int = length; |
| for (var i:int = 0; i < len; i++) |
| { |
| var search:Object = source[i]; |
| if (search === item) |
| { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Notify the view that an item has been updated. This is useful if the |
| * contents of the view do not implement <code>IEventDispatcher</code> |
| * and dispatches a <code>PropertyChangeEvent</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; |
| |
| itemUpdateHandler(event); |
| } |
| |
| /** |
| * 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) |
| { |
| for (var i:int=length - 1; i >= 0; i--) |
| { |
| stopTrackUpdates(source[i]); |
| delete source[i]; |
| } |
| |
| if (_dispatchEvents == 0) |
| { |
| var event:CollectionEvent = |
| new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| event.kind = CollectionEventKind.RESET; |
| dispatchEvent(event); |
| } |
| } |
| } |
| |
| /** |
| * Removes the specified item from this list, should it exist. |
| * Relies on ArrayList implementation |
| * |
| * @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 Apache Flex 4.10 |
| */ |
| public function removeItem( item:Object ):Boolean |
| { |
| var _item:Object = removeItemAt[getItemIndex(item)]; |
| return _item != null; |
| } |
| |
| /** |
| * 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 is index is less than 0 or greater than length |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function removeItemAt(index:int):Object |
| { |
| if (index < 0 || index >= length) |
| { |
| var message:String = resourceManager.getString( |
| "collections", "outOfBounds", [ index ]); |
| throw new RangeError(message); |
| } |
| |
| setBusy(); |
| |
| var removed:Object = source[index]; |
| delete source[index]; |
| stopTrackUpdates(removed); |
| if (_dispatchEvents == 0) |
| { |
| var event:CollectionEvent = |
| new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| event.kind = CollectionEventKind.REMOVE; |
| event.location = index; |
| event.items.push(removed); |
| dispatchEvent(event); |
| } |
| |
| clearBusy(); |
| |
| return removed; |
| } |
| |
| /** |
| * 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. |
| * If the list is shorter than the specified index it will grow to |
| * to accomodate the new item. |
| * |
| * @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 |
| * |
| * @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, seedUID + uidCounter.toString()); |
| uidCounter++; |
| |
| if (_dispatchEvents == 0) |
| { |
| var event:CollectionEvent = |
| new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| event.kind = CollectionEventKind.REPLACE; |
| var updateInfo:PropertyChangeEvent = |
| new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE); |
| updateInfo.kind = PropertyChangeEventKind.UPDATE; |
| updateInfo.oldValue = oldItem; |
| updateInfo.newValue = item; |
| event.location = index; |
| event.items.push(updateInfo); |
| dispatchEvent(event); |
| } |
| return oldItem; |
| } |
| |
| /** |
| * Return an Array that is 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 |
| { |
| var s:XMLList = source; |
| var len:int = s.length(); |
| var ret:Array = []; |
| for (var i:int = 0; i < len; i++) |
| { |
| ret[i] = s[i]; |
| } |
| return ret; |
| } |
| |
| /** |
| * Pretty prints the contents of this XMLListAdapter to a string and returns it. |
| * |
| * @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); |
| } |
| |
| /** |
| * True if we're processing a addItem or removeItem call |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function busy():Boolean |
| { |
| return (_busy != 0) |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Internal Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Enables event dispatch for this list. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| protected 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 |
| */ |
| protected function disableEvents():void |
| { |
| _dispatchEvents--; |
| } |
| |
| /** |
| * clears busy flag |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private function clearBusy():void |
| { |
| _busy++; |
| if (_busy > 0) |
| _busy = 0; |
| } |
| |
| /** |
| * Sets busy flag. Tree DP's check it so they |
| * know whether to fake events for it or not. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private function setBusy():void |
| { |
| _busy--; |
| } |
| |
| /** |
| * Called whenever any of the contained items in the list fire an |
| * ObjectChange event. |
| * Wraps it in a CollectionEventKind.UPDATE. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| protected function itemUpdateHandler(event:PropertyChangeEvent):void |
| { |
| if (_dispatchEvents == 0) |
| { |
| var updateEvent:CollectionEvent = |
| new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| updateEvent.kind = CollectionEventKind.UPDATE; |
| updateEvent.items.push(event); |
| dispatchEvent(updateEvent); |
| } |
| } |
| |
| /** |
| * Called whenever an XML object contained in our list is updated |
| * in some way. The initial implementation stab is very lenient, |
| * any changeType will cause an update no matter how much further down |
| * in a hierarchy. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function xmlNotification(currentTarget:Object, |
| type:String, |
| target:Object, |
| value:Object, |
| detail:Object):void |
| { |
| var prop:String; |
| var oldValue:Object; |
| var newValue:Object; |
| |
| if (currentTarget === target) |
| { |
| switch(type) |
| { |
| case "attributeAdded": |
| { |
| prop = "@" + String(value); |
| newValue = detail; |
| break; |
| } |
| |
| case "attributeChanged": |
| { |
| prop = "@" + String(value); |
| oldValue = detail; |
| newValue = target[prop]; |
| break; |
| } |
| |
| case "attributeRemoved": |
| { |
| prop = "@" + String(value); |
| oldValue = detail; |
| break; |
| } |
| |
| case "nodeAdded": |
| { |
| prop = value.localName(); |
| newValue = value; |
| break; |
| } |
| |
| case "nodeChanged": |
| { |
| prop = value.localName(); |
| oldValue = detail; |
| newValue = value; |
| break; |
| } |
| |
| case "nodeRemoved": |
| { |
| prop = value.localName(); |
| oldValue = value; |
| break; |
| } |
| |
| case "textSet": |
| { |
| prop = String(value); |
| newValue = String(target[prop]); |
| oldValue = detail; |
| break; |
| } |
| |
| default: |
| { |
| break; |
| } |
| } |
| } |
| else if (type == "textSet") |
| { |
| var par:* = target.parent(); |
| if (par != undefined) |
| { |
| var gpar:* = par.parent(); |
| if (gpar === currentTarget) |
| { |
| prop = par.name().toString(); |
| newValue = value; |
| oldValue = detail; |
| } |
| } |
| } |
| |
| itemUpdated(currentTarget, prop, oldValue, newValue); |
| } |
| |
| /** |
| * This is called by addItemAt and when the source is initially |
| * assigned. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| protected function startTrackUpdates(item:Object, uid:String):void |
| { |
| XMLNotifier.getInstance().watchXML(item, this, uid); |
| } |
| |
| /** |
| * This is called by removeItemAt, removeAll, and before a new |
| * source is assigned. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| protected function stopTrackUpdates(item:Object):void |
| { |
| XMLNotifier.getInstance().unwatchXML(item, this); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * indicates if events should be dispatched. |
| * calls to enableEvents() and disableEvents() effect the value when == 0 |
| * events should be dispatched. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var _dispatchEvents:int = 0; |
| |
| /** |
| * non-zero if we're processing an addItem or removeItem |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private var _busy:int = 0; |
| |
| private var seedUID:String; |
| |
| private var uidCounter:int = 0; |
| |
| } |
| |
| } |