| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 spark.components.supportClasses |
| { |
| |
| import flash.events.Event; |
| import flash.events.MouseEvent; |
| |
| import mx.collections.IList; |
| import mx.core.FlexVersion; |
| import mx.core.IVisualElement; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.CollectionEvent; |
| import mx.events.CollectionEventKind; |
| import mx.events.FlexEvent; |
| |
| import spark.components.IItemRenderer; |
| import spark.components.IItemRendererOwner; |
| import spark.components.SkinnableDataContainer; |
| import spark.components.supportClasses.IDataProviderEnhance; |
| import spark.components.supportClasses.RegExPatterns; |
| import spark.events.IndexChangeEvent; |
| import spark.events.ListEvent; |
| import spark.events.RendererExistenceEvent; |
| import spark.layouts.supportClasses.LayoutBase; |
| import spark.utils.LabelUtil; |
| |
| use namespace mx_internal; //ListBase and List share selection properties that are mx_internal |
| |
| /** |
| * Dispatched when the user rolls the mouse pointer over an item in the control. |
| * |
| * @eventType spark.events.ListEvent.ITEM_ROLL_OVER |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10.2 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| [Event(name="itemRollOver", type="spark.events.ListEvent")] |
| |
| /** |
| * Dispatched when the user rolls the mouse pointer out of an item in the control. |
| * |
| * @eventType spark.events.ListEvent.ITEM_ROLL_OUT |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10.2 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| [Event(name="itemRollOut", type="spark.events.ListEvent")] |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when the selection is going to change. |
| * Calling the <code>preventDefault()</code> method |
| * on the event prevents the selection from changing. |
| * |
| * <p>This event is dispatched when the user interacts with the control. |
| * When you change the value of the <code>selectedIndex</code> |
| * or <code>selectedItem</code> properties programmatically, |
| * the control does not dispatch the <code>changing</code> event. |
| * It dispatches the <code>valueCommit</code> event instead.</p> |
| * |
| * @eventType spark.events.IndexChangeEvent.CHANGING |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="changing", type="spark.events.IndexChangeEvent")] |
| |
| /** |
| * Dispatched after the selection has changed. |
| * This event is dispatched when the user interacts with the control. |
| * |
| * <p>When you change the value of the <code>selectedIndex</code> |
| * or <code>selectedItem</code> properties programmatically, |
| * the control does not dispatch the <code>change</code> event. |
| * It dispatches the <code>valueCommit</code> event instead.</p> |
| * |
| * @eventType spark.events.IndexChangeEvent.CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="change", type="spark.events.IndexChangeEvent")] |
| |
| /** |
| * Dispatched after the focus has changed. |
| * |
| * @eventType spark.events.IndexChangeEvent.CARET_CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="caretChange", type="spark.events.IndexChangeEvent")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [AccessibilityClass(implementation="spark.accessibility.ListBaseAccImpl")] |
| |
| /** |
| * The ListBase class is the base class for all components that support |
| * selection. |
| * |
| * <p><b>Note: </b>The Spark list-based controls (the Spark ListBase class and its subclasses |
| * such as ButtonBar, ComboBox, DropDownList, List, and TabBar) do not support the BasicLayout class |
| * as the value of the <code>layout</code> property. |
| * Do not use BasicLayout with the Spark list-based controls.</p> |
| * |
| * @mxml <p>The <code><s:ListBase></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:ListBase |
| * |
| * <strong>Properties</strong> |
| * arrowKeysWrapFocus="false" |
| * labelField="label" |
| * labelFunction="null" |
| * requireSelection="false" |
| * selectedIndex="-1" |
| * selectedItem="undefined" |
| * useVirtualLayout="false" |
| * |
| * <strong>Events</strong> |
| * caretChange="<i>No default</i>" |
| * change="<i>No default</i>" |
| * changing="<i>No default</i>" |
| * /> |
| * </pre> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public class ListBase extends SkinnableDataContainer implements IDataProviderEnhance |
| { |
| include "../../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class constants |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Static constant representing the value "no selection". |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public static const NO_SELECTION:int = -1; |
| |
| /** |
| * @private |
| * Static constant representing no proposed selection. |
| */ |
| mx_internal static const NO_PROPOSED_SELECTION:int = -2; |
| |
| /** |
| * @private |
| * Static constant representing no item in focus. |
| */ |
| private static const NO_CARET:int = -1; |
| |
| /** |
| * @private |
| */ |
| mx_internal static var CUSTOM_SELECTED_ITEM:int = -3; |
| |
| /** |
| * @private |
| * Static constant representing no item in focus. |
| */ |
| private static const TYPE_MAP:Object = { rollOver: "itemRollOver", |
| rollOut: "itemRollOut" }; |
| |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class mixins |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Placeholder for mixin by ListBaseAccImpl. |
| */ |
| mx_internal static var createAccessibilityImplementation:Function; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function ListBase() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var allowCustomSelectedItem:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| mx_internal var dispatchChangeAfterSelection:Boolean = false; |
| |
| /** |
| * @private |
| * Used to keep track of whether selection transitions should play |
| * in the updateRenderer() code. See turnOnSelectionTransitionsForOneFrame() |
| */ |
| private var allowSelectionTransitions:Boolean = false; |
| |
| /** |
| * @private |
| * Used to keep track of whether caret transitions should play |
| * in the updateRenderer() code. See turnOnCaretTransitionsForOneFrame() |
| */ |
| private var allowCaretTransitions:Boolean = false; |
| |
| /** |
| * @private |
| * Used to keep track of whether we are in updateRenderer(). This is |
| * so we know that any calls to itemSelected() and itemShowingCaret(), |
| * are called based on a user-action (either interaction or programattic) |
| * and not through item renderer recycling. |
| */ |
| private var inUpdateRenderer:Boolean; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // baselinePosition |
| //---------------------------------- |
| |
| /** |
| * @private |
| * The baseline position of a ListBase is calculated for the first item renderer. |
| * If there are no items, one is temporarily added to do the calculation. |
| */ |
| override public function get baselinePosition():Number |
| { |
| if (!validateBaselinePosition()) |
| return NaN; |
| |
| // Fabricate temporary data provider if necessary. |
| var isNull:Boolean = dataProvider == null; |
| var isEmpty:Boolean = dataProvider != null && dataProvider.length == 0; |
| |
| if (isNull || isEmpty) |
| { |
| var originalProvider:IList = isEmpty ? dataProvider : null; |
| //TODO (jmclean) seems odd to me double check |
| dataProvider = new mx.collections.ArrayList([ {} ]); |
| validateNow(); |
| } |
| |
| if (!dataGroup || dataGroup.numElements == 0) |
| return super.baselinePosition; |
| |
| // Obtain reference to newly generated item element which will be used |
| // to compute the baseline. |
| var listItem:Object = dataGroup ? dataGroup.getElementAt(0) : undefined; |
| if (!listItem) |
| return super.baselinePosition; |
| |
| // Compute baseline position of item relative to our list component. |
| if ("baselinePosition" in listItem) |
| var result:Number = getSkinPartPosition(IVisualElement(listItem)).y + listItem.baselinePosition; |
| else |
| super.baselinePosition; |
| |
| // Restore the previous state of our list. |
| if (isNull || isEmpty) |
| { |
| if (isNull) |
| dataProvider = null; |
| else if (isEmpty) |
| dataProvider = originalProvider; |
| |
| validateNow(); |
| } |
| |
| return result; |
| } |
| |
| //---------------------------------- |
| // dataProvider |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var dataProviderChanged:Boolean; |
| |
| [Inspectable(category="Data")] |
| |
| /** |
| * @private |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| override public function set dataProvider(value:IList):void |
| { |
| if (dataProvider) |
| dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataProvider_collectionChangeHandler); |
| |
| dataProviderChanged = true; |
| doingWholesaleChanges = true; |
| |
| // ensure that our listener is added before the dataGroup which adds a listener during |
| // the base class setter if the dataGroup already exists. If the dataGroup isn't |
| // created yet, then we still be first. |
| if (value) |
| value.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataProvider_collectionChangeHandler, false, 0, true); |
| |
| super.dataProvider = value; |
| invalidateProperties(); |
| } |
| |
| //---------------------------------- |
| // layout |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set layout(value:LayoutBase):void |
| { |
| if (value && useVirtualLayout) |
| value.useVirtualLayout = true; |
| |
| super.layout = value; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // arrowKeysWrapFocus |
| //---------------------------------- |
| |
| /** |
| * If <code>true</code>, using arrow keys to navigate within |
| * the component wraps when it hits either end. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public var arrowKeysWrapFocus:Boolean; |
| |
| //---------------------------------- |
| // caretIndex |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _caretIndex:Number = NO_CARET; |
| |
| [Bindable("caretChange")] |
| |
| /** |
| * Item that is currently in focus. |
| * |
| * @default -1 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get caretIndex():Number |
| { |
| return _caretIndex; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal var doingWholesaleChanges:Boolean = false; |
| |
| //---------------------------------- |
| // caretItem |
| //---------------------------------- |
| |
| /** |
| * @private |
| * This private variable differs from selectedItem in that |
| * the value is not computed, it's cached each time setCurrentCaretIndex() is called. |
| * It's used by List/dataProviderRefreshed() to provide a little scroll position and |
| * selection/caret stability after a dataProvider refresh. |
| */ |
| mx_internal var caretItem:* = undefined; |
| |
| |
| //---------------------------------- |
| // isFirstRow |
| //---------------------------------- |
| |
| /** |
| * Returns if the selectedIndex is equal to the first row. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function get isFirstRow():Boolean |
| { |
| if (dataProvider && dataProvider.length > 0) |
| { |
| if (selectedIndex == 0) |
| { |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| |
| //---------------------------------- |
| // isLastRow |
| //---------------------------------- |
| |
| /** |
| * Returns if the selectedIndex is equal to the last row. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function get isLastRow():Boolean |
| { |
| if (dataProvider && dataProvider.length > 0) |
| { |
| if (selectedIndex == dataProvider.length - 1) |
| { |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| |
| //---------------------------------- |
| // labelField |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _labelField:String = "label"; |
| |
| /** |
| * @private |
| */ |
| private var labelFieldOrFunctionChanged:Boolean; |
| |
| [Inspectable(category="Data", defaultValue="label")] |
| |
| /** |
| * The name of the field in the data provider items to display |
| * as the label. |
| * |
| * If labelField is set to an empty string (""), no field will |
| * be considered on the data provider to represent label. |
| * |
| * The <code>labelFunction</code> property overrides this property. |
| * |
| * @default "label" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get labelField():String |
| { |
| return _labelField; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set labelField(value:String):void |
| { |
| if (value == _labelField) |
| return; |
| |
| _labelField = value; |
| labelFieldOrFunctionChanged = true; |
| invalidateProperties(); |
| } |
| |
| //---------------------------------- |
| // labelFunction |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _labelFunction:Function; |
| |
| [Inspectable(category="Data")] |
| |
| /** |
| * A user-supplied function to run on each item to determine its label. |
| * The <code>labelFunction</code> property overrides |
| * the <code>labelField</code> property. |
| * |
| * <p>You can supply a <code>labelFunction</code> that finds the |
| * appropriate fields and returns a displayable string. The |
| * <code>labelFunction</code> is also good for handling formatting and |
| * localization. </p> |
| * |
| * <p>The label function takes a single argument which is the item in |
| * the data provider and returns a String.</p> |
| * <pre> |
| * myLabelFunction(item:Object):String</pre> |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get labelFunction():Function |
| { |
| return _labelFunction; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set labelFunction(value:Function):void |
| { |
| if (value == _labelFunction) |
| return; |
| |
| _labelFunction = value; |
| labelFieldOrFunctionChanged = true; |
| invalidateProperties(); |
| } |
| |
| |
| //---------------------------------- |
| // preventSelection |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _preventSelection:Boolean = false; |
| |
| |
| /** |
| * If <code>true</code> items will be prevented from being selected. The <code>selectedIndex</code> value should always be -1. |
| * When this is set to <code>true</code>, it will set the <code>requireSelection</code> to false. |
| * Click events will continue to be called. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function get preventSelection():Boolean |
| { |
| return _preventSelection; |
| } |
| |
| public function set preventSelection(newValue:Boolean):void |
| { |
| if (newValue == _preventSelection) |
| { |
| return; |
| } |
| |
| |
| if (newValue == true) |
| { |
| //Make sure to disable requireSelection since these two properties are polar opposites. |
| requireSelection = false; |
| |
| //Remove any previous selection and allow it to update before enabling the prevent selection. |
| setSelectedIndex(NO_SELECTION, false); |
| validateNow(); |
| } |
| |
| _preventSelection = newValue; |
| } |
| |
| |
| //---------------------------------- |
| // requireSelection |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the requireSelection property. |
| */ |
| private var _requireSelection:Boolean = false; |
| |
| /** |
| * @private |
| * Flag that is set when requireSelection has changed. |
| */ |
| private var requireSelectionChanged:Boolean = false; |
| |
| [Inspectable(category="General", defaultValue="false")] |
| |
| /** |
| * If <code>true</code>, a data item must always be selected in the control. |
| * If the value is <code>true</code>, the <code>selectedIndex</code> property |
| * is always set to a value between 0 and (<code>dataProvider.length</code> - 1). |
| * |
| * <p>The default value is <code>false</code> for most subclasses, except TabBar. In that case, the default is <code>true</code>.</p> |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get requireSelection():Boolean |
| { |
| return _requireSelection; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requireSelection(value:Boolean):void |
| { |
| if (value == _requireSelection) |
| return; |
| |
| _requireSelection = value; |
| |
| // We only need to update if the value is changing |
| // from false to true |
| if (value == true) |
| { |
| //Make sure to disable preventSelection since these two properties are polar opposites. |
| preventSelection = false; |
| |
| requireSelectionChanged = true; |
| invalidateProperties(); |
| } |
| } |
| |
| //---------------------------------- |
| // selectedIndex |
| //---------------------------------- |
| |
| /** |
| * @private |
| * The proposed selected index. This is a temporary variable that is |
| * used until the selected index is committed. |
| */ |
| mx_internal var _proposedSelectedIndex:int = NO_PROPOSED_SELECTION; |
| |
| /** |
| * @private |
| * Flag that is set when the selectedIndex has been adjusted due to |
| * items being added or removed. When this flag is true, the value |
| * of the selectedIndex has changed, but the actual selected item |
| * is the same. |
| * This flag can also be set if the selectedItem has changed to ensure |
| * a valueCommit is dispatched even if the selectedIndex has not changed. |
| * This flag is cleared in commitProperties(). |
| */ |
| mx_internal var selectedIndexAdjusted:Boolean = false; |
| |
| /** |
| * @private |
| * Flag that is set when the caretIndex has been adjusted due to |
| * items being added or removed. This flag is cleared in |
| * commitProperties(). |
| */ |
| mx_internal var caretIndexAdjusted:Boolean = false; |
| |
| /** |
| * @private |
| * Keeps track of whether the caret should change when selection changes. |
| * By default it is true |
| */ |
| mx_internal var changeCaretOnSelection:Boolean = true; |
| |
| /** |
| * @private |
| * Internal storage for the selectedIndex property. |
| */ |
| mx_internal var _selectedIndex:int = NO_SELECTION; |
| |
| [Bindable("change")] |
| [Bindable("valueCommit")] |
| [Inspectable(category="General", defaultValue="-1")] |
| |
| /** |
| * The 0-based index of the selected item, or -1 if no item is selected. |
| * Setting the <code>selectedIndex</code> property deselects the currently selected |
| * item and selects the data item at the specified index. |
| * |
| * <p>The value is always between -1 and (<code>dataProvider.length</code> - 1). |
| * If items at a lower index than <code>selectedIndex</code> are |
| * removed from the component, the selected index is adjusted downward |
| * accordingly.</p> |
| * |
| * <p>If the selected item is removed, the selected index is set to:</p> |
| * |
| * <ul> |
| * <li>-1 if <code>requireSelection</code> = <code>false</code> |
| * or there are no remaining items.</li> |
| * <li>0 if <code>requireSelection</code> = <code>true</code> |
| * and there is at least one item.</li> |
| * </ul> |
| * |
| * <p>When the user changes the <code>selectedIndex</code> property by interacting with the control, |
| * the control dispatches the <code>change</code> and <code>changing</code> events. |
| * When you change the value of the <code>selectedIndex</code> property programmatically, |
| * it dispatches the <code>valueCommit</code> event.</p> |
| * |
| * @default -1 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectedIndex():int |
| { |
| if (_proposedSelectedIndex != NO_PROPOSED_SELECTION) |
| return _proposedSelectedIndex; |
| |
| return _selectedIndex; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set selectedIndex(value:int):void |
| { |
| setSelectedIndex(value, false); |
| } |
| |
| /** |
| * <p>The <code>rowIndex</code> is the index in the data provider |
| * of the item containing the selected cell.</p> |
| * |
| * @param rowIndex The 0-based row index of the cell. |
| * |
| * @param dispatchChangeEvent if true, the component will dispatch a "change" event if the |
| * rowIndex has changed. Otherwise, it will dispatch a "valueCommit" event. |
| * |
| * @param changeCaret if true, the caret will be set to the selectedIndex as a side-effect of calling |
| * this method. If false, caretIndex won't change. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function setSelectedIndex(rowIndex:int, dispatchChangeEvent:Boolean = false, changeCaret:Boolean = true):void |
| { |
| if (rowIndex == selectedIndex) |
| { |
| // this should short-circuit, but we should check to make sure |
| // that caret doesn't need to be changed either, as that's a side |
| // effect of setting selectedIndex |
| if (changeCaret) |
| setCurrentCaretIndex(selectedIndex); |
| |
| return; |
| } |
| |
| if (dispatchChangeEvent) |
| dispatchChangeAfterSelection = (dispatchChangeAfterSelection || dispatchChangeEvent); |
| changeCaretOnSelection = changeCaret; |
| _proposedSelectedIndex = rowIndex; |
| invalidateProperties(); |
| } |
| |
| //---------------------------------- |
| // selectedItem |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _pendingSelectedItem:*; |
| |
| /** |
| * @private |
| */ |
| private var _selectedItem:*; |
| |
| [Bindable("change")] |
| [Bindable("valueCommit")] |
| [Inspectable(category="General", defaultValue="null")] |
| |
| /** |
| * The item that is currently selected. |
| * Setting this property deselects the currently selected |
| * item and selects the newly specified item. |
| * |
| * <p>Setting <code>selectedItem</code> to an item that is not |
| * in this component results in no selection, |
| * and <code>selectedItem</code> being set to <code>undefined</code>.</p> |
| * |
| * <p>If the selected item is removed, the selected item is set to:</p> |
| * |
| * <ul> |
| * <li><code>undefined</code> if <code>requireSelection</code> = <code>false</code> |
| * or there are no remaining items.</li> |
| * <li>The first item if <code>requireSelection</code> = <code>true</code> |
| * and there is at least one item.</li> |
| * </ul> |
| * |
| * <p>When the user changes the <code>selectedItem</code> property by interacting with the control, |
| * the control dispatches the <code>change</code> and <code>changing</code> events. |
| * When you change the value of the <code>selectedItem</code> property programmatically, |
| * it dispatches the <code>valueCommit</code> event.</p> |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectedItem():* |
| { |
| if (_pendingSelectedItem !== undefined) |
| return _pendingSelectedItem; |
| |
| if (allowCustomSelectedItem && selectedIndex == CUSTOM_SELECTED_ITEM) |
| return _selectedItem; |
| |
| if (selectedIndex < 0 || dataProvider == null) |
| return undefined; |
| |
| return dataProvider.length > selectedIndex ? dataProvider.getItemAt(selectedIndex) : undefined; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set selectedItem(value:*):void |
| { |
| setSelectedItem(value, false); |
| } |
| |
| /** |
| * @private |
| * Used internally to specify whether the selectedItem changed programmatically or due to |
| * user interaction. |
| * |
| * @param dispatchChangeEvent if true, the component will dispatch a "change" event if the |
| * value has changed. Otherwise, it will dispatch a "valueCommit" event. |
| */ |
| mx_internal function setSelectedItem(value:*, dispatchChangeEvent:Boolean = false):void |
| { |
| if (selectedItem === value) |
| return; |
| |
| if (dispatchChangeEvent) |
| dispatchChangeAfterSelection = (dispatchChangeAfterSelection || dispatchChangeEvent); |
| |
| // ensure that a "valueCommit" is dispatched even if the selectedIndex did not change. |
| selectedIndexAdjusted = true; |
| |
| _pendingSelectedItem = value; |
| invalidateProperties(); |
| } |
| |
| //---------------------------------- |
| // useVirtualLayout |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _useVirtualLayout:Boolean = false; |
| |
| /** |
| * Sets the value of the <code>useVirtualLayout</code> property |
| * of the layout associated with this control. |
| * If the layout is subsequently replaced and the value of this |
| * property is <code>true</code>, then the new layout's |
| * <code>useVirtualLayout</code> property is set to <code>true</code>. |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get useVirtualLayout():Boolean |
| { |
| return (layout) ? layout.useVirtualLayout : _useVirtualLayout; |
| } |
| |
| /** |
| * @private |
| * Note: this property deviates a little from the conventional delegation pattern. |
| * If the user explicitly sets ListBase.useVirtualLayout=false and then sets |
| * the layout property to a layout with useVirtualLayout=true, the layout's value |
| * for this property trumps the ListBase. The convention dictates opposite |
| * however in this case, always honoring the layout's useVirtalLayout property seems |
| * less likely to cause confusion. |
| */ |
| public function set useVirtualLayout(value:Boolean):void |
| { |
| if (value == useVirtualLayout) |
| return; |
| |
| _useVirtualLayout = value; |
| if (layout) |
| layout.useVirtualLayout = value; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function initializeAccessibility():void |
| { |
| if (ListBase.createAccessibilityImplementation != null) |
| ListBase.createAccessibilityImplementation(this); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| var e:IndexChangeEvent; |
| var changedSelection:Boolean = false; |
| |
| super.commitProperties(); |
| |
| if (dataProviderChanged) |
| { |
| dataProviderChanged = false; |
| doingWholesaleChanges = false; |
| |
| if (selectedIndex >= 0 && dataProvider && selectedIndex < dataProvider.length) |
| itemSelected(selectedIndex, true); |
| else if (requireSelection) |
| _proposedSelectedIndex = 0; |
| else |
| setSelectedIndex(-1, false); |
| } |
| |
| if (requireSelectionChanged) |
| { |
| requireSelectionChanged = false; |
| |
| if (requireSelection && |
| selectedIndex == NO_SELECTION && |
| dataProvider && |
| dataProvider.length > 0) |
| { |
| // Set the proposed selected index here to make sure |
| // commitSelection() is called below. |
| _proposedSelectedIndex = 0; |
| } |
| } |
| |
| if (_pendingSelectedItem !== undefined) |
| { |
| if (dataProvider) |
| _proposedSelectedIndex = dataProvider.getItemIndex(_pendingSelectedItem); |
| else |
| _proposedSelectedIndex = NO_SELECTION; |
| |
| if (allowCustomSelectedItem && _proposedSelectedIndex == -1 && _pendingSelectedItem != null) |
| { |
| _proposedSelectedIndex = CUSTOM_SELECTED_ITEM; |
| _selectedItem = _pendingSelectedItem; |
| } |
| |
| _pendingSelectedItem = undefined; |
| } |
| |
| if (_proposedSelectedIndex != NO_PROPOSED_SELECTION) |
| changedSelection = commitSelection(); |
| |
| // If the selectedIndex has been adjusted to account for items that |
| // have been added or removed, or the selectedItem has changed because of a dp collection |
| // event which didn't cause the selectedIndex to change, send out a "change" event so any |
| // bindings to selectedIndex/selectedItem are updated correctly. |
| if (selectedIndexAdjusted) |
| { |
| selectedIndexAdjusted = false; |
| if (!changedSelection) |
| { |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| } |
| |
| if (caretIndexAdjusted) |
| { |
| caretIndexAdjusted = false; |
| if (!changedSelection) |
| { |
| // Put the new caretIndex renderer into the |
| // caret state and dispatch an "caretChange" |
| // event to update any bindings. Additionally, update |
| // the backing variable. |
| itemShowingCaret(selectedIndex, true); |
| _caretIndex = selectedIndex; |
| |
| e = new IndexChangeEvent(IndexChangeEvent.CARET_CHANGE); |
| e.oldIndex = caretIndex; |
| e.newIndex = caretIndex; |
| dispatchEvent(e); |
| } |
| } |
| |
| if (labelFieldOrFunctionChanged) |
| { |
| // Cycle through all instantiated renderers to push the correct text |
| // in to the renderer by setting its label property |
| if (dataGroup) |
| { |
| var itemIndex:int; |
| |
| // if virtual layout, only loop through the indices in view |
| // otherwise, loop through all of the item renderers |
| if (layout && layout.useVirtualLayout) |
| { |
| for each (itemIndex in dataGroup.getItemIndicesInView()) |
| { |
| updateRendererLabelProperty(itemIndex); |
| } |
| } |
| else |
| { |
| var n:int = dataGroup.numElements; |
| for (itemIndex = 0; itemIndex < n; itemIndex++) |
| { |
| updateRendererLabelProperty(itemIndex); |
| } |
| } |
| } |
| |
| labelFieldOrFunctionChanged = false; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function updateRendererLabelProperty(itemIndex:int):void |
| { |
| // grab the renderer at that index and re-compute it's label property |
| var renderer:IItemRenderer = dataGroup.getElementAt(itemIndex) as IItemRenderer; |
| if (renderer) |
| renderer.label = itemToLabel(renderer.data); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function partAdded(partName:String, instance:Object):void |
| { |
| super.partAdded(partName, instance); |
| |
| if (instance == dataGroup) |
| { |
| // Not your typical delegation, see 'set useVirtualLayout' |
| if (_useVirtualLayout && dataGroup.layout && dataGroup.layout.virtualLayoutSupported) |
| dataGroup.layout.useVirtualLayout = true; |
| |
| dataGroup.addEventListener( |
| RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler); |
| dataGroup.addEventListener( |
| RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function partRemoved(partName:String, instance:Object):void |
| { |
| super.partRemoved(partName, instance); |
| |
| if (instance == dataGroup) |
| { |
| dataGroup.removeEventListener( |
| RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler); |
| dataGroup.removeEventListener( |
| RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override public function updateRenderer(renderer:IVisualElement, itemIndex:int, data:Object):void |
| { |
| // We keep track of whether we are in updateRenderer(). This is |
| // so we know that any calls to itemSelected() and itemShowingCaret(), |
| // are called based on a user-action (either interaction or programattic) |
| // and not through item renderer recycling. |
| inUpdateRenderer = true; |
| |
| // We need to deal with transitions appropriately. If selection transitions |
| // are on for updateRenderer() but caret transitions are not, then we want to |
| // set the caret first (with transitions off) and then set the selection. |
| // We do it in this order for this case because we want the last change |
| // we make to cause a transition (if it's on). |
| // Otherwise, we set selection first and then caret. |
| if (allowSelectionTransitions && !allowCaretTransitions) |
| { |
| // no transitions for caret, so turn them off since this is just |
| // due to item renderer recycling |
| if (renderer is ItemRenderer) |
| ItemRenderer(renderer).playTransitions = false; |
| |
| itemShowingCaret(itemIndex, isItemIndexShowingCaret(itemIndex)); |
| |
| // turn on transitions for selection |
| if (renderer is ItemRenderer) |
| ItemRenderer(renderer).playTransitions = true; |
| |
| itemSelected(itemIndex, isItemIndexSelected(itemIndex)); |
| } |
| else |
| { |
| // if selection transitions are on, turn them on. otherwise, turn them off, |
| // so we don't see them for item renderer recycling |
| if (renderer is ItemRenderer) |
| ItemRenderer(renderer).playTransitions = allowSelectionTransitions; |
| |
| itemSelected(itemIndex, isItemIndexSelected(itemIndex)); |
| |
| // deal with transitions for caret appropriately |
| if (renderer is ItemRenderer) |
| ItemRenderer(renderer).playTransitions = allowCaretTransitions; |
| |
| itemShowingCaret(itemIndex, isItemIndexShowingCaret(itemIndex)); |
| } |
| |
| // Now turn the transitions back on by setting playTransitions |
| // back to true |
| if (renderer is ItemRenderer) |
| ItemRenderer(renderer).playTransitions = true; |
| |
| // Now run through and initialize the renderer correctly. We |
| // call super.updateRenderer() last because super.updateRenderer() |
| // sets the data on the item renderer, and that should be done last. |
| super.updateRenderer(renderer, itemIndex, data); |
| |
| // let us know we're out of updateRenderer() |
| inUpdateRenderer = false; |
| } |
| |
| /** |
| * Given a data item, return the correct text a renderer |
| * should display while taking the <code>labelField</code> |
| * and <code>labelFunction</code> properties into account. |
| * |
| * @param item A data item |
| * |
| * @return String representing the text to display for the |
| * data item in the renderer. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| override public function itemToLabel(item:Object):String |
| { |
| return LabelUtil.itemToLabel(item, labelField, labelFunction); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * This will search through a dataprovider checking the given field and for the given value and return the index for the match. |
| * It can start the find from a given startingIndex; |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function findRowIndex(field:String, value:String, startingIndex:int = 0, patternType:String = RegExPatterns.EXACT):int |
| { |
| var pattern:RegExp; |
| var currentObject:Object = null; |
| var dataProviderTotal:int = 0; |
| var loopingIndex:int = startingIndex; |
| |
| |
| pattern = RegExPatterns.createRegExp(value, patternType); |
| |
| |
| if (dataProvider && dataProvider.length > 0) |
| { |
| dataProviderTotal = dataProvider.length; |
| |
| if (startingIndex >= dataProviderTotal) |
| { |
| return -1; |
| } |
| |
| |
| for (loopingIndex; loopingIndex < dataProviderTotal; loopingIndex++) |
| { |
| currentObject = dataProvider.getItemAt(loopingIndex); |
| |
| if (currentObject.hasOwnProperty(field) == true && pattern.test(currentObject[field]) == true) |
| { |
| return loopingIndex; |
| } |
| } |
| |
| } |
| |
| return -1; |
| } |
| |
| |
| /** |
| * This will search through a dataprovider checking the given field and for the given values and return an array of indices that matched. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function findRowIndices(field:String, values:Array, patternType:String = RegExPatterns.EXACT):Array |
| { |
| var currentObject:Object = null; |
| var regexList:Array = []; |
| var matchedIndices:Array = []; |
| var dataProviderTotal:uint = 0; |
| var valuesTotal:uint = 0; |
| var loopingDataProviderIndex:uint = 0; |
| var loopingValuesIndex:uint = 0; |
| |
| |
| if (dataProvider != null && dataProvider.length > 0 && values != null && values.length > 0) |
| { |
| dataProviderTotal = dataProvider.length; |
| valuesTotal = values.length; |
| |
| |
| //Set the regex patterns in an array once. |
| for (loopingValuesIndex = 0; loopingValuesIndex < valuesTotal; loopingValuesIndex++) |
| { |
| regexList.push(RegExPatterns.createRegExp(values[loopingValuesIndex], patternType)); |
| } |
| |
| |
| //Loop through dataprovider |
| for (loopingDataProviderIndex; loopingDataProviderIndex < dataProviderTotal; loopingDataProviderIndex++) |
| { |
| currentObject = dataProvider.getItemAt(loopingDataProviderIndex); |
| |
| if (currentObject.hasOwnProperty(field) == false) |
| { |
| continue; |
| } |
| |
| //Loop through regex patterns from the values array. |
| for (loopingValuesIndex = 0; loopingValuesIndex < valuesTotal; loopingValuesIndex++) |
| { |
| if (regexList[loopingValuesIndex].test(currentObject[field]) == true) |
| { |
| matchedIndices.push(loopingDataProviderIndex); |
| |
| break; |
| } |
| } |
| } |
| |
| } |
| |
| |
| return matchedIndices; |
| } |
| |
| |
| /** |
| * Called when an item is selected or deselected. |
| * Subclasses must override this method to display the selection. |
| * |
| * @param index The item index that was selected. |
| * |
| * @param selected <code>true</code> if the item is selected, |
| * and <code>false</code> if it is deselected. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| protected function itemSelected(index:int, selected:Boolean):void |
| { |
| // if this method is called from anywhere besides the updateRenderer(), |
| // then we want transitions for selection to play. This way if it's called |
| // from the selectedIndex setter or through the keyDown handler or through |
| // commitProperties(), transitions will be enabled for it. |
| if (!inUpdateRenderer) |
| turnOnSelectionTransitionsForOneFrame(); |
| |
| // Subclasses must override this method to display the selection. |
| } |
| |
| /** |
| * Called when an item is in its caret state or not. |
| * Subclasses must override this method to display the caret. |
| * |
| * @param index The item index that was put into caret state. |
| * |
| * @param showsCaret <code>true</code> if the item is in the caret state, |
| * and <code>false</code> if it is not. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| protected function itemShowingCaret(index:int, showsCaret:Boolean):void |
| { |
| // if this method is called from anywhere besides the updateRenderer(), |
| // then we want transitions for caret to play. This way if it's called |
| // from the keyDown handler or through |
| // commitProperties(), transitions will be enabled for it. |
| if (!inUpdateRenderer) |
| turnOnCaretTransitionsForOneFrame(); |
| |
| // Subclasses must override this method to display the caret. |
| } |
| |
| /** |
| * Returns <code>true</code> if the item at the index is selected. |
| * |
| * @param index The index of the item whose selection status is being checked |
| * |
| * @return <code>true</code> if the item at that index is selected, |
| * and <code>false</code> otherwise. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| mx_internal function isItemIndexSelected(index:int):Boolean |
| { |
| return index == selectedIndex; |
| } |
| |
| /** |
| * Returns true if the item at the index is the caret item, which is |
| * essentially the item in focus. |
| * |
| * @param index The index of the item whose caret status is being checked |
| * |
| * @return true if the item at that index is the caret item, false otherwise. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| mx_internal function isItemIndexShowingCaret(index:int):Boolean |
| { |
| return index == caretIndex; |
| } |
| |
| |
| /** |
| * This will search through a dataprovider checking the given field and will set the selectedIndex to a matching value. |
| * It can start the search from the startingIndex; |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| * |
| */ |
| public function moveIndexFindRow(field:String, value:String, startingIndex:int = 0, patternType:String = RegExPatterns.EXACT):Boolean |
| { |
| var indexFound:int = -1; |
| |
| indexFound = findRowIndex(field, value, startingIndex, patternType); |
| |
| if (indexFound != -1) |
| { |
| selectedIndex = indexFound; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /** |
| * Changes the selectedIndex to the first row of the dataProvider. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function moveIndexFirstRow():void |
| { |
| if (dataProvider && dataProvider.length > 0) |
| { |
| selectedIndex = 0; |
| } |
| } |
| |
| |
| /** |
| * Changes the selectedIndex to the last row of the dataProvider. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function moveIndexLastRow():void |
| { |
| if (dataProvider && dataProvider.length > 0) |
| { |
| selectedIndex = dataProvider.length - 1; |
| } |
| } |
| |
| |
| /** |
| * Changes the selectedIndex to the next row of the dataProvider. If there isn't a current selectedIndex, it silently returns. |
| * If the selectedIndex is on the first row, it does not wrap around. However the <code>isFirstRow</code> property returns true. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function moveIndexNextRow():void |
| { |
| if (dataProvider && dataProvider.length > 0 && selectedIndex >= 0) |
| { |
| if (isLastRow == false) |
| { |
| selectedIndex += 1; |
| } |
| } |
| } |
| |
| |
| /** |
| * Changes the selectedIndex to the previous row of the dataProvider. If there isn't a current selectedIndex, it silently returns. |
| * If the selectedIndex is on the last row, it does not wrap around. However the <code>isLastRow</code> property returns true. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11.1 |
| * @playerversion AIR 3.4 |
| * @productversion Flex 4.10 |
| */ |
| public function moveIndexPreviousRow():void |
| { |
| if (dataProvider && dataProvider.length > 0 && selectedIndex >= 0) |
| { |
| if (isFirstRow == false) |
| { |
| selectedIndex -= 1; |
| } |
| } |
| } |
| |
| |
| /** |
| * @private |
| * Set current caret index. This function takes the item that was |
| * previously the caret item and sets its caret property to false, |
| * then takes the current proposed caret item and sets its |
| * caret property to true as well as updating the backing variable. |
| * |
| */ |
| mx_internal function setCurrentCaretIndex(value:Number):void |
| { |
| if (value == caretIndex) |
| return; |
| |
| itemShowingCaret(caretIndex, false); |
| |
| _caretIndex = value; |
| |
| if (caretIndex != CUSTOM_SELECTED_ITEM) |
| { |
| itemShowingCaret(_caretIndex, true); |
| |
| // Track the caretItem (often the same as the selectedItem), so that |
| // if the dataProvider is refreshed, we can relocate the caretIndex. |
| |
| const validIndex:Boolean = dataProvider && (_caretIndex >= 0) && (_caretIndex < dataProvider.length); |
| caretItem = (validIndex) ? dataProvider.getItemAt(_caretIndex) : undefined; |
| } |
| } |
| |
| /** |
| * @private |
| * The selection validation and commitment workhorse method. |
| * Called to commit the pending selected index. This method dispatches |
| * the "changing" event, and if the event is not cancelled, |
| * commits the selection change and then dispatches the "change" |
| * event. |
| * |
| * Returns true if the selection was committed, or false if the selection |
| * was cancelled. |
| */ |
| protected function commitSelection(dispatchChangedEvents:Boolean = true):Boolean |
| { |
| // Step 1: make sure the proposed selected index is in range. |
| var maxIndex:int = dataProvider ? dataProvider.length - 1 : -1; |
| var oldSelectedIndex:int = _selectedIndex; |
| var oldCaretIndex:int = _caretIndex; |
| var e:IndexChangeEvent; |
| |
| |
| //Prevents an in item from being selected. Stops the change before it sends out changing events. |
| if (_preventSelection == true) |
| { |
| if (_selectedIndex != NO_SELECTION) |
| { |
| itemSelected(NO_SELECTION, false); |
| _selectedIndex = NO_SELECTION; |
| } |
| |
| //Cancel the selection change and return false. |
| itemSelected(_proposedSelectedIndex, false); |
| _proposedSelectedIndex = NO_PROPOSED_SELECTION; |
| dispatchChangeAfterSelection = false; |
| |
| return false; |
| } |
| |
| |
| if (!allowCustomSelectedItem || _proposedSelectedIndex != CUSTOM_SELECTED_ITEM) |
| { |
| if (_proposedSelectedIndex < NO_SELECTION) |
| _proposedSelectedIndex = NO_SELECTION; |
| if (_proposedSelectedIndex > maxIndex) |
| _proposedSelectedIndex = maxIndex; |
| if (requireSelection && _proposedSelectedIndex == NO_SELECTION && |
| dataProvider && dataProvider.length > 0) |
| { |
| _proposedSelectedIndex = NO_PROPOSED_SELECTION; |
| return false; |
| } |
| } |
| |
| // Caching value of proposed index prevents its being changed in the dispatch |
| // of the changing event, if that results in a call into this function |
| var tmpProposedIndex:int = _proposedSelectedIndex; |
| |
| // Step 2: dispatch the "changing" event. If preventDefault() is called |
| // on this event, the selection change will be cancelled. |
| if (dispatchChangeAfterSelection) |
| { |
| e = new IndexChangeEvent(IndexChangeEvent.CHANGING, false, true); |
| e.oldIndex = _selectedIndex; |
| e.newIndex = _proposedSelectedIndex; |
| if (!dispatchEvent(e)) |
| { |
| // The event was cancelled. Cancel the selection change and return. |
| itemSelected(_proposedSelectedIndex, false); |
| _proposedSelectedIndex = NO_PROPOSED_SELECTION; |
| dispatchChangeAfterSelection = false; |
| return false; |
| } |
| } |
| |
| // Step 3: commit the selection change and caret change |
| _selectedIndex = tmpProposedIndex; |
| _proposedSelectedIndex = NO_PROPOSED_SELECTION; |
| |
| if (oldSelectedIndex != NO_SELECTION) |
| itemSelected(oldSelectedIndex, false); |
| if (_selectedIndex != NO_SELECTION && _selectedIndex != CUSTOM_SELECTED_ITEM) |
| itemSelected(_selectedIndex, true); |
| if (changeCaretOnSelection) |
| setCurrentCaretIndex(_selectedIndex); |
| |
| // Step 4: dispatch the "change" event and "caretChange" |
| // events based on the dispatchChangeEvents parameter. Overrides may |
| // chose to dispatch the change/caretChange events |
| // themselves, in which case we wouldn't want to dispatch the event |
| // here. |
| if (dispatchChangedEvents) |
| { |
| // Dispatch the change event |
| if (dispatchChangeAfterSelection) |
| { |
| e = new IndexChangeEvent(IndexChangeEvent.CHANGE); |
| e.oldIndex = oldSelectedIndex; |
| e.newIndex = _selectedIndex; |
| dispatchEvent(e); |
| dispatchChangeAfterSelection = false; |
| } |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| |
| //Dispatch the caretChange event |
| if (changeCaretOnSelection) |
| { |
| e = new IndexChangeEvent(IndexChangeEvent.CARET_CHANGE); |
| e.oldIndex = oldCaretIndex; |
| e.newIndex = caretIndex; |
| dispatchEvent(e); |
| } |
| } |
| |
| // default changeCaretOnSelection back to true |
| changeCaretOnSelection = true; |
| |
| return true; |
| } |
| |
| /** |
| * Adjusts the selected index to account for items being added to or |
| * removed from this component. This method adjusts the selected index |
| * value and dispatches a <code>valueCommit</code> event. It does not |
| * dispatch a <code>change</code> event because the change did not |
| * occur as a direct result of user-interaction. Moreover, |
| * it does not dispatch a <code>changing</code> event |
| * or allow the cancellation of the selection. |
| * It also does not call the <code>itemSelected()</code> method, |
| * since the same item is selected; |
| * the only thing that has changed is the index of the item. |
| * |
| * <p>A <code>valueCommit</code> event is dispatched in the next call to |
| * the <code>commitProperties()</code> method.</p> |
| * |
| * <p>The <code>change</code> and <code>changing</code> events are |
| * not sent when the <code>selectedIndex</code> is adjusted.</p> |
| * |
| * @param newIndex The new index. |
| * |
| * @param add <code>true</code> if an item was added to the component, |
| * and <code>false</code> if an item was removed. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| protected function adjustSelection(newIndex:int, add:Boolean=false):void |
| { |
| if (_proposedSelectedIndex != NO_PROPOSED_SELECTION) |
| _proposedSelectedIndex = newIndex; |
| else |
| _selectedIndex = newIndex; |
| selectedIndexAdjusted = true; |
| invalidateProperties(); |
| } |
| |
| /** |
| * Called when an item has been added to this component. Selection |
| * and caret related properties are adjusted accordingly. |
| * |
| * @param index The index of the item being added. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| protected function itemAdded(index:int):void |
| { |
| // if doing wholesale changes, we'll handle this more effeciently in commitProperties() with dataProviderChanged == true |
| if (doingWholesaleChanges) |
| return; |
| |
| if (selectedIndex == NO_SELECTION) |
| { |
| // If there's no selection, there's nothing to adjust unless |
| // we requireSelection and need to select what was added |
| if (requireSelection) |
| adjustSelection(index); |
| } |
| else if (index <= selectedIndex) |
| { |
| // If an item is added before the selected item, bump up our |
| // selected index backing variable. |
| adjustSelection(selectedIndex + 1); |
| } |
| } |
| |
| /** |
| * Called when an item has been removed from this component. |
| * Selection and caret related properties are adjusted |
| * accordingly. |
| * |
| * @param index The index of the item being removed. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| protected function itemRemoved(index:int):void |
| { |
| if (selectedIndex == NO_SELECTION || doingWholesaleChanges) |
| return; |
| |
| // If the selected item is being removed, clear the selection (or |
| // reset to the first item if requireSelection is true) |
| if (index == selectedIndex) |
| { |
| if (requireSelection && dataProvider && dataProvider.length > 0) |
| { |
| if (index == 0) |
| { |
| //We can't just set selectedIndex to 0 directly |
| //since the previous value was 0 and the new value is |
| //0, so the setter will return early. |
| _proposedSelectedIndex = 0; |
| invalidateProperties(); |
| } |
| else |
| setSelectedIndex(0, false); |
| } |
| else |
| adjustSelection(-1); |
| } |
| else if (index < selectedIndex) |
| { |
| // An item below the selected index has been removed, bump |
| // the selected index backing variable. |
| adjustSelection(selectedIndex - 1); |
| } |
| } |
| |
| /** |
| * @private |
| * Default response to dataProvider refresh events: clear the selection and caret. |
| */ |
| mx_internal function dataProviderRefreshed():void |
| { |
| if (dataProvider && dataProvider.length > 0 && requireSelection == true) |
| { |
| setSelectedIndex(0, false); |
| } |
| else |
| { |
| selectedItem = undefined; |
| setSelectedIndex(NO_SELECTION, false); |
| // TODO (rfrishbe): probably don't need the setCurrentCaretIndex below |
| setCurrentCaretIndex(NO_CARET); |
| } |
| } |
| |
| /** |
| * @private |
| * Turns on selection transitions for one frame. We turn it on for |
| * one frame to make sure that no matter how the property gets set |
| * (synchronously in the event handler or asynchronously during validation) |
| * that we make sure selection transitions will play appropriately. |
| */ |
| private function turnOnSelectionTransitionsForOneFrame():void |
| { |
| // if already on, no need to run this again |
| // or if there's no systemManager, then we're not really initialized, so let's |
| // just keep transitions off. |
| if (allowSelectionTransitions || !systemManager) |
| return; |
| |
| allowSelectionTransitions = true; |
| |
| // We want to wait one frame after validation has occured before turning off |
| // selection transitions again. We tried using dataGroup's updateComplete event listener, |
| // but because some validateNows() occur (before all of the event handling code has finished), |
| // this was occuring too early. We also tried just using callLater or ENTER_FRAME, but that |
| // occurs before the LayoutManager has run, so we add an ENTER_FRAME handler with a |
| // low priority to make sure it occurs after the LayoutManager pass. |
| systemManager.addEventListener(Event.ENTER_FRAME, allowSelectionTransitions_enterFrameHandler, false, -100); |
| } |
| |
| /** |
| * @private |
| * After waiting a frame, we want to turn off selection transitions |
| */ |
| private function allowSelectionTransitions_enterFrameHandler(event:Event):void |
| { |
| event.target.removeEventListener(Event.ENTER_FRAME, allowSelectionTransitions_enterFrameHandler); |
| |
| allowSelectionTransitions = false; |
| } |
| |
| /** |
| * @private |
| * Turns on caret transitions for one frame. We turn it on for |
| * one frame to make sure that no matter how the property gets set |
| * (synchronously in the event handler or asynchronously during validation) |
| * that we make sure caret transitions will play appropriately. |
| */ |
| private function turnOnCaretTransitionsForOneFrame():void |
| { |
| // if already on, no need to run this again |
| // or if there's no systemManager, then we're not really initialized, so let's |
| // just keep transitions off. |
| if (allowCaretTransitions || !systemManager) |
| return; |
| |
| allowCaretTransitions = true; |
| |
| // We want to wait one frame after validation has occured before turning off |
| // selection transitions again. We tried using dataGroup's updateComplete event listener, |
| // but because some validateNows() occur (before all of the event handling code has finished), |
| // this was occuring too early. We also tried just using callLater or ENTER_FRAME, but that |
| // occurs before the LayoutManager has run, so we add an ENTER_FRAME handler with a |
| // low priority to make sure it occurs after the LayoutManager pass. |
| systemManager.addEventListener(Event.ENTER_FRAME, allowCaretTransitions_enterFrameHandler, false, -100); |
| } |
| |
| /** |
| * @private |
| * After waiting a frame, we want to turn off caret transitions |
| */ |
| private function allowCaretTransitions_enterFrameHandler(event:Event):void |
| { |
| event.target.removeEventListener(Event.ENTER_FRAME, allowCaretTransitions_enterFrameHandler); |
| |
| allowCaretTransitions = false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Called when an item has been added to this component. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10.2 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| protected function dataGroup_rendererAddHandler(event:RendererExistenceEvent):void |
| { |
| var renderer:IVisualElement = event.renderer; |
| |
| if (!renderer) |
| return; |
| |
| renderer.addEventListener(MouseEvent.ROLL_OVER, item_mouseEventHandler); |
| renderer.addEventListener(MouseEvent.ROLL_OUT, item_mouseEventHandler); |
| } |
| |
| /** |
| * @private |
| * Called when an item has been removed from this component. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10.2 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| protected function dataGroup_rendererRemoveHandler(event:RendererExistenceEvent):void |
| { |
| var renderer:IVisualElement = event.renderer; |
| |
| if (!renderer) |
| return; |
| |
| renderer.removeEventListener(MouseEvent.ROLL_OVER, item_mouseEventHandler); |
| renderer.removeEventListener(MouseEvent.ROLL_OUT, item_mouseEventHandler); |
| } |
| |
| /** |
| * @private |
| * Called on rollover or roll out. |
| */ |
| private function item_mouseEventHandler(event:MouseEvent):void |
| { |
| var type:String = event.type; |
| type = TYPE_MAP[type]; |
| if (hasEventListener(type) && dataProvider != null) |
| { |
| var itemRenderer:IItemRenderer = event.currentTarget as IItemRenderer; |
| |
| var itemIndex:int = -1; |
| if (itemRenderer) |
| itemIndex = itemRenderer.itemIndex; |
| else |
| itemIndex = dataGroup.getElementIndex(event.currentTarget as IVisualElement); |
| // The event can be called by an item renderer which has already been removed from the dataProvider. |
| // In that case, bail out. |
| if(itemIndex < 0 || itemIndex >= dataProvider.length) |
| return; |
| |
| var listEvent:ListEvent = new ListEvent(type, false, false, |
| event.localX, |
| event.localY, |
| event.relatedObject, |
| event.ctrlKey, |
| event.altKey, |
| event.shiftKey, |
| event.buttonDown, |
| event.delta, |
| itemIndex, |
| dataProvider.getItemAt(itemIndex), |
| itemRenderer); |
| dispatchEvent(listEvent); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when contents within the dataProvider changes. |
| * |
| * @param event The collection change event |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| protected function dataProvider_collectionChangeHandler(event:Event):void |
| { |
| if (event is CollectionEvent) |
| { |
| var ce:CollectionEvent = CollectionEvent(event); |
| |
| if (ce.kind == CollectionEventKind.ADD) |
| { |
| itemAdded(ce.location); |
| } |
| else if (ce.kind == CollectionEventKind.REMOVE) |
| { |
| itemRemoved(ce.location); |
| } |
| else if (ce.kind == CollectionEventKind.RESET) |
| { |
| // Data provider is being reset, clear out the selection which includes the |
| // selectedItem so that any bindings on the selectedItem are triggered. |
| selectedItem = undefined; |
| if (dataProvider.length == 0) |
| { |
| setSelectedIndex(NO_SELECTION, false); |
| // TODO (rfrishbe): probably don't need the setCurrentCaretIndex below |
| setCurrentCaretIndex(NO_CARET); |
| } |
| else |
| { |
| dataProviderChanged = true; |
| invalidateProperties(); |
| } |
| } |
| else if (ce.kind == CollectionEventKind.REFRESH) |
| { |
| dataProviderRefreshed(); |
| } |
| else if (ce.kind == CollectionEventKind.REPLACE || |
| ce.kind == CollectionEventKind.MOVE) |
| { |
| //These cases are handled by the DataGroup skinpart |
| } |
| } |
| |
| } |
| } |
| |
| } |