blob: fb0676f2babca3d0bb7f38244190fbac489ab779 [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 spark.components
{
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import mx.collections.ArrayCollection;
import mx.collections.ComplexFieldChangeWatcher;
import mx.collections.ICollectionView;
import mx.collections.IComplexSortField;
import mx.collections.IList;
import mx.collections.ISort;
import mx.collections.ISortField;
import mx.collections.ListCollectionView;
import mx.core.DragSource;
import mx.core.EventPriority;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IIMESupport;
import mx.core.IUID;
import mx.core.IVisualElement;
import mx.core.InteractionMode;
import mx.core.LayoutDirection;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.events.DragEvent;
import mx.events.FlexEvent;
import mx.events.SandboxMouseEvent;
import mx.events.TouchInteractionEvent;
import mx.managers.CursorManager;
import mx.managers.CursorManagerPriority;
import mx.managers.DragManager;
import mx.managers.IFocusManagerComponent;
import mx.styles.IAdvancedStyleClient;
import mx.utils.ObjectUtil;
import mx.utils.UIDUtil;
import spark.collections.Sort;
import spark.components.gridClasses.CellPosition;
import spark.components.gridClasses.CellRegion;
import spark.components.gridClasses.DataGridEditor;
import spark.components.gridClasses.GridColumn;
import spark.components.gridClasses.GridItemEditorActivationMouseEvent;
import spark.components.gridClasses.GridLayout;
import spark.components.gridClasses.GridSelection;
import spark.components.gridClasses.GridSelectionMode;
import spark.components.gridClasses.GridView;
import spark.components.gridClasses.GridViewLayout;
import spark.components.gridClasses.IDataGridElement;
import spark.components.gridClasses.IGridItemEditor;
import spark.components.supportClasses.IDataProviderEnhance;
import spark.components.supportClasses.RegExPatterns;
import spark.components.supportClasses.SkinnableContainerBase;
import spark.core.NavigationUnit;
import spark.events.GridCaretEvent;
import spark.events.GridEvent;
import spark.events.GridSelectionEvent;
import spark.events.GridSelectionEventKind;
import spark.events.GridSortEvent;
import spark.layouts.supportClasses.DropLocation;
use namespace mx_internal;
//--------------------------------------
// Styles
//--------------------------------------
/**
* Used to initialize the DataGrid's <code>rowBackground</code> skin part.
* If the <code>alternatingRowColors</code> style is specified,
* then use the <code>alternatingRowColorsBackground</code> skin part
* as the value of the <code>rowBackground</code> skin part.
* The alternating colors for the grid rows are defined by
* successive entries in the Array value of this style.
*
* <p>If you want to change how this style is rendered,
* replace the <code>alternatingRowColorsBackground</code> skin part
* in the DataGridSkin class.
* If you want to specify the background for each row, then
* initialize the <code>rowBackground</code> skin part directly.</p>
*
* @default undefined
*
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="alternatingRowColors", type="Array", arrayType="uint", format="Color", inherit="no", theme="spark")]
/**
* The alpha value of the border for this component.
* Valid values are 0.0 to 1.0.
*
* @default 1.0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="borderAlpha", type="Number", inherit="no", theme="spark", minValue="0.0", maxValue="1.0")]
/**
* The color of the border for this component.
*
* @default #696969
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="borderColor", type="uint", format="Color", inherit="no", theme="spark")]
/**
* Controls the visibility of the border for this component.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="borderVisible", type="Boolean", inherit="no", theme="spark")]
/**
* Color of the caret indicator when navigating the Grid.
*
* @default 0x0167FF
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="caretColor", type="uint", format="Color", inherit="yes", theme="spark")]
/**
* The alpha of the content background for this component.
* Valid values are 0.0 to 1.0.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="contentBackgroundAlpha", type="Number", inherit="yes", theme="spark", minValue="0.0", maxValue="1.0")]
/**
* @copy spark.components.supportClasses.GroupBase#style:contentBackgroundColor
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="contentBackgroundColor", type="uint", format="Color", inherit="yes", theme="spark")]
/**
* @copy spark.components.supportClasses.GroupBase#style:rollOverColor
*
* @default 0xCEDBEF
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes", theme="spark")]
/**
* @copy spark.components.List#style:selectionColor
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="selectionColor", type="uint", format="Color", inherit="yes", theme="spark, mobile")]
/**
* The class to use as the skin for the cursor that indicates that a column
* can be resized.
* The default value is the <code>cursorStretch</code> symbol from the Assets.swf file.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="stretchCursor", type="Class", inherit="no")]
/**
* @copy spark.components.supportClasses.GroupBase#style:symbolColor
*
* @default 0x000000
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="symbolColor", type="uint", format="Color", inherit="yes", theme="spark, mobile")]
include "../styles/metadata/BasicInheritingTextStyles.as"
/**
* The class to use as the item editor, if one is not
* specified by a column.
* This style property lets you set
* an item editor for a group of DataGrid controls instead of having to
* set each one individually.
* The <code>DataGridColumn.itemEditor</code> property supercedes this value.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="defaultDataGridItemEditor", type="Class", inherit="no")]
/**
* Indicates the conditions for which the horizontal scroll bar is displayed.
*
* <ul>
* <li>
* <code>ScrollPolicy.ON</code> ("on") - The scroll bar is always displayed.
* </li>
* <li>
* <code>ScrollPolicy.OFF</code> ("off") - The scroll bar is never displayed.
* The viewport can still be scrolled programmatically, by setting its
* <code>horizontalScrollPosition</code> property.
* </li>
* <li>
* <code>ScrollPolicy.AUTO</code> ("auto") - The scroll bar is displayed when
* the viewport's <code>contentWidth</code> is larger than its width.
* </li>
* </ul>
*
* <p>
* The scroll policy affects the measured size of the scroller skin part.
* This style is a reference to the scroller skin part's
* <code>horizontalScrollPolicy</code> style.
* It is not an inheriting style
* Therefor, for example, it will not affect item renderers. </p>
*
* @default ScrollPolicy.AUTO
*
* @see mx.core.ScrollPolicy
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="horizontalScrollPolicy", type="String", inherit="no", enumeration="off,on,auto")]
/**
* Indicates under what conditions the vertical scroll bar is displayed.
*
* <ul>
* <li>
* <code>ScrollPolicy.ON</code> ("on") - The scroll bar is always displayed.
* </li>
* <li>
* <code>ScrollPolicy.OFF</code> ("off") - The scroll bar is never displayed.
* The viewport can still be scrolled programmatically, by setting its
* <code>verticalScrollPosition</code> property.
* </li>
* <li>
* <code>ScrollPolicy.AUTO</code> ("auto") - The scroll bar is displayed when
* the viewport's <code>contentHeight</code> is larger than its height.
* </li>
* </ul>
*
* <p>
* The scroll policy affects the measured size of the scroller skin part.
* This style is a reference to the scroller skin part's
* <code>verticalScrollPolicy</code> style.
* It is not an inheriting style
* Therefor, for example, it will not affect item renderers. </p>
*
* @default ScrollPolicy.AUTO
*
* @see mx.core.ScrollPolicy
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Style(name="verticalScrollPolicy", type="String", inherit="no", enumeration="off,on,auto")]
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched by the <code>grid</code> skin part when the caret position, size, or
* visibility has changed due to user interaction or being programmatically set.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridCaretEvent.CARET_CHANGE
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="caretChange", type="spark.events.GridCaretEvent")]
/**
* Dispatched by the <code>grid</code> skin part when the mouse button
* is pressed over a grid cell.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_MOUSE_DOWN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridMouseDown", type="spark.events.GridEvent")]
/**
* Dispatched by the <code>grid</code> skin part after a <code>gridMouseDown</code> event
* if the mouse moves before the button is released.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_MOUSE_DRAG
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridMouseDrag", type="spark.events.GridEvent")]
/**
* Dispatched by the <code>grid</code> skin part after a <code>gridMouseDown</code> event
* when the mouse button is released, even if the mouse is no longer within the grid.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_MOUSE_UP
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridMouseUp", type="spark.events.GridEvent")]
/**
* Dispatched by the <code>grid</code> skin part when the mouse enters a grid cell.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_ROLL_OVER
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridRollOver", type="spark.events.GridEvent")]
/**
* Dispatched by the <code>grid</code> skin part when the mouse leaves a grid cell.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_ROLL_OUT
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridRollOut", type="spark.events.GridEvent")]
/**
* Dispatched by the <code>grid</code> skin part when the mouse is clicked over a cell.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_CLICK
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridClick", type="spark.events.GridEvent")]
/**
* Dispatched by the <code>grid</code> skin part when the mouse is double-clicked over a cell.
*
* <p>To handle this event, assign an event handler to the <code>grid</code> skin part
* of the DataGrid control.</p>
*
* @eventType spark.events.GridEvent.GRID_DOUBLE_CLICK
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridDoubleClick", type="spark.events.GridEvent")]
/**
* 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 selection programmatically,
* the component does not dispatch the <code>selectionChanging</code> event. </p>
*
* @eventType spark.events.GridSelectionEvent.SELECTION_CHANGING
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="selectionChanging", type="spark.events.GridSelectionEvent")]
/**
* Dispatched when the selection has changed.
*
* <p>This event is dispatched when the user interacts with the control.
* When you change the selection programmatically,
* the component does not dispatch the <code>selectionChange</code> event.
* In either case it dispatches the <code>valueCommit</code> event as well.</p>
*
* @eventType spark.events.GridSelectionEvent.SELECTION_CHANGE
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="selectionChange", type="spark.events.GridSelectionEvent")]
/**
* Dispatched before the sort has been applied to the data provider's collection.
* Typically this is when the user releases the mouse button on a column header
* to request the control to sort the grid contents based on the contents of the column.
* Only dispatched if the column is sortable and the data provider supports sorting.
*
* <p>The DataGrid control has a default handler for this event that implements
* a single-column sort and updates the <code>visibleSortIndices</code> in the grid's
* <code>columnHeaderGroup</code> with the <code>columnIndices</code>.</p>
*
* <p>Multiple-column sort can be implemented by calling the <code>preventDefault()</code> method
* to prevent the single column sort and setting the <code>columnIndices</code> and
* <code>newSortFields</code> parameters of the event to change the default behavior.
* <code>newSortFields</code> should be set to the desired sort fields.
* <code>columnIndices</code> should be set to the indices of the columns that should
* have a visible sort indicator in the column header bar.</p>
*
* <p>This event is dispatched when the user interacts with the control.
* When you sort the data provider's collection programmatically,
* the component does not dispatch the <code>sortChanging</code> event. </p>
*
* @eventType spark.events.GridSortEvent.SORT_CHANGING
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="sortChanging", type="spark.events.GridSortEvent")]
/**
* Dispatched after the sort has been applied to the data provider's collection.
* Typically this is after the user releases the mouse button on a column header and
* the sort has been applied to the data provider's collection.
*
* <p>This event is dispatched when the user interacts with the control.
* When you sort the data provider's collection programmatically,
* the component does not dispatch the <code>sortChanging</code> event.</p>
*
* @eventType spark.events.GridSortEvent.SORT_CHANGE
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="sortChange", type="spark.events.GridSortEvent")]
//--------------------------------------
// Edit Events
//--------------------------------------
/**
* Dispatched when a new item editor session has been requested. A listener can
* dynamically determine if a cell is editable and cancel the edit (by calling
* the <code>preventDefault()</code> method) if it is not.
* A listener may also dynamically change the editor used by assigning a
* different item editor to a column.
*
* <p>If this event is canceled the item editor will not be created.</p>
*
* @eventType spark.events.GridItemEditorEvent.GRID_ITEM_EDITOR_SESSION_STARTING
*
* @see spark.components.DataGrid.itemEditorInstance
* @see flash.events.Event
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridItemEditorSessionStarting", type="spark.events.GridItemEditorEvent")]
/**
* Dispatched immediately after an item editor has been opened.
*
* @eventType spark.events.GridItemEditorEvent.GRID_ITEM_EDITOR_SESSION_START
*
* @see spark.components.DataGrid.itemEditorInstance
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridItemEditorSessionStart", type="spark.events.GridItemEditorEvent")]
/**
* Dispatched after the data in item editor has been saved into the data provider
* and the editor has been closed.
*
* @eventType spark.events.GridItemEditorEvent.GRID_ITEM_EDITOR_SESSION_SAVE
*
* @see spark.components.DataGrid.itemEditorInstance
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridItemEditorSessionSave", type="spark.events.GridItemEditorEvent")]
/**
* Dispatched after the item editor has been closed without saving its data.
*
* @eventType spark.events.GridItemEditorEvent.GRID_ITEM_EDITOR_SESSION_CANCEL
*
* @see spark.components.DataGrid.itemEditorInstance
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="gridItemEditorSessionCancel", type="spark.events.GridItemEditorEvent")]
//--------------------------------------
// Other metadata
//--------------------------------------
[AccessibilityClass(implementation="spark.accessibility.DataGridAccImpl")]
[DefaultProperty("dataProvider")]
[DefaultTriggerEvent("selectionChange")]
[DiscouragedForProfile("mobileDevice")]
[IconFile("DataGrid.png")]
/**
* The DataGrid displays a row of column headings above a scrollable grid.
* The grid is arranged as a collection of individual cells arranged
* in rows and columns.
* The DataGrid control is designed to support smooth scrolling through
* large numbers of rows and columns.
*
* <p>The Spark DataGrid control is implemented as a skinnable wrapper
* around the Spark Grid control.
* The Grid control defines the columns of the data grid, and much of
* the functionality of the DataGrid control itself.</p>
*
* <p>The DataGrid skin is responsible for laying out the grid, column header, and scroller.
* The skin also configures the graphic elements used to render visual elements
* used as indicators, separators, and backgrounds.
* The DataGrid skin also defines a default item renderer,
* used to display the contents of each cell.
* Please see the documentation for the renderer class for the list of supported styles.</p>
*
* <p>Transitions in DataGrid item renderers aren't supported. The GridItemRenderer class
* has disabled its <code>transitions</code> property so setting it will have no effect.</p>
*
* @mxml <p>The <code>&lt;s:DataGrid&gt;</code> tag inherits all of the tag
* attributes of its superclass and adds the following tag attributes:</p>
*
* <pre>
* &lt;s:DataGrid
* <strong>Properties</strong>
* columns="null"
* dataProvider="null"
* dataTipField="null"
* dataTipFunction="null"
* doubleClickMode="row"
* editable="false"
* editorColumnIndex="-1"
* editorRowIndex="-1"
* imeMode="null"
* itemEditor="null"
* itemRenderer="<i>DefaultGridItemRenderer</i>"
* preserveSelection="true"
* requestedColumnCount="-1"
* requestedMaxRowCount="-1"
* requestedMinColumnCount="-1"
* requestedMinRowCount="-1"
* requestedRowCount="-1"
* requireSelection="false"
* resizeableColumns="true"
* rowHeight="<i>Calculated default</i>"
* selectedCell="null"
* selectedCells="<i>empty Vector.&lt;CellPosition&gt</i>"
* selectedIndex="null"
* selectedIndices="<i>empty Vector.&lt;CellPosition&gt</i>"
* selectedItem="null"
* selectedItems="<i>empty Vector.&lt;Object&gt</i>"
* selectionMode="singleRow"
* showDataTips="false"
* sortableColumns="true"
* typicalItem="null"
* variableRowHeight="false"
*
* <strong>Styles</strong>
* alignmentBaseline="useDominantBaseline"
* baselineShift="0.0"
* cffHinting="horizontalStem"
* color="0"
* defaultGridItemEditor="null"
* digitCase="default"
* digitWidth="default"
* direction="ltr"
* dominantBaseline="auto"
* fontFamily="Arial"
* fontLookup="device"
* fontSize="12"
* fontStyle="normal"
* fontWeight="normal"
* justificationRule="auto"
* justificationStyle="auto"
* kerning="auto"
* ligatureLevel="common"
* lineHeight="120%"
* lineThrough="false"
* locale="en"
* renderingMode="cff"
* stretchCursor="<i>cursorStretch symbol from Assets.swf</i>"
* textAlign="start"
* textAlignLast="start"
* textAlpha="1"
* textDecoration="none"
* textJustify="interWord"
* trackingLeft="0"
* trackingRight="0"
* typographicCase="default"
* verticalScrollPolicy="auto"
*
* <strong>Styles for the Spark Theme</strong>
* alternatingRowColors="undefined"
* borderAlpha="1.0"
* borderColor="0x696969"
* borderVisible="true"
* caretColor="0x0167FF"
* contentBackgroundAlpha="1.0"
* contentBackgroundColor="0xFFFFFF"
* rollOverColor="0xCEDBEF"
* selectionColor="0xA8C6EE"
* symbolColor="0x000000"
*
* <strong>Styles for the Mobile Theme</strong>
* leading="0"
* letterSpacing="0"
* selectionColor="0xE0E0E0"
* symbolColor="0x000000"
*
* <strong>Events</strong>
* caretChange="<i>No default</i>"
* gridClick="<i>No default</i>"
* gridDoubleClick="<i>No default</i>"
* gridItemEditorSessionCancel="<i>No default</i>"
* gridItemEditorSessionSave="<i>No default</i>"
* gridItemEditorSessionStart="<i>No default</i>"
* gridItemEditorSessionStarting="<i>No default</i>"
* gridMouseDown="<i>No default</i>"
* gridMouseDrag="<i>No default</i>"
* gridMouseUp="<i>No default</i>"
* gridMouseRollOut="<i>No default</i>"
* gridMouseRollOver="<i>No default</i>"
* selectionChange="<i>No default</i>"
* selectionChanging="<i>No default</i>"
* sortChange="<i>No default</i>"
* sortChanging="<i>No default</i>"
* /&gt;
* </pre>
*
* @see spark.components.Grid
* @see spark.components.gridClasses.GridColumn
* @see spark.skins.spark.DataGridSkin
* @see spark.skins.spark.DefaultGridItemRenderer
*
* @includeExample examples/DataGridSimpleExample.mxml
* @includeExample examples/DataGridMasterDetailExample.mxml
* @includeExample examples/DataGridTypicalItemExample.mxml
* @includeExample examples/DataGridRowHeightExample.mxml
* @includeExample examples/DataGridSelectionExample.mxml
* @includeExample examples/DataGridInvalidateCellExample.mxml
* @includeExample examples/DataGridLockedRowsAndColumnsExample.mxml
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public class DataGrid extends SkinnableContainerBase
implements IDataProviderEnhance, IFocusManagerComponent, IIMESupport
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by DataGridAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function DataGrid()
{
super();
addEventListener(Event.SELECT_ALL, selectAllHandler);
addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler, false, EventPriority.DEFAULT_HANDLER);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* True, when the hover indicator should be updated on a ROLL_OVER event.
* If the mouse button is depressed while outside of the grid, the hover
* indicator is not enabled again until MOUSE_UP or ROLL_OUT.
*/
private var updateHoverOnRollOver:Boolean = true;
//--------------------------------------------------------------------------
//
// Drag and Drop Variables
//
//--------------------------------------------------------------------------
/**
* @private
* The point where the mouse down event was received.
* Used to track whether a drag operation should be initiated when the user
* drags further than a certain threshold.
*/
private var mouseDownPoint:Point;
/**
* @private
* The index of the element the mouse down event was received for. Used to
* track which is the "focus item" for a drag and drop operation.
*/
private var mouseDownRowIndex:int = -1;
/**
* @private
* The index of the element the mouse down event was received for. Used to
* track which is the "focus item" for a drag and drop operation.
*/
private var mouseDownColumnIndex:int = -1;
/**
* @private
* The displayObject where the mouse down event was received.
* In touch interactionMode, used to track whether this item is
* the one that is moused up on so we can possibly select it.
*/
private var mouseDownObject:DisplayObject;
/**
* @private
* When dragging is enabled with multiple selection, the selection is not
* comitted immediately on mouse down, but we wait to see whether the user
* intended to start a drag gesture instead. In that case we postpone
* comitting the selection until mouse up.
*/
private var pendingSelectionOnMouseUp:Boolean = false;
//--------------------------------------------------------------------------
//
// Skin Parts
//
//--------------------------------------------------------------------------
/**
* @private
* IFactory valued skin parts that require special handling, see findSkinParts().
*/
private static const factorySkinPartNames:Array = [
"alternatingRowColorsBackground",
"caretIndicator",
"columnSeparator",
"headerColumnSeparator",
"hoverIndicator",
"lockedRowsSeparator",
"lockedColumnsSeparator",
"rowBackground",
"rowSeparator",
"selectionIndicator"];
//----------------------------------
// alternatingRowColorsBackground
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the <code>alternatingRowColors</code> style.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var alternatingRowColorsBackground:IFactory;
//----------------------------------
// caretIndicator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the grid's caret indicator.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var caretIndicator:IFactory;
//----------------------------------
// columnHeaderGroup
//----------------------------------
[Bindable]
[SkinPart(required="false")]
/**
* A reference to the GridColumnHeaderGroup object that displays the column headers.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var columnHeaderGroup:GridColumnHeaderGroup;
//----------------------------------
// columnSeparator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the vertical separator between columns.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var columnSeparator:IFactory;
//----------------------------------
// dropIndicator
//----------------------------------
[SkinPart(required="false", type="flash.display.DisplayObject")]
/**
* The IVisualElement class that defines the appearance of the drop indicator.
* The drop indicator is resized and positioned by the layout to indicate
* in between which grid rows the drop will insert the drag source item.
*
* <p>This is a dynamic skin part: it's created as needed and then destroyed.</p>
*
* <p>The DataGrid will set the dropIndicator's height to its
* preferred height bracketed by its minHeight and maxHeight values.
* Its width will be unconditionally set to the grid's visible width.</p>
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public var dropIndicator:IFactory;
//----------------------------------
// editorIndicator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render a background behind
* item renderers that are being edited.
* Item renderers may only be edited
* when the data grid and the column are both editable and the
* column sets <code>rendererIsEditable</code> to <code>true</code>.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var editorIndicator:IFactory;
//----------------------------------
// grid
//----------------------------------
[Bindable]
[SkinPart(required="false")]
/**
* A reference to the Grid control that displays row and columns.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var grid:Grid;
//----------------------------------
// hoverIndicator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to provide hover feedback.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var hoverIndicator:IFactory;
//----------------------------------
// lockedColumnsSeparator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the vertical separator between locked and unlocked columns.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 5.0
*/
public var lockedColumnsSeparator:IFactory;
//----------------------------------
// lockedRowsSeparator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the horizontal separator between locked and unlocked rows.
*/
public var lockedRowsSeparator:IFactory;
//----------------------------------
// rowBackground
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the background of each row.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var rowBackground:IFactory;
//----------------------------------
// rowSeparator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render the horizontal separator between header rows.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var rowSeparator:IFactory;
//----------------------------------
// scroller
//----------------------------------
[Bindable]
[SkinPart(required="false")]
/**
* A reference to the Scroller control in the skin class
* that adds scroll bars to the DataGrid control.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var scroller:Scroller;
//----------------------------------
// selectionIndicator
//----------------------------------
[Bindable]
[SkinPart(required="false", type="mx.core.IVisualElement")]
/**
* The IVisualElement class used to render selected rows or cells.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public var selectionIndicator:IFactory;
/**
* @private
* If the alternatingRowColors style is set AND the alternatingRowColorsBackground
* skin part has been added AND the grid skin part has been added, then set
* grid.rowBackground = alternatingRowColorsBackground here. Otherwise just
* set it to the value of the rowBackground skin part (property).
*/
private function initializeGridRowBackground():void
{
if (!grid)
return;
if ((getStyle("alternatingRowColors") as Array) && alternatingRowColorsBackground)
grid.rowBackground = alternatingRowColorsBackground;
else
grid.rowBackground = rowBackground;
}
//--------------------------------------------------------------------------
//
// Skin Part Property Internals
//
//--------------------------------------------------------------------------
/**
* @private
* A list of functions to be applied to the grid skin part at partAdded() time.
* This list is used to defer making grid selection updates per the set methods for
* the selectedIndex, selectedIndices, selectedItem, selectedItems, selectedCell
* and selectedCells properties.
*/
private const deferredGridOperations:Vector.<Function> = new Vector.<Function>();
/**
* @private
* Defines one bit for each skin part property that's covered by DataGrid. Currently
* there are only grid properties.
*/
private static const partPropertyBits:Object = {
columns: uint(1 << 0),
dataProvider: uint(1 << 1),
itemRenderer: uint(1 << 2),
requestedRowCount: uint(1 << 3),
requestedColumnCount: uint(1 << 4),
requestedMaxRowCount: uint(1 << 5),
requestedMinRowCount: uint(1 << 6),
requestedMinColumnCount: uint(1 << 7),
rowHeight: uint(1 << 8),
showDataTips: uint(1 << 9),
typicalItem: uint(1 << 10),
variableRowHeight: uint(1 << 11),
dataTipField: uint(1 << 12),
dataTipFunction: uint(1 << 13),
resizableColumns: uint(1 << 14),
lockedColumnCount: uint(1 << 15),
lockedRowCount: uint(1 << 16)
};
/**
* @private
* If the grid skin part hasn't been added, this var is an object whose properties
* temporarily record the values of DataGrid properties that just "cover" grid skin
* part properties.
*
* If the grid skin part has been added (is non-null), then this var has
* a single is a uint bitmask property called propertyBits that's used
* used to track which grid properties have been explicitly set.
*
* See getPartProperty(), setPartProperty().
*/
private var gridProperties:Object = {};
/**
* @private
* The default values of the grid skin part properties covered by DataGrid.
*/
private static const gridPropertyDefaults:Object = {
columns: null,
dataProvider: null,
itemRenderer: null,
resizableColumns: true,
requestedRowCount: int(-1),
requestedMaxRowCount: int(10),
requestedMinRowCount: int(-1),
requestedColumnCount: int(-1),
requestedMinColumnCount: int(-1),
rowHeight: NaN,
showDataTips: false,
typicalItem: null,
variableRowHeight: false,
dataTipField: null,
dataTipFunction: null,
lockedColumnCount: int(0),
lockedRowCount: int(0)
};
/**
* @private
* A utility method for looking up a skin part property that accounts for the possibility that
* the skin part is null. It's intended to be used in the definition of properties that just
* "cover" skin part properties.
*
* If part is non-null, then return part[propertyName]. Otherwise return the value
* of properties[propertyName], or defaults[propertyName] if the specified property's
* value is undefined.
*/
private static function getPartProperty(part:Object, properties:Object, propertyName:String, defaults:Object):*
{
if (part)
return part[propertyName];
const value:* = properties[propertyName];
return (value === undefined) ? defaults[propertyName] : value;
}
/**
* @private
* A utility method for setting a skin part property that accounts for the possibility that
* the skin part is null. It's intended to be used in the definition of properties that just
* "cover" skin part properties.
*
* Return true if the property's value was changed.
*
* If part is non-null, then set part[propertyName], otherwise set properties[propertyName].
*
* If part is non-null then we set the bit for this property on the properties.propertyBits, to record
* the fact that the DataGrid cover property was explicitly set.
*
* In either case we treat setting a property to its default value specially: the effect
* is as if the property was never set at all.
*/
private static function setPartProperty(part:Object, properties:Object, propertyName:String, value:*, defaults:Object):Boolean
{
if (getPartProperty(part, properties, propertyName, defaults) === value)
return false;
const defaultValue:* = defaults[propertyName];
if (part)
{
part[propertyName] = value;
if (value === defaultValue)
properties.propertyBits &= ~partPropertyBits[propertyName];
else
properties.propertyBits |= partPropertyBits[propertyName];
}
else
{
if (value === defaultValue)
delete properties[propertyName];
else
properties[propertyName] = value;
}
return true;
}
/**
* @private
* Return the specified grid property.
*/
private function getGridProperty(propertyName:String):*
{
return getPartProperty(grid, gridProperties, propertyName, gridPropertyDefaults);
}
/**
* @private
* Set the specified grid property and return true if the property actually changed.
*/
private function setGridProperty(propertyName:String, value:*):Boolean
{
return setPartProperty(grid, gridProperties, propertyName, value, gridPropertyDefaults);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* Maximum time in milliseconds between a click and a double click.
*/
mx_internal var doubleClickTime:Number = 620;
/**
* @private
* Key used to start editting a cell.
*/
mx_internal var editKey:uint = Keyboard.F2;
/**
* @private
* A cell editor is initiated by a single click on a selected cell.
* If this variable is true then also open an editor when a cell is
* double clicked, otherwise cancel the edit.
*/
mx_internal var editOnDoubleClick:Boolean = false;
/**
* @private
* Provides all the logic to start and end item
* editor sessions.
*
* Create your own editor by overriding the <code>createEditor()</code>
* method.
*/
mx_internal var editor:DataGridEditor;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function dispatchChangeEvent(type:String):void
{
if (hasEventListener(type))
dispatchEvent(new Event(type));
}
/**
* @private
*/
private function dispatchFlexEvent(type:String):void
{
if (hasEventListener(type))
dispatchEvent(new FlexEvent(type));
}
//----------------------------------
// columns (delegates to grid.columns)
//----------------------------------
[Bindable("columnsChanged")]
[Inspectable(category="General")]
/**
* @copy spark.components.Grid#columns
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get columns():IList
{
return getGridProperty("columns");
}
/**
* @private
*/
public function set columns(value:IList):void
{
if (setGridProperty("columns", value))
{
if (columnHeaderGroup)
{
columnHeaderGroup.layout.clearVirtualLayoutCache();
columnHeaderGroup.invalidateSize();
columnHeaderGroup.invalidateDisplayList();
}
dispatchChangeEvent("columnsChanged");
}
}
/**
* @private
*/
private function getColumnAt(columnIndex:int):GridColumn
{
const grid:Grid = grid;
if (!grid || !grid.columns)
return null;
const columns:IList = grid.columns;
return ((columnIndex >= 0) && (columnIndex < columns.length)) ? columns.getItemAt(columnIndex) as GridColumn : null;
}
//----------------------------------
// columnsLength
//----------------------------------
/**
* Returns the value of <code>columns.length</code> if the columns IList
* was specified, otherwise 0.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5*
*/
public function get columnsLength():int
{
const columns:IList = columns;
return (columns) ? columns.length : 0;
}
//----------------------------------
// dataProvider (delegates to grid.dataProvider)
//----------------------------------
[Bindable("dataProviderChanged")]
[Inspectable(category="Data")]
/**
* @copy spark.components.Grid#dataProvider
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get dataProvider():IList
{
return getGridProperty("dataProvider");
}
/**
* @private
*/
public function set dataProvider(value:IList):void
{
if (setGridProperty("dataProvider", value))
dispatchChangeEvent("dataProviderChanged");
}
//----------------------------------
// dataProviderLength
//----------------------------------
/**
* Returns the value of <code>dataProvider.length</code> if the dataProvider IList
* was specified, otherwise 0.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get dataProviderLength():int
{
const dataProvider:IList = dataProvider;
return (dataProvider) ? dataProvider.length : 0;
}
//----------------------------------
// dataTipField (delegates to grid.dataTipField)
//----------------------------------
[Bindable("dataTipFieldChanged")]
[Inspectable(category="Data", defaultValue="null")]
/**
* @copy spark.components.Grid#dataTipField
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get dataTipField():String
{
return getGridProperty("dataTipField");
}
/**
* @private
*/
public function set dataTipField(value:String):void
{
if (setGridProperty("dataTipField", value))
dispatchChangeEvent("dataTipFieldChanged");
}
//----------------------------------
// dataTipFunction (delegates to grid.dataTipFunction)
//----------------------------------
[Bindable("dataTipFunctionChanged")]
[Inspectable(category="Data")]
/**
* @copy spark.components.Grid#dataTipFunction
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get dataTipFunction():Function
{
return getGridProperty("dataTipFunction");
}
/**
* @private
*/
public function set dataTipFunction(value:Function):void
{
if (setGridProperty("dataTipFunction", value))
dispatchChangeEvent("dataTipFunctionChanged");
}
//----------------------------------
// doubleClickMode
//----------------------------------
[Bindable("doubleClickModeChanged")]
[Inspectable(category="General", enumeration="cell,grid,row", defaultValue="row")]
/**
* @copy spark.components.Grid#doubleClickMode
*
* @default GridDoubleClickMode.ROW
*
* @see spark.components.gridClasses.GridDoubleClickMode
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function get doubleClickMode():String
{
return grid.doubleClickMode;
}
/**
* @private
*/
public function set doubleClickMode(newValue:String):void
{
if (setGridProperty("doubleClickMode", newValue))
{
dispatchChangeEvent("doubleClickModeChanged");
}
}
//----------------------------------
// draggableColumns
//----------------------------------
private var _draggableColumns:Boolean = false;
[Inspectable(category="General", defaultValue="false")]
[Bindable("draggableColumnsChanged")]
/**
*
* @langversion 3.0
* @playerversion Flash 1
* @playerversion AIR 2.5
* @productversion Flex 5.0
*/
public function get draggableColumns():Boolean
{
return _draggableColumns;
}
/**
* @private
*/
public function set draggableColumns(value:Boolean):void
{
if (value == _draggableColumns)
return;
_draggableColumns = value;
dispatchChangeEvent("draggableColumnsChanged");
}
//----------------------------------
// editable
//----------------------------------
/**
* @private
* Storage for the editable property.
*/
private var _editable:Boolean = false;
[Inspectable(category="General", defaultValue="false")]
/**
* A flag which enables editing the data items in the DataGrid.
* If <code>true</code> and <code>selectionMode</code> is not equal to "none", clicking on
* a cell opens an item editor.
*
* <p>You can disable editing for individual columns of the DataGrid control using the
* GridColumn <code>editable</code> property.
* By default, all visible columns are editable.</p>
*
* <p>You can enable or disable editing per cell (rather than per column)
* by handling the <code>startItemEditorSession</code> event.
* In the event handler, add the necessary logic to determine
* if the cell should be editable.</p>
*
* @default false
*
* @see #selectionMode
* @see spark.components.gridClasses.GridColumn#editable
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get editable():Boolean
{
return _editable;
}
/**
* @private
*/
public function set editable(value:Boolean):void
{
_editable = value;
}
//----------------------------------
// editorActivationMouseEvent
//----------------------------------
private var _editorActivationMouseEvent:String = GridItemEditorActivationMouseEvent.SINGLE_CLICK_ON_SELECTED_CELL;
[Bindable("editorActivationMouseEventChanged")]
[Inspectable(category="General", enumeration="doubleClick,none,singleClick,singleClickOnSelectedCell", defaultValue="singleClickOnSelectedCell")]
/**
* The type of mouse event that starts an editor session. Must be one of
* values in <code>GridItemEditorMouseEvent</code>. This value
* provides a default value for each column of a DataGrid. A different
* value can be specified on a grid column to override the default.
*
* @default "singleClickOnSelectedCell"
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function get editorActivationMouseEvent():String
{
return _editorActivationMouseEvent;
}
/**
* @private
*/
public function set editorActivationMouseEvent(value:String):void
{
if (_editorActivationMouseEvent == value)
return;
_editorActivationMouseEvent = value;
dispatchChangeEvent("editorActivationMouseEventChanged");
}
//----------------------------------
// editorColumnIndex
//----------------------------------
/**
* The zero-based column index of the cell that is being edited.
* The value is -1 if no cell is being edited.
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get editorColumnIndex():int
{
if (editor)
return editor.editorColumnIndex;
return -1;
}
//----------------------------------
// editorRowIndex
//----------------------------------
/**
* The zero-based row index of the cell that is being edited.
* The value is -1 if no cell is being edited.
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get editorRowIndex():int
{
if (editor)
return editor.editorRowIndex;
return -1;
}
//----------------------------------
// enableIME
//----------------------------------
/**
* A flag that indicates whether the IME should
* be enabled when the component receives focus.
*
* If the item editor is open, it sets this property
* accordingly.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get enableIME():Boolean
{
return false;
}
//----------------------------------
// isFirstRow
//----------------------------------
/**
* @copy spark.components.Grid#isFirstRow
* If a <code>grid</code> is not assigned, will always return false;
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function get isFirstRow():Boolean
{
if (grid)
{
return grid.isFirstRow;
}
else
{
return false;
}
}
//----------------------------------
// isLastRow
//----------------------------------
/**
* @copy spark.components.Grid#isLastRow
* If a <code>grid</code> is not assigned, will always return false;
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function get isLastRow():Boolean
{
if (grid)
{
return grid.isLastRow;
}
else
{
return false;
}
}
//----------------------------------
// multiColumnSortingEnabled
//----------------------------------
private var _multiColumnSortingEnabled:Boolean = false;
[Bindable("multiColumnSortingEnabledChanged")]
[Inspectable(category="General", defaultValue="false")]
/**
* If this property is true and sorting has been enabled, then users can interactively
* specify multi-column sorts by control-clicking in column headers (Command+Click on OSX).
* Sorting is enabled for a column if the DataGrid <code>sortable</code> property is true
* and the column's <code>sortable</code> property is true. Sorting is enabled for
* all columns by default, multi-column sorting is not.
*
* @default false
*
* @see #sortableColumns
* @see spark.components.gridClasses.GridColumn#sortable
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get multiColumnSortingEnabled():Boolean
{
return _multiColumnSortingEnabled;
}
/**
* @private
*/
public function set multiColumnSortingEnabled(value:Boolean):void
{
if (_multiColumnSortingEnabled == value)
return;
_multiColumnSortingEnabled = value;
dispatchChangeEvent("multiColumnSortingEnabledChanged");
}
//----------------------------------
// gridSelection (private)
//----------------------------------
private var _gridSelection:GridSelection = null;
/**
* @private
* This object becomes the grid's gridSelection property after the grid skin part has been
* added. It should only be referenced by this class when the grid skin part is null.
*/
protected function get gridSelection():GridSelection
{
if (!_gridSelection)
_gridSelection = createGridSelection();
return _gridSelection;
}
//----------------------------------
// imeMode
//----------------------------------
/**
* @private
*/
private var _imeMode:String = null;
[Inspectable(environment="none")]
/**
* The default value for the GridColumn <code>imeMode</code> property,
* which specifies the IME (Input Method Editor) mode.
* The IME enables users to enter text in Chinese, Japanese, and Korean.
* Flex sets the specified IME mode when the control gets focus,
* and sets it back to the previous value when the control loses focus.
*
* <p>The flash.system.IMEConversionMode class defines constants for the
* valid values for this property.
* You can also specify <code>null</code> to specify no IME.</p>
*
* @see flash.system.IMEConversionMode
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get imeMode():String
{
return _imeMode;
}
/**
* @private
*/
public function set imeMode(value:String):void
{
_imeMode = value;
}
//----------------------------------
// internalFocusOwner
//----------------------------------
private static const GRID_FOCUS_OWNER:int = 0;
private static const HEADER_FOCUS_OWNER:int = 1;
private static const NO_FOCUS_OWNER:int = -1;
private var _internalFocusOwner:int = -1;
[Bindable("internalFocusOwnerChanged")]
/**
* Tracks the internal focus owner when the DataGrid has the focus: one of GRID_FOCUS_OWNER,
* HEADER_FOCUS_OWNER, or NO_FOCUS_OWNER.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
private function get internalFocusOwner():int
{
return _internalFocusOwner;
}
/**
* @private
*/
private function set internalFocusOwner(value:int):void
{
if (_internalFocusOwner == value)
return;
_internalFocusOwner = value;
if (columnHeaderGroup)
columnHeaderGroup.highlightSelectedColumn = (value == HEADER_FOCUS_OWNER);
if (grid)
grid.showCaret = (value == GRID_FOCUS_OWNER);
dispatchChangeEvent("internalFocusOwnerChanged");
}
//----------------------------------
// itemEditor
//----------------------------------
private var _itemEditor:IFactory = null;
[Bindable("itemEditorChanged")]
/**
* The default value for the GridColumn <code>itemEditor</code> property,
* which specifies the IGridItemEditor class used to create item editor instances.
*
* @default null.
*
* @see #dataField
* @see spark.components.gridClasses.IGridItemEditor
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get itemEditor():IFactory
{
return _itemEditor;
}
/**
* @private
*/
public function set itemEditor(value:IFactory):void
{
if (_itemEditor == value)
return;
_itemEditor = value;
dispatchChangeEvent("itemEditorChanged");
}
/**
* A reference to the currently active instance of the item editor,
* if it exists.
*
* <p>To access the item editor instance and the new item value when an
* item is being edited, you use the <code>itemEditorInstance</code>
* property. The <code>itemEditorInstance</code> property
* is not valid until the <code>itemEditorSessionStart</code> event is
* dispatched.</p>
*
* <p>The <code>DataGridColumn.itemEditor</code> property defines the
* class of the item editor and, therefore, the data type of the item
* editor instance.</p>
*
* <p>Do not set this property in MXML.</p>
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get itemEditorInstance():IGridItemEditor
{
if (editor)
return editor.itemEditorInstance;
return null;
}
//----------------------------------
// itemRenderer (delegates to grid.itemRenderer)
//----------------------------------
[Bindable("itemRendererChanged")]
[Inspectable(category="Data")]
/**
* @copy spark.components.Grid#itemRenderer
*
* @default DefaultGridItemRenderer
*
* @see spark.components.gridClasses.GridItemRenderer
* @see spark.skins.spark.DefaultGridItemRenderer
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get itemRenderer():IFactory
{
return getGridProperty("itemRenderer");
}
/**
* @private
*/
public function set itemRenderer(value:IFactory):void
{
if (setGridProperty("itemRenderer", value))
dispatchChangeEvent("itemRendererChanged");
}
//----------------------------------
// lockedColumnCount (delegates to grid.lockedColumnCount)
//----------------------------------
[Bindable("lockedColumnCountChanged")]
[Inspectable(category="General", defaultValue="0", minValue="0")]
/**
* @copy spark.components.Grid#lockedColumnCount
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 5.0
*/
public function get lockedColumnCount():int
{
return getGridProperty("lockedColumnCount");
}
/**
* @private
*/
public function set lockedColumnCount(value:int):void
{
if (setGridProperty("lockedColumnCount", value))
dispatchChangeEvent("lockedColumnCountChanged");
}
//----------------------------------
// lockedRowCount (delegates to grid.lockedRowCount)
//----------------------------------
[Bindable("lockedRowCountChanged")]
[Inspectable(category="General", defaultValue="0", minValue="0")]
/**
* @copy spark.components.Grid#lockedRowCount
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 5.0
*/
public function get lockedRowCount():int
{
return getGridProperty("lockedRowCount");
}
/**
* @private
*/
public function set lockedRowCount(value:int):void
{
if (setGridProperty("lockedRowCount", value))
dispatchChangeEvent("lockedRowCountChanged");
}
//----------------------------------
// preserveSelection (delegates to grid.preserveSelection)
//----------------------------------
[Inspectable(category="General", defaultValue="true")]
/**
* @copy spark.components.Grid#preserveSelection
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get preserveSelection():Boolean
{
if (grid)
return grid.preserveSelection;
else
return gridSelection.preserveSelection;
}
/**
* @private
*/
public function set preserveSelection(value:Boolean):void
{
if (grid)
grid.preserveSelection = value;
else
gridSelection.preserveSelection = value;
}
//----------------------------------
// requireSelection (delegates to grid.requireSelection)
//----------------------------------
[Inspectable(category="General", defaultValue="false")]
/**
* @copy spark.components.Grid#requireSelection
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get requireSelection():Boolean
{
if (grid)
return grid.requireSelection;
else
return gridSelection.requireSelection;
}
/**
* @private
*/
public function set requireSelection(value:Boolean):void
{
if (grid)
grid.requireSelection = value;
else
gridSelection.requireSelection = value;
}
//----------------------------------
// requestedRowCount (delegates to grid.requestedRowCount)
//----------------------------------
[Inspectable(category="General", minValue="-1")]
/**
* @copy spark.components.Grid#requestedRowCount
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get requestedRowCount():int
{
return getGridProperty("requestedRowCount");
}
/**
* @private
*/
public function set requestedRowCount(value:int):void
{
setGridProperty("requestedRowCount", value);
}
//----------------------------------
// requestedColumnCount (delegates to grid.requestedColumnCount)
//----------------------------------
[Inspectable(category="General", minValue="-1")]
/**
* @copy spark.components.Grid#requestedColumnCount
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get requestedColumnCount():int
{
return getGridProperty("requestedColumnCount");
}
/**
* @private
*/
public function set requestedColumnCount(value:int):void
{
setGridProperty("requestedColumnCount", value);
}
//----------------------------------
// requestedMaxRowCount (delegates to grid.requestedMaxRowCount)
//----------------------------------
[Inspectable(category="General", defaultValue="10", minValue="-1")]
/**
* @copy spark.components.Grid#requestedMaxRowCount
*
* @default 10
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get requestedMaxRowCount():int
{
return getGridProperty("requestedMaxRowCount");
}
/**
* @private
*/
public function set requestedMaxRowCount(value:int):void
{
setGridProperty("requestedMaxRowCount", value);
}
//----------------------------------
// requestedMinRowCount (delegates to grid.requestedMinRowCount)
//----------------------------------
[Inspectable(category="General", minValue="-1")]
/**
* @copy spark.components.Grid#requestedMinRowCount
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get requestedMinRowCount():int
{
return getGridProperty("requestedMinRowCount");
}
/**
* @private
*/
public function set requestedMinRowCount(value:int):void
{
setGridProperty("requestedMinRowCount", value);
}
//----------------------------------
// requestedMinColumnCount (delegates to grid.requestedMinColumnCount)
//----------------------------------
[Inspectable(category="General", minValue="-1")]
/**
* @copy spark.components.Grid#requestedMinColumnCount
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get requestedMinColumnCount():int
{
return getGridProperty("requestedMinColumnCount");
}
/**
* @private
*/
public function set requestedMinColumnCount(value:int):void
{
setGridProperty("requestedMinColumnCount", value);
}
//----------------------------------
// resizableColumns (delegates to grid.resizableColumns)
//----------------------------------
[Bindable("resizableColumnsChanged")]
[Inspectable(category="General", defaultValue="true")]
/**
* @copy spark.components.Grid#resizableColumns
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get resizableColumns():Boolean
{
return getGridProperty("resizableColumns");
}
/**
* @private
*/
public function set resizableColumns(value:Boolean):void
{
if (setGridProperty("resizableColumns", value))
dispatchChangeEvent("resizableColumnsChanged");
}
//----------------------------------
// rowHeight (delegates to grid.rowHeight)
//----------------------------------
[Bindable("rowHeightChanged")]
[Inspectable(category="General", minValue="0.0")]
/**
* @copy spark.components.Grid#rowHeight
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get rowHeight():Number
{
return getGridProperty("rowHeight");
}
/**
* @private
*/
public function set rowHeight(value:Number):void
{
if (setGridProperty("rowHeight", value))
dispatchChangeEvent("rowHeightChanged");
}
//----------------------------------
// selectionMode (delegates to grid.selectionMode)
//----------------------------------
[Bindable("selectionModeChanged")]
[Inspectable(category="General", enumeration="none,singleRow,multipleRows,singleCell,multipleCells", defaultValue="singleRow")]
/**
* @copy spark.components.Grid#selectionMode
*
* @default GridSelectionMode.SINGLE_ROW
*
* @see spark.components.gridClasses.GridSelectionMode
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectionMode():String
{
if (grid)
return grid.selectionMode;
else
return gridSelection.selectionMode;
}
/**
* @private
*/
public function set selectionMode(value:String):void
{
if (selectionMode == value)
return;
if (grid)
grid.selectionMode = value;
else
gridSelection.selectionMode = value;
// Show the caret if we have focus and not in grid selection mode of "none".
if (grid)
grid.showCaret = (value != GridSelectionMode.NONE) && (this == getFocus()) && (internalFocusOwner == GRID_FOCUS_OWNER);
dispatchChangeEvent("selectionModeChanged");
}
/**
* @private
*/
mx_internal function isRowSelectionMode():Boolean
{
const mode:String = selectionMode;
return mode == GridSelectionMode.SINGLE_ROW || mode == GridSelectionMode.MULTIPLE_ROWS;
}
/**
* @private
*/
mx_internal function isCellSelectionMode():Boolean
{
const mode:String = selectionMode;
return mode == GridSelectionMode.SINGLE_CELL || mode == GridSelectionMode.MULTIPLE_CELLS;
}
//----------------------------------
// showDataTips
//----------------------------------
[Bindable("showDataTipsChanged")]
[Inspectable(category="Data", defaultValue="false")]
/**
* @copy spark.components.Grid#showDataTips
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get showDataTips():Boolean
{
return getGridProperty("showDataTips");
}
/**
* @private
*/
public function set showDataTips(value:Boolean):void
{
if (setGridProperty("showDataTips", value))
dispatchChangeEvent("showDataTipsChanged");
}
//----------------------------------
// sortableColumns
//----------------------------------
private var _sortableColumns:Boolean = true;
[Bindable("sortableColumnsChanged")]
[Inspectable(category="General", defaultValue="true")]
/**
* Specifies whether the user can interactively sort columns.
* If <code>true</code>, the user can sort the data provider by the
* data field of a column by clicking on the column's header.
* If <code>true</code>, an individual column can set its
* <code>sortable</code> property to <code>false</code> to
* prevent the user from sorting by that column.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get sortableColumns():Boolean
{
return _sortableColumns;
}
/**
* @private
*/
public function set sortableColumns(value:Boolean):void
{
if (_sortableColumns == value)
return;
_sortableColumns = value;
dispatchChangeEvent("sortableColumnsChanged");
}
//----------------------------------
// typicalItem (delegates to grid.typicalItem)
//----------------------------------
[Bindable("typicalItemChanged")]
[Inspectable(category="Data")]
/**
* @copy spark.components.Grid#typicalItem
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get typicalItem():Object
{
return getGridProperty("typicalItem");
}
/**
* @private
*/
public function set typicalItem(value:Object):void
{
if (setGridProperty("typicalItem", value))
dispatchChangeEvent("typicalItemChanged");
}
/**
* @copy spark.components.Grid#invalidateTypicalItem()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4.5
*/
public function invalidateTypicalItem():void
{
if (grid)
grid.invalidateTypicalItemRenderer();
}
//----------------------------------
// variableRowHeight (delegates to grid.variableRowHeight)
//----------------------------------
[Bindable("variableRowHeightChanged")]
[Inspectable(category="General", defaultValue="false")]
/**
* @copy spark.components.Grid#variableRowHeight
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get variableRowHeight():Boolean
{
return getGridProperty("variableRowHeight");
}
/**
* @private
*/
public function set variableRowHeight(value:Boolean):void
{
if (setGridProperty("variableRowHeight", value))
dispatchChangeEvent("variableRowHeightChanged");
}
//--------------------------------------------------------------------------
//
// Drag and Drop Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// dragEnabled
//----------------------------------
/**
* @private
* Storage for the dragEnabled property.
*/
private var _dragEnabled:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether you can drag items out of
* this control and drop them on other controls.
* If <code>true</code>, dragging is enabled for the control.
* If the <code>dropEnabled</code> property is also <code>true</code>,
* you can drag items and drop them within this control
* to reorder the items.
*
* <p>Drag and drop is not supported on mobile devices where
* <code>interactionMode</code> is set to <code>touch</code>.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function get dragEnabled():Boolean
{
return _dragEnabled;
}
/**
* @private
*/
public function set dragEnabled(value:Boolean):void
{
if (value == _dragEnabled)
return;
_dragEnabled = value;
if (_dragEnabled)
{
addEventListener(DragEvent.DRAG_START, dragStartHandler, false, EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler, false, EventPriority.DEFAULT_HANDLER);
}
else
{
removeEventListener(DragEvent.DRAG_START, dragStartHandler, false);
removeEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler, false);
}
}
//----------------------------------
// dragMoveEnabled
//----------------------------------
/**
* @private
* Storage for the dragMoveEnabled property.
*/
private var _dragMoveEnabled:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether items can be moved instead
* of just copied from the control as part of a drag-and-drop
* operation.
* If <code>true</code>, and the <code>dragEnabled</code> property
* is <code>true</code>, items can be moved.
* Often the data provider cannot or should not have items removed
* from it, so a MOVE operation should not be allowed during
* drag-and-drop.
*
* <p>Drag and drop is not supported on mobile devices where
* <code>interactionMode</code> is set to <code>touch</code>.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function get dragMoveEnabled():Boolean
{
return _dragMoveEnabled;
}
/**
* @private
*/
public function set dragMoveEnabled(value:Boolean):void
{
_dragMoveEnabled = value;
}
//----------------------------------
// dropEnabled
//----------------------------------
/**
* @private
* Storage for the <code>dropEnabled</code> property.
*/
private var _dropEnabled:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether dragged items can be dropped onto the
* control.
*
* <p>If you set this property to <code>true</code>,
* the control accepts all data formats, and assumes that
* the dragged data matches the format of the data in the data provider.
* If you want to explicitly check the data format of the data
* being dragged, you must handle one or more of the drag events,
* such as <code>dragEnter</code> and <code>dragOver</code>,
* and call the DragEvent's <code>preventDefault()</code> method
* to customize the way the list class accepts dropped data.</p>
*
* <p>When you set <code>dropEnabled</code> to <code>true</code>,
* Flex automatically calls the <code>showDropFeedback()</code>
* and <code>hideDropFeedback()</code> methods to display the drop
* indicator.</p>
*
* <p>Drag and drop is not supported on mobile devices where
* <code>interactionMode</code> is set to <code>touch</code>.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function get dropEnabled():Boolean
{
return _dropEnabled;
}
/**
* @private
*/
public function set dropEnabled(value:Boolean):void
{
if (value == _dropEnabled)
return;
_dropEnabled = value;
if (_dropEnabled)
{
addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false, EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false, EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_OVER, dragOverHandler, false, EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_DROP, dragDropHandler, false, EventPriority.DEFAULT_HANDLER);
}
else
{
removeEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false);
removeEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false);
removeEventListener(DragEvent.DRAG_OVER, dragOverHandler, false);
removeEventListener(DragEvent.DRAG_DROP, dragDropHandler, false);
}
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
* This component holds the focus and accessibilityImplementation (DataGridAccImpl)
* for the DataGrid to enable accessibility when the focus shifts to DataGrid descendants,
* notably item editors.
*
* DataGridAccImpl/createAccessibilityImplementation() initializes the focusOwner's
* accessibilityImplementation, rather than the DataGrid's, so that the MSAA
* representation of descendant components will not be "obscured" by DataGridAccImpl.
*
* @see #createChildren(), #setFocus(), #isOurFocus().
*/
mx_internal var focusOwner:UIComponent;
/**
* @private
* The accessibility implementation depends on the focusOwner's bounds, as in
* DisplayObject.getBounds(), which is defined by the bounds of what has been drawn,
* not the focusOwner's width and height properties.
*/
private var focusOwnerWidth:Number = 1;
private var focusOwnerHeight:Number = 1;
/**
* @private
* Create the focusOwner - a component that holds the keyboard focus for the DataGrid
* for the sake of the accessibility implementation.
*
* This component fails to add its DataGridAccImpl accessibilityImplementation to
* the MSAA if it doesn't render, so we draw one alpha=0.0 pixel here and make it
* $visible=true.
*/
override protected function createChildren():void
{
super.createChildren();
focusOwner = new UIComponent();
const g:Graphics = focusOwner.graphics;
g.clear();
g.lineStyle(0, 0x000000, 0);
g.drawRect(0, 0, focusOwnerWidth, focusOwnerHeight);
$addChild(focusOwner);
focusOwner.tabEnabled = true;
focusOwner.tabIndex = tabIndex;
focusOwner.$visible = true;
}
/**
* @private
* Ensure that IDataGridElements are laid out after the grid skin part.
* At the moment, DataGrid has only one IDataGridElement, columnHeaderGroup.
*
* The DataGrid columnHeaderGroup's layout role is unusual, since it depends on
* the layout of the grid skin part.
*  The columnHeaderGroup's column widths, scroll position, and list of visible columns
* are defined by the grid.
*  That means that the grid needs to compute its layout first.
*
* The Flex LayoutManager orders the per-element work in each of its phases according
* to the element's nestLevel. The measure() (validateSize) phase is "bottom up"
* with respect to the nestLevel, which means that the deepest nodes in the tree with the
* highest nestLevel are measured first.
* Similarly the updateDisplayList() (validateDisplayList) phase is "top down" where the nodes
* with the lowest nestLevel are validated first. 
* To guarantee that the columnHeaderGroup skin part is laid out after the grid skin part,
* we need to ensure that its nestLevel is greater than the grid's nestLevel.
*
* As the DataGrid's skin defines the grid and columnHeaderGroup skin parts, there is no
* way for the DataGrid component to constrain their relative locations in the visual element
* hierarchy.
* However, all ILayoutManagerClients have a nestLevel property, which specifies the element's
* depth relative to its SystemManager root.
* Thus, we can overcome the limitation by initializing the columnHeaderGroup's nestLevel
* to be grid.nestLevel+1, which ensures that the columnHeaderGroup will be validated after
* the grid.
*/
override public function set nestLevel(value:int):void
{
super.nestLevel = value;
if (grid)
initializeDataGridElement(columnHeaderGroup);
}
/**
* @private
* Ensure that the DataGrid appears in the correct tab order, despite the fact that its
* focusOwner gets the focus.
*/
override public function set tabIndex(index:int):void
{
super.tabIndex = index;
if (focusOwner)
focusOwner.tabIndex = index;
}
/**
* @private
* Sync the focusOwner's copy of this value because the we can't cover this case in
* DataGridAccImpl/get_accDescription(childID). Currently get_accDescription() is never
* called with childID=0.
*/
override public function set accessibilityDescription(value:String):void
{
super.accessibilityDescription = value;
if (focusOwner)
focusOwner.accessibilityDescription = value;
}
/**
* @private
* Sync the focusOwner's copy of this value because the we can't cover this case in
* DataGridAccImpl/get_accShortcut(childID). Currently get_accShortcut() is never
* called with childID=0.
*/
override public function set accessibilityShortcut(value:String):void
{
super.accessibilityShortcut = value;
if (focusOwner)
focusOwner.accessibilityShortcut = value;
}
/**
* @private
*/
override protected function initializeAccessibility():void
{
if (DataGrid.createAccessibilityImplementation != null)
DataGrid.createAccessibilityImplementation(this);
}
/**
* @private
* Override to set proper width and height on focusOwner object that
* the DataGridAccImpl uses to compute the location of the DataGrid.
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (focusOwner && ((focusOwnerWidth != unscaledWidth) || (focusOwnerHeight != unscaledHeight)))
{
focusOwnerWidth = unscaledWidth;
focusOwnerHeight = unscaledHeight;
const g:Graphics = focusOwner.graphics;
g.clear();
g.lineStyle(0, 0x000000, 0);
g.drawRect(0, 0, focusOwnerWidth, focusOwnerHeight);
}
}
/**
* @private
*/
override public function setFocus():void
{
if (grid)
focusOwner.setFocus();
}
/**
* @private
*/
override protected function isOurFocus(target:DisplayObject):Boolean
{
return (target == focusOwner) || super.isOurFocus(target);
}
/**
* @private
*/
override public function styleChanged(styleName:String):void
{
super.styleChanged(styleName);
const allStyles:Boolean = (styleName == null || styleName == "styleName");
if (grid)
{
if (allStyles || styleManager.isSizeInvalidatingStyle(styleName))
{
if (grid)
{
grid.invalidateSize();
grid.clearGridLayoutCache(true);
}
if (columnHeaderGroup)
{
columnHeaderGroup.layout.clearVirtualLayoutCache();
columnHeaderGroup.invalidateSize();
}
}
if (allStyles || (styleName == "alternatingRowColors"))
{
initializeGridRowBackground();
if (grid && grid.layout)
grid.layout.clearVirtualLayoutCache();
}
if (grid)
grid.invalidateDisplayList();
if (columnHeaderGroup)
columnHeaderGroup.invalidateDisplayList();
}
if (scroller)
{
// The scrollPolicy styles are initialized in framework/defaults.css
// so there's no way to determine if been explicitly set here. To avoid
// preempting explicitly set scroller scrollPolicy styles with the default
// "auto" value, we only forward the style if it's not "auto".
const vsp:String = getStyle("verticalScrollPolicy");
if (styleName == "verticalScrollPolicy")
scroller.setStyle("verticalScrollPolicy", vsp);
else if (allStyles && vsp && (vsp !== ScrollPolicy.AUTO))
scroller.setStyle("verticalScrollPolicy", vsp);
const hsp:String = getStyle("horizontalScrollPolicy");
if (styleName == "horizontalScrollPolicy")
scroller.setStyle("horizontalScrollPolicy", hsp);
else if (allStyles && hsp && (hsp !== ScrollPolicy.AUTO))
scroller.setStyle("horizontalScrollPolicy", hsp);
}
}
/**
* @private
* Used to prevent keyboard events that have been redispatched to the Scroller from being
* redispatched a second time when they bubble back up.
*/
private var scrollerEvent:KeyboardEvent = null;
/**
* @private
* Build in basic keyboard navigation support in Grid. The focus is on
* the DataGrid which means the Scroller doesn't see the Keyboard events
* unless the event is dispatched to it.
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
if (!grid || event.isDefaultPrevented())
return;
// We've seen this event already and dispatched it to the Scroller
if (event == scrollerEvent)
{
scrollerEvent = null;
event.preventDefault();
return;
}
// If the key wasn't targeted to us, then ignore it.
if (!isOurFocus(DisplayObject(event.target)))
return;
// Ctrl-A only comes thru on Mac. On Windows it is a SELECT_ALL
// event.
if (event.keyCode == Keyboard.A && event.ctrlKey)
{
selectAllFromKeyboard();
event.preventDefault();
return;
}
// Row selection requires valid row caret, cell selection
// requires both a valid row and a valid column caret.
if (selectionMode == GridSelectionMode.NONE ||
grid.caretRowIndex < 0 ||
grid.caretRowIndex >= dataProviderLength ||
(isCellSelectionMode() &&
(grid.caretColumnIndex < 0 || grid.caretColumnIndex >= columnsLength)))
{
// We're not going to handle this event, so give the scroller a chance.
if (scroller && (scrollerEvent != event))
{
scrollerEvent = event.clone() as KeyboardEvent;
scroller.dispatchEvent(scrollerEvent);
}
return;
}
switch (internalFocusOwner)
{
case GRID_FOCUS_OWNER: handleGridKeyEvent(event); break;
case HEADER_FOCUS_OWNER: handleHeaderKeyEvent(event); break;
}
}
/**
* @private
* Handle KeyboardEvents when the internalFocusOwner is the grid.
*/
private function handleGridKeyEvent(event:KeyboardEvent):void
{
var op:String;
if (event.keyCode == Keyboard.SPACE)
{
if (event.ctrlKey)
{
// Updates the selection. The caret remains the same and the
// anchor is updated.
if (toggleSelection(grid.caretRowIndex, grid.caretColumnIndex))
{
grid.anchorRowIndex = grid.caretRowIndex;
grid.anchorColumnIndex = grid.caretColumnIndex;
event.preventDefault();
}
}
else if (event.shiftKey)
{
// Extend the selection. The caret remains the same.
if (extendSelection(grid.caretRowIndex, grid.caretColumnIndex))
event.preventDefault();
}
else
{
if (grid.caretRowIndex != -1)
{
if (isRowSelectionMode())
{
op = selectionMode == GridSelectionMode.SINGLE_ROW ?
GridSelectionEventKind.SET_ROW :
GridSelectionEventKind.ADD_ROW;
// Add the row and leave the caret position unchanged.
if (!commitInteractiveSelection(
op, grid.caretRowIndex, grid.caretColumnIndex))
{
return;
}
event.preventDefault();
}
else if (isCellSelectionMode() && grid.caretColumnIndex != -1)
{
op = selectionMode == GridSelectionMode.SINGLE_CELL ?
GridSelectionEventKind.SET_CELL :
GridSelectionEventKind.ADD_CELL;
// Add the cell and leave the caret position unchanged.
if (!commitInteractiveSelection(
op, grid.caretRowIndex, grid.caretColumnIndex))
{
return;
}
event.preventDefault();
}
}
}
return;
}
// Was some other navigation key hit?
adjustSelectionUponNavigation(event);
}
/**
* @private
* Handle KeyboardEvents when the internalFocusOwner is the header.
*/
private function handleHeaderKeyEvent(event:KeyboardEvent):void
{
if (!columnHeaderGroup)
return;
const selectedIndex:int = columnHeaderGroup.selectedColumnIndex;
if (selectedIndex == -1)
return;
if ((event.keyCode == Keyboard.SPACE) || (event.keyCode == Keyboard.ENTER))
{
const column:GridColumn = getColumnAt(selectedIndex);
const isMultiColumnSort:Boolean = event.ctrlKey;
interactiveSortByColumns(column, isMultiColumnSort);
if (!isMultiColumnSort && grid)
internalFocusOwner = GRID_FOCUS_OWNER;
return;
}
if (!NavigationUnit.isNavigationUnit(event.keyCode))
return;
// swap Keyboard.LEFT and Keyboard.RIGHT for RTL layout
const navigationUnit:uint = mapKeycodeForLayoutDirection(event);
const firstVisibleColumnIndex:int = grid.getNextVisibleColumnIndex(-1);
const lastVisibleColumnIndex:int = grid.getPreviousVisibleColumnIndex(columns.length);
var newSelectedIndex:int = selectedIndex;
switch (navigationUnit)
{
case NavigationUnit.HOME:
{
newSelectedIndex = firstVisibleColumnIndex;
break;
}
case NavigationUnit.END:
{
newSelectedIndex = lastVisibleColumnIndex;
break;
}
case NavigationUnit.LEFT:
{
if (event.ctrlKey && !event.shiftKey)
{
moveColumnPosition(selectedIndex, -1);
}
else if (event.ctrlKey && event.shiftKey)
{
changeColumnWidth(selectedIndex, -3);
}
else
{
if (selectedIndex <= firstVisibleColumnIndex)
newSelectedIndex = firstVisibleColumnIndex;
else
newSelectedIndex = grid.getPreviousVisibleColumnIndex(selectedIndex);
}
break;
}
case NavigationUnit.RIGHT:
{
if (event.ctrlKey && !event.shiftKey)
{
moveColumnPosition(selectedIndex, +1);
}
else if (event.ctrlKey && event.shiftKey)
{
changeColumnWidth(selectedIndex, +3);
}
else
{
if (selectedIndex >= lastVisibleColumnIndex)
newSelectedIndex = lastVisibleColumnIndex;
else
newSelectedIndex = grid.getNextVisibleColumnIndex(selectedIndex);
}
break;
}
}
if (newSelectedIndex != selectedIndex)
{
columnHeaderGroup.selectedColumnIndex = newSelectedIndex;
ensureCellIsVisible(-1, newSelectedIndex);
}
}
/**
* @private
* Change the specified column's position by delta, where delta is either +1 or -1.
*/
private function moveColumnPosition(columnIndex:int, delta:int):void
{
const column:GridColumn = getColumnAt(columnIndex);
if (!column)
return;
var newColumnIndex:int = -1;
if (delta == +1)
newColumnIndex = grid.getNextVisibleColumnIndex(columnIndex);
else if (delta == -1)
newColumnIndex = grid.getPreviousVisibleColumnIndex(columnIndex);
if (newColumnIndex != -1)
{
columns.removeItemAt(columnIndex);
columns.addItemAt(column, newColumnIndex);
columnHeaderGroup.selectedColumnIndex = newColumnIndex;
grid.ensureCellIsVisible(-1, newColumnIndex);
}
}
/**
* @private
* Change the specified column's width by delta.
*/
private function changeColumnWidth(columnIndex:int, delta:Number):void
{
const column:GridColumn = getColumnAt(columnIndex);
if (!column)
return;
var columnWidth:Number = column.width;
if (isNaN(columnWidth))
columnWidth = grid.getColumnWidth(columnIndex);
columnWidth = Math.max(1, columnWidth + delta);
if (!isNaN(column.maxWidth))
columnWidth = Math.min(columnWidth, column.maxWidth);
if (!isNaN(column.minWidth))
columnWidth = Math.max(columnWidth, column.minWidth);
column.width = columnWidth;
}
/**
* @private
* If the focusManager is trying to tab forwards from the DataGrid and the internalFocusOwner is
* currently the grid, then switch the internal focus to the header and cancel the focusManager's
* event. Similarly, if the focusManager is trying to tab backwards (shift-tab) and the internal
* focus is currently on the header, then set the internal focus to the grid and cancel the event.
*/
protected function keyFocusChangeHandler(event:FocusEvent):void
{
if (event.isDefaultPrevented())
return;
// In an edit session. Don't let the TAB switch the internal focus.
if (editor && editor.editorRowIndex != -1 && editor.editorColumnIndex != -1)
return;
if ((internalFocusOwner == GRID_FOCUS_OWNER) && !event.shiftKey && columnHeaderGroup)
{
internalFocusOwner = HEADER_FOCUS_OWNER;
event.preventDefault();
}
else if ((internalFocusOwner == HEADER_FOCUS_OWNER) && event.shiftKey && grid)
{
internalFocusOwner = GRID_FOCUS_OWNER;
event.preventDefault();
}
else
{
internalFocusOwner = (internalFocusOwner == GRID_FOCUS_OWNER) ? HEADER_FOCUS_OWNER : GRID_FOCUS_OWNER;
// If there's another focusable component, we will lose the focus here
}
}
/**
* @private
* New event in FP10 dispatched when the user activates the platform
* specific accelerator key combination for a select all operation.
* On Windows this is ctrl-A and on Mac this is cmd-A.
*/
protected function selectAllHandler(event:Event):void
{
// If the select all was not targeted to the DataGrid, then ignore it.
if (!grid || event.isDefaultPrevented() ||
!isOurFocus(DisplayObject(event.target)))
{
return;
}
selectAllFromKeyboard();
}
/**
* @private
*/
private function selectAllFromKeyboard():void
{
// If there is a caret, leave it in its current position.
if (selectionMode == GridSelectionMode.MULTIPLE_CELLS ||
selectionMode == GridSelectionMode.MULTIPLE_ROWS)
{
if (commitInteractiveSelection(
GridSelectionEventKind.SELECT_ALL,
0, 0, dataProvider.length, columns.length))
{
grid.anchorRowIndex = 0;
grid.anchorColumnIndex = 0;
}
}
}
/**
* @private
* Call partAdded() for IFactory type skin parts. By default, partAdded() is not
* called for IFactory type skin part variables, because they're assumed to be
* "dynamic" skin parts, to be created with createDynamicPartInstance(). That's
* not the case with the IFactory valued parts listed in factorySkinPartNames.
*/
override protected function findSkinParts():void
{
super.findSkinParts();
for each (var partName:String in factorySkinPartNames)
{
if ((partName in skin) && skin[partName])
partAdded(partName, skin[partName]);
}
}
/**
* @private
*/
private function initializeDataGridElement(elt:IDataGridElement):void
{
if (!elt)
return;
elt.dataGrid = this;
if (elt.nestLevel <= grid.nestLevel)
{
elt.validateNow();
elt.nestLevel = grid.nestLevel + 1;
}
}
/**
* @private
*/
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == grid)
{
// Basic Initialization
gridSelection.grid = grid;
grid.gridSelection = gridSelection;
grid.dataGrid = this;
// Grid cover Properties
const modifiedGridProperties:Object = gridProperties; // explicitly set properties
gridProperties = {propertyBits:0};
for (var propertyName:String in modifiedGridProperties)
{
if (propertyName == "propertyBits")
continue;
setGridProperty(propertyName, modifiedGridProperties[propertyName]);
}
// IFactory valued skin parts => Grid visual element properties
initializeGridRowBackground(); // sets grid.rowBackground if alternatingRowColors set
grid.columnSeparator = columnSeparator;
grid.rowSeparator = rowSeparator;
grid.hoverIndicator = hoverIndicator;
grid.caretIndicator = caretIndicator;
grid.selectionIndicator = selectionIndicator;
grid.lockedColumnsSeparator = lockedColumnsSeparator;
grid.lockedRowsSeparator = lockedRowsSeparator;
// Event Handlers
grid.addEventListener(GridEvent.GRID_MOUSE_DOWN, grid_mouseDownHandler, false, EventPriority.DEFAULT_HANDLER);
grid.addEventListener(GridEvent.GRID_MOUSE_UP, grid_mouseUpHandler, false, EventPriority.DEFAULT_HANDLER);
grid.addEventListener(GridEvent.GRID_ROLL_OVER, grid_rollOverHandler, false, EventPriority.DEFAULT_HANDLER);
grid.addEventListener(GridEvent.GRID_ROLL_OUT, grid_rollOutHandler, false, EventPriority.DEFAULT_HANDLER);
grid.addEventListener(GridCaretEvent.CARET_CHANGE, grid_caretChangeHandler);
grid.addEventListener(FlexEvent.VALUE_COMMIT, grid_valueCommitHandler);
grid.addEventListener("invalidateSize", grid_invalidateSizeHandler);
grid.addEventListener("invalidateDisplayList", grid_invalidateDisplayListHandler);
grid.addEventListener("gridViewsChanged", grid_gridViewsChangedHandler);
// Deferred operations (grid selection updates)
for each (var deferredGridOperation:Function in deferredGridOperations)
deferredGridOperation(grid);
deferredGridOperations.length = 0;
// IDataGridElements: columnHeaderGroup...
initializeDataGridElement(columnHeaderGroup);
// Create the data grid editor
editor = createEditor();
editor.initialize();
}
if (instance == alternatingRowColorsBackground)
initializeGridRowBackground();
if (grid)
{
if (instance == columnSeparator)
grid.columnSeparator = columnSeparator;
if (instance == rowSeparator)
grid.rowSeparator = rowSeparator;
if (instance == lockedColumnsSeparator)
grid.lockedColumnsSeparator = lockedColumnsSeparator;
if (instance == lockedRowsSeparator)
grid.lockedRowsSeparator = lockedRowsSeparator;
if (instance == hoverIndicator)
grid.hoverIndicator = hoverIndicator;
if (instance == caretIndicator)
{
grid.caretIndicator = caretIndicator;
// Add a focus listener so we can turn the caret on and off
// when we get and lose focus.
addEventListener(FocusEvent.FOCUS_IN, dataGrid_focusHandler);
addEventListener(FocusEvent.FOCUS_OUT, dataGrid_focusHandler);
if (grid)
grid.showCaret = false;
}
if (instance == rowBackground)
grid.rowBackground = rowBackground;
if (instance == selectionIndicator)
grid.selectionIndicator = selectionIndicator;
}
if (instance == columnHeaderGroup)
{
if (grid)
initializeDataGridElement(columnHeaderGroup);
columnHeaderGroup.addEventListener(GridEvent.GRID_CLICK, columnHeaderGroup_clickHandler);
columnHeaderGroup.addEventListener(GridEvent.GRID_ROLL_OVER, columnHeaderGroup_rollOverHandler);
columnHeaderGroup.addEventListener(GridEvent.GRID_ROLL_OUT, columnHeaderGroup_rollOutHandler);
columnHeaderGroup.addEventListener(GridEvent.SEPARATOR_ROLL_OVER, separator_rollOverHandler);
columnHeaderGroup.addEventListener(GridEvent.SEPARATOR_ROLL_OUT, separator_rollOutHandler);
columnHeaderGroup.addEventListener(GridEvent.SEPARATOR_MOUSE_DOWN, separator_mouseDownHandler);
columnHeaderGroup.addEventListener(GridEvent.SEPARATOR_MOUSE_DRAG, separator_mouseDragHandler);
columnHeaderGroup.addEventListener(GridEvent.SEPARATOR_MOUSE_UP, separator_mouseUpHandler);
}
if (instance == scroller)
{
// The scrollPolicy styles are initialized in framework/defaults.css
// so there's no way to determine if been explicitly set here. To avoid
// preempting explicitly set scroller scrollPolicy styles with the default
// "auto" value, we only forward the style if it's not "auto".
const vsp:String = getStyle("verticalScrollPolicy");
if (vsp && (vsp !== ScrollPolicy.AUTO))
scroller.setStyle("verticalScrollPolicy", vsp);
const hsp:String = getStyle("horizontalScrollPolicy");
if (hsp && (hsp !== ScrollPolicy.AUTO))
scroller.setStyle("horizontalScrollPolicy", hsp);
}
}
/**
* @private
*/
override protected function partRemoved(partName:String, instance:Object):void
{
super.partRemoved(partName, instance);
if (instance == grid)
{
// Basic Initialization
gridSelection.grid = null;
grid.gridSelection = null;
grid.dataGrid = null;
// Event Handlers
grid.removeEventListener("gridViewsChanged", grid_gridViewsChangedHandler);
grid.removeEventListener("invalidateSize", grid_invalidateSizeHandler);
grid.removeEventListener("invalidateDisplayList", grid_invalidateDisplayListHandler);
grid.removeEventListener(GridEvent.GRID_MOUSE_DOWN, grid_mouseDownHandler);
grid.removeEventListener(GridEvent.GRID_MOUSE_UP, grid_mouseUpHandler);
grid.removeEventListener(GridEvent.GRID_ROLL_OVER, grid_rollOverHandler);
grid.removeEventListener(GridEvent.GRID_ROLL_OUT, grid_rollOutHandler);
grid.removeEventListener(GridCaretEvent.CARET_CHANGE, grid_caretChangeHandler);
grid.removeEventListener(FlexEvent.VALUE_COMMIT, grid_valueCommitHandler);
// Cover Properties
const gridPropertyBits:uint = gridProperties.propertyBits;
gridProperties = {};
for (var propertyName:String in gridPropertyDefaults)
{
var propertyBit:uint = partPropertyBits[propertyName];
if ((propertyBit & gridPropertyBits) == propertyBit)
gridProperties[propertyName] = getGridProperty(propertyName);
}
// Visual Elements
grid.rowBackground = null;
grid.columnSeparator = null;
grid.rowSeparator = null;
grid.hoverIndicator = null;
grid.caretIndicator = null;
grid.selectionIndicator = null;
// IDataGridElements: grid, columnHeaderGroup
if (columnHeaderGroup)
columnHeaderGroup.dataGrid = null;
// Data grid editor
if (editor)
{
editor.uninitialize();
editor = null;
}
if (internalFocusOwner == GRID_FOCUS_OWNER)
internalFocusOwner = NO_FOCUS_OWNER;
}
if (grid)
{
if (instance == columnSeparator)
grid.columnSeparator = null;
if (instance == rowSeparator)
grid.rowSeparator = null;
if (instance == lockedColumnsSeparator)
grid.lockedColumnsSeparator = null;
if (instance == lockedRowsSeparator)
grid.lockedRowsSeparator = null;
if (instance == hoverIndicator)
grid.hoverIndicator = null;
if (instance == caretIndicator)
{
grid.caretIndicator = null;
removeEventListener(FocusEvent.FOCUS_IN, dataGrid_focusHandler);
removeEventListener(FocusEvent.FOCUS_OUT, dataGrid_focusHandler);
}
if (instance == selectionIndicator)
grid.selectionIndicator = null;
if (instance == rowBackground)
grid.rowBackground = null;
}
if (instance == columnHeaderGroup)
{
columnHeaderGroup.dataGrid = null;
columnHeaderGroup.removeEventListener(GridEvent.GRID_CLICK, columnHeaderGroup_clickHandler);
columnHeaderGroup.removeEventListener(GridEvent.GRID_ROLL_OVER, columnHeaderGroup_rollOverHandler);
columnHeaderGroup.removeEventListener(GridEvent.GRID_ROLL_OUT, columnHeaderGroup_rollOutHandler);
columnHeaderGroup.removeEventListener(GridEvent.SEPARATOR_ROLL_OVER, separator_rollOverHandler);
columnHeaderGroup.removeEventListener(GridEvent.SEPARATOR_ROLL_OUT, separator_rollOutHandler);
columnHeaderGroup.removeEventListener(GridEvent.SEPARATOR_MOUSE_DOWN, separator_mouseDownHandler);
columnHeaderGroup.removeEventListener(GridEvent.SEPARATOR_MOUSE_DRAG, separator_mouseDragHandler);
columnHeaderGroup.removeEventListener(GridEvent.SEPARATOR_MOUSE_UP, separator_mouseUpHandler);
if (internalFocusOwner == HEADER_FOCUS_OWNER)
internalFocusOwner = NO_FOCUS_OWNER;
}
}
//----------------------------------
// selectedCell
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
/**
* @copy spark.components.Grid#selectedCell
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedCell():CellPosition
{
if (grid)
return grid.selectedCell;
return selectedCells.length ? selectedCells[0] : null;
}
/**
* @private
*/
public function set selectedCell(value:CellPosition):void
{
if (grid)
grid.selectedCell = value;
else
{
const valueCopy:CellPosition = (value) ? new CellPosition(value.rowIndex, value.columnIndex) : null;
var f:Function = function(g:Grid):void
{
g.selectedCell = valueCopy;
}
deferredGridOperations.push(f);
}
}
//----------------------------------
// selectedCells
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
/**
* @copy spark.components.Grid#selectedCells
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedCells():Vector.<CellPosition>
{
return grid ? grid.selectedCells : gridSelection.allCells();
}
/**
* @private
*/
public function set selectedCells(value:Vector.<CellPosition>):void
{
if (grid)
grid.selectedCells = value;
else
{
const valueCopy:Vector.<CellPosition> = (value) ? value.concat() : null;
var f:Function = function(g:Grid):void
{
g.selectedCells = valueCopy;
}
deferredGridOperations.push(f);
}
}
//----------------------------------
// selectedIndex
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="-1")]
/**
* @copy spark.components.Grid#selectedIndex
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedIndex():int
{
if (grid)
return grid.selectedIndex;
return (selectedIndices.length > 0) ? selectedIndices[0] : -1;
}
/**
* @private
*/
public function set selectedIndex(value:int):void
{
if (grid)
grid.selectedIndex = value;
else
{
var f:Function = function(g:Grid):void
{
g.selectedIndex = value;
};
deferredGridOperations.push(f);
}
}
//----------------------------------
// selectedIndices
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General")]
/**
* @copy spark.components.Grid#selectedIndices
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedIndices():Vector.<int>
{
return grid ? grid.selectedIndices : gridSelection.allRows();
}
/**
* @private
*/
public function set selectedIndices(value:Vector.<int>):void
{
if (grid)
grid.selectedIndices = value;
else
{
const valueCopy:Vector.<int> = (value) ? value.concat() : null;
var f:Function = function(g:Grid):void
{
g.selectedIndices = valueCopy;
}
deferredGridOperations.push(f);
}
}
//----------------------------------
// selectedItem
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="null")]
/**
* @copy spark.components.Grid#selectedItem
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedItem():Object
{
if (grid)
return grid.selectedItem;
return (dataProvider && (selectedIndex > 0)) ?
dataProvider.getItemAt(selectedIndex) : undefined;
}
/**
* @private
*/
public function set selectedItem(value:Object):void
{
if (grid)
grid.selectedItem = value;
else
{
var f:Function = function(g:Grid):void
{
g.selectedItem = value;
}
deferredGridOperations.push(f);
}
}
//----------------------------------
// selectedItems
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General")]
/**
* @copy spark.components.Grid#selectedItems
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedItems():Vector.<Object>
{
if (grid)
return grid.selectedItems;
var items:Vector.<Object> = new Vector.<Object>();
for (var i:int = 0; i < selectedIndices.length; i++)
items.push(selectedIndices[i]);
return items;
}
/**
* @private
*/
public function set selectedItems(value:Vector.<Object>):void
{
if (grid)
grid.selectedItems = value;
else
{
const valueCopy:Vector.<Object> = value.concat();
var f:Function = function(g:Grid):void
{
g.selectedItems = valueCopy;
}
deferredGridOperations.push(f);
}
}
//----------------------------------
// selectionLength
//----------------------------------
[Bindable("selectionChange")]
[Bindable("valueCommit")]
/**
* @copy spark.components.Grid#selectionLength
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectionLength():int
{
return grid ? grid.selectionLength : gridSelection.selectionLength;
}
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
/**
* @copy spark.components.Grid#findRowIndex()
*
* @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
{
if (grid)
{
return grid.findRowIndex(field, value, startingIndex, patternType);
}
else
{
return -1;
}
}
/**
* @copy spark.components.Grid#findRowIndices()
*
* @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
{
if (grid)
{
return grid.findRowIndices(field, values, patternType);
}
else
{
return [];
}
}
/**
* @copy spark.components.Grid#invalidateCell()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function invalidateCell(rowIndex:int, columnIndex:int):void
{
if (grid)
grid.invalidateCell(rowIndex, columnIndex);
}
/**
* @copy spark.components.Grid#moveIndexFindRow()
*
* @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
{
if (grid)
{
return grid.moveIndexFindRow(field, value, startingIndex, patternType);
}
else
{
return false;
}
}
/**
* @copy spark.components.Grid#moveIndexFirstRow()
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function moveIndexFirstRow():void
{
if (grid)
{
grid.moveIndexFirstRow();
}
}
/**
* @copy spark.components.Grid#moveIndexLastRow()
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function moveIndexLastRow():void
{
if (grid)
{
grid.moveIndexLastRow();
}
}
/**
* @copy spark.components.Grid#moveIndexNextRow()
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function moveIndexNextRow():void
{
if (grid)
{
grid.moveIndexNextRow();
}
}
/**
* @copy spark.components.Grid#moveIndexPreviousRow()
*
* @langversion 3.0
* @playerversion Flash 11.1
* @playerversion AIR 3.4
* @productversion Flex 4.10
*/
public function moveIndexPreviousRow():void
{
if (grid)
{
grid.moveIndexPreviousRow();
}
}
/**
* @copy spark.components.Grid#selectAll()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectAll():Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.selectAll();
}
else
{
selectionChanged = gridSelection.selectAll();
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#clearSelection()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function clearSelection():Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.clearSelection();
}
else
{
selectionChanged = gridSelection.removeAll();
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
//----------------------------------
// selection for rows
//----------------------------------
/**
* @copy spark.components.Grid#selectionContainsIndex()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectionContainsIndex(rowIndex:int):Boolean
{
if (grid)
return grid.selectionContainsIndex(rowIndex);
else
return gridSelection.containsRow(rowIndex);
}
/**
* @copy spark.components.Grid#selectionContainsIndices()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectionContainsIndices(rowIndices:Vector.<int>):Boolean
{
if (grid)
return grid.selectionContainsIndices(rowIndices);
else
return gridSelection.containsRows(rowIndices);
}
/**
* @copy spark.components.Grid#setSelectedIndex()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function setSelectedIndex(rowIndex:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.setSelectedIndex(rowIndex);
}
else
{
selectionChanged = gridSelection.setRow(rowIndex);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#addSelectedIndex()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function addSelectedIndex(rowIndex:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.addSelectedIndex(rowIndex);
}
else
{
selectionChanged = gridSelection.addRow(rowIndex);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#removeSelectedIndex()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function removeSelectedIndex(rowIndex:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.removeSelectedIndex(rowIndex);
}
else
{
selectionChanged = gridSelection.removeRow(rowIndex);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#selectIndices()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectIndices(rowIndex:int, rowCount:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.selectIndices(rowIndex, rowCount);
}
else
{
selectionChanged = gridSelection.setRows(rowIndex, rowCount);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
//----------------------------------
// selection for cells
//----------------------------------
/**
* @copy spark.components.Grid#selectionContainsCell()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectionContainsCell(rowIndex:int, columnIndex:int):Boolean
{
if (grid)
return grid.selectionContainsCell(rowIndex, columnIndex);
else
return gridSelection.containsCell(rowIndex, columnIndex);
}
/**
* @copy spark.components.Grid#selectionContainsCellRegion()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectionContainsCellRegion(rowIndex:int, columnIndex:int,
rowCount:int, columnCount:int):Boolean
{
if (grid)
{
return grid.selectionContainsCellRegion(
rowIndex, columnIndex, rowCount, columnCount);
}
else
{
return gridSelection.containsCellRegion(
rowIndex, columnIndex, rowCount, columnCount);
}
}
/**
* @copy spark.components.Grid#setSelectedCell()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function setSelectedCell(rowIndex:int, columnIndex:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.setSelectedCell(rowIndex, columnIndex);
}
else
{
selectionChanged = gridSelection.setCell(rowIndex, columnIndex);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#addSelectedCell()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function addSelectedCell(rowIndex:int, columnIndex:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.addSelectedCell(rowIndex, columnIndex);
}
else
{
selectionChanged = gridSelection.addCell(rowIndex, columnIndex);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#removeSelectedCell()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function removeSelectedCell(rowIndex:int, columnIndex:int):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.removeSelectedCell(rowIndex, columnIndex);
}
else
{
selectionChanged = gridSelection.removeCell(rowIndex, columnIndex);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* @copy spark.components.Grid#selectCellRegion()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function selectCellRegion(rowIndex:int, columnIndex:int,
rowCount:uint, columnCount:uint):Boolean
{
var selectionChanged:Boolean;
if (grid)
{
selectionChanged = grid.selectCellRegion(
rowIndex, columnIndex, rowCount, columnCount);
}
else
{
selectionChanged = gridSelection.setCellRegion(
rowIndex, columnIndex, rowCount, columnCount);
if (selectionChanged)
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
}
return selectionChanged;
}
/**
* In response to user input (mouse or keyboard) which changes the
* selection, this method dispatches the <code>selectionChanging</code> event.
* If the event is not canceled, it then commits the selection change, and then
* dispatches the <code>selectionChange</code> event.
* The caret location is not updated.
* To detect if the caret has changed, use the <code>caretChanged</code> event.
*
* @param selectionEventKind A constant defined by the GridSelectionEventKind class
* that specifies the selection that is being committed. If not null, this is used to
* generate the <code>selectionChanging</code> event.
*
* @param rowIndex If <code>selectionEventKind</code> is for a row or a
* cell, the 0-based <code>rowIndex</code> of the selection in the
* data provider.
* If <code>selectionEventKind</code> is
* for multiple cells, the 0-based <code>rowIndex</code> of the origin of the
* cell region. The default is -1 to indicate this
* parameter is not being used.
*
* @param columnIndex If <code>selectionEventKind</code> is for a single row or
* a single cell, the 0-based <code>columnIndex</code> of the selection.
* If <code>selectionEventKind</code> is for multiple
* cells, the 0-based <code>columnIndex</code> of the origin of the
* cell region. The default is -1 to indicate this
* parameter is not being used.
*
* @param rowCount If <code>selectionEventKind</code> is for a cell region,
* the number of rows in the cell region. The default is -1 to indicate
* this parameter is not being used.
*
* @param columnCount If <code>selectionEventKind</code> is for a cell region,
* the number of columns in the cell region. The default is -1 to
* indicate this parameter is not being used.
*
* @return <code>true</code> if the selection was committed or did not change, or
* <code>false</code> if the selection was canceled or could not be committed due to
* an error, such as index out of range or the <code>selectionEventKind</code> is not
* compatible with the <code>selectionMode</code>.
*
* @see spark.events.GridSelectionEvent#SELECTION_CHANGE
* @see spark.events.GridSelectionEvent#SELECTION_CHANGING
* @see spark.events.GridSelectionEventKind
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function commitInteractiveSelection(
selectionEventKind:String,
rowIndex:int,
columnIndex:int,
rowCount:int = 1,
columnCount:int = 1):Boolean
{
if (!grid)
return false;
// Assumes selectionEventKind is valid for given selectionMode. Assumes
// indices are within range.
var selectionChange:CellRegion =
new CellRegion(rowIndex, columnIndex, rowCount, columnCount);
// Step 1: determine if the selection will change if the operation.
// is committed.
if (!doesChangeCurrentSelection(selectionEventKind, selectionChange))
return true;
// Step 2: dispatch the "changing" event. If preventDefault() is called
// on this event, the selection change will be cancelled.
if (hasEventListener(GridSelectionEvent.SELECTION_CHANGING))
{
const changingEvent:GridSelectionEvent =
new GridSelectionEvent(GridSelectionEvent.SELECTION_CHANGING,
false, true,
selectionEventKind, selectionChange);
// The event was cancelled so don't change the selection.
if (!dispatchEvent(changingEvent))
return false;
}
// Step 3: commit the selection change. Call the gridSelection
// methods directly so that the caret position is not altered and
// a VALUE_COMMIT event is not dispatched.
var changed:Boolean;
switch (selectionEventKind)
{
case GridSelectionEventKind.SET_ROW:
{
changed = grid.gridSelection.setRow(rowIndex);
break;
}
case GridSelectionEventKind.ADD_ROW:
{
changed = grid.gridSelection.addRow(rowIndex);
break;
}
case GridSelectionEventKind.REMOVE_ROW:
{
changed = grid.gridSelection.removeRow(rowIndex);
break;
}
case GridSelectionEventKind.SET_ROWS:
{
changed = grid.gridSelection.setRows(rowIndex, rowCount);
break;
}
case GridSelectionEventKind.SET_CELL:
{
changed = grid.gridSelection.setCell(rowIndex, columnIndex);
break;
}
case GridSelectionEventKind.ADD_CELL:
{
changed = grid.gridSelection.addCell(rowIndex, columnIndex);
break;
}
case GridSelectionEventKind.REMOVE_CELL:
{
changed = grid.gridSelection.removeCell(rowIndex, columnIndex);
break;
}
case GridSelectionEventKind.SET_CELL_REGION:
{
changed = grid.gridSelection.setCellRegion(
rowIndex, columnIndex,
rowCount, columnCount);
break;
}
case GridSelectionEventKind.SELECT_ALL:
{
changed = grid.gridSelection.selectAll();
break;
}
}
// Selection change failed for some unforseen reason.
if (!changed)
return false;
grid.invalidateDisplayListFor("selectionIndicator");
// Step 4: dispatch the "change" event.
if (hasEventListener(GridSelectionEvent.SELECTION_CHANGE))
{
const changeEvent:GridSelectionEvent =
new GridSelectionEvent(GridSelectionEvent.SELECTION_CHANGE,
false, true,
selectionEventKind, selectionChange);
dispatchEvent(changeEvent);
// TBD: to trigger bindings on grid selectedCell/Index/Item properties
if (grid.hasEventListener(GridSelectionEvent.SELECTION_CHANGE))
grid.dispatchEvent(changeEvent);
}
// Step 5: dispatch the "valueCommit" event.
dispatchFlexEvent(FlexEvent.VALUE_COMMIT);
return true;
}
/**
* Updates the grid's caret position.
* If the caret position changes, the <code>grid</code> skin part dispatches a
* <code>caretChange</code> event.
*
* @param newCaretRowIndex The 0-based rowIndex of the new caret position.
*
* @param newCaretColumnIndex The 0-based columnIndex of the new caret
* position. If the selectionMode is row-based, this is -1.
*
* @see spark.events.GridCaretEvent#CARET_CHANGE
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
protected function commitCaretPosition(newCaretRowIndex:int,
newCaretColumnIndex:int):void
{
grid.caretRowIndex = newCaretRowIndex;
grid.caretColumnIndex = newCaretColumnIndex;
}
/**
* Creates a grid selection object to use to manage selection. Override this method if you have a custom grid
* selection that you want to use in place of the default.
*
* @see spark.components.Grid.createGridSelection
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
mx_internal function createGridSelection():GridSelection
{
return new GridSelection();
}
//--------------------------------------------------------------------------
//
// Selection Utility Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
protected function selectionContainsOnlyIndex(index:int):Boolean
{
if (grid)
return grid.selectionContainsIndex(index) && grid.selectionLength == 1;
else
return gridSelection.containsRow(index) && gridSelection.selectionLength == 1;
}
/**
* @private
* Return true, if the current selection, only contains the rows in
* the selection change.
*/
protected function selectionContainsOnlyIndices(selectionChange:CellRegion):Boolean
{
var selectionLength:int =
grid ? grid.selectionLength : gridSelection.selectionLength;
if (selectionChange.rowCount != selectionLength)
return false;
const bottom:int =
selectionChange.rowIndex + selectionChange.rowCount;
for (var rowIndex:int = selectionChange.rowIndex;
rowIndex < bottom;
rowIndex++)
{
if (grid)
{
if (!grid.selectionContainsIndex(rowIndex))
return false;
}
else
{
if (!gridSelection.containsRow(rowIndex))
return false;
}
}
return true;
}
/**
* @private
*/
private function selectionContainsOnlyCell(rowIndex:int, columnIndex:int):Boolean
{
if (grid)
return grid.selectionContainsCell(rowIndex, columnIndex) && grid.selectionLength == 1;
else
return gridSelection.containsCell(rowIndex, columnIndex) && gridSelection.selectionLength == 1;
}
/**
* @private
*/
private function selectionContainsOnlyCellRegion(rowIndex:int,
columnIndex:int,
rowCount:int,
columnCount:int):Boolean
{
if (grid)
{
return grid.selectionContainsCellRegion(
rowIndex, columnIndex, rowCount, columnCount) &&
grid.selectionLength == rowCount * columnCount;
}
else
{
return gridSelection.containsCellRegion(
rowIndex, columnIndex, rowCount, columnCount) &&
gridSelection.selectionLength == rowCount * columnCount;
}
}
//--------------------------------------------------------------------------
//
// Item Editor Methods
//
//--------------------------------------------------------------------------
/**
* Returns true if a datagrid cell is editable.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*
*/
protected function isCellEditable(rowIndex:int, columnIndex:int):Boolean
{
try
{
var dataItem:Object = dataProvider.getItemAt(rowIndex);
var column:GridColumn = GridColumn(columns.getItemAt(columnIndex));
var dataField:String = column.dataField;
}
catch (e:RangeError)
{
return false;
}
return isDataEditable(dataItem, dataField);
}
/**
* Override to make a datagrid cell editable based on the data item.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*
*/
protected function isDataEditable(dataItem:Object, dataField:String):Boolean
{
return (dataItem != null);
}
/**
* Starts an editor session on a selected cell in the grid. This method
* by-passes checks of the editable property on the DataGrid and GridColumn
* that prevent the user interface from starting an editor session.
*
* A <code>startItemEditorSession</code> event is dispatched before
* an item editor is created. This allows a listener dynamically change
* the item editor for a specified cell.
*
* The event can also be cancelled by calling the
* <code>preventDefault()</code> method, to prevent the
* editor session from being created.
*
* @param rowIndex The zero-based row index of the cell to edit.
*
* @param columnIndex The zero-based column index of the cell to edit.
*
* @return <code>true</code> if the editor session was started.
* Returns <code>false</code> if the editor session was cancelled or was
* otherwise unable to be started. Note that an editor session cannot be
* started in a column that is not visible.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*
*/
public function startItemEditorSession(rowIndex:int, columnIndex:int):Boolean
{
if (editor && isCellEditable(rowIndex,columnIndex))
return editor.startItemEditorSession(rowIndex, columnIndex);
return false;
}
/**
* Closes the currently active editor and optionally saves the editor's value
* by calling the item editor's <code>save()</code> method.
* If the <code>cancel</code> parameter is <code>true</code>,
* then the editor's <code>cancel()</code> method is called instead.
*
* @param cancel If <code>false</code>, the data in the editor is saved.
* Otherwise the data in the editor is discarded.
*
* @return <code>true</code> if the editor session was saved,
* and <code>false</code> if the save was cancelled.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function endItemEditorSession(cancel:Boolean = false):Boolean
{
if (editor)
return editor.endItemEditorSession(cancel);
return false;
}
/**
* Create the data grid editor. Overriding this function will
* allow the complete replacement of the data grid editor.
*/
mx_internal function createEditor():DataGridEditor
{
return new DataGridEditor(this);
}
//--------------------------------------------------------------------------
//
// Sorting Methods
//
//--------------------------------------------------------------------------
private function ensureComplexFieldsMonitoring(sortFields:Array):void
{
if(!(dataProvider is ListCollectionView))
return;
for (var i:int = 0; i < sortFields.length; i++)
{
var sortField:ISortField = sortFields[i];
if(sortField is IComplexSortField)
break;
}
if(i < sortFields.length)
{
if(!ListCollectionView(dataProvider).complexFieldWatcher)
ListCollectionView(dataProvider).complexFieldWatcher = new ComplexFieldChangeWatcher();
}
else
ListCollectionView(dataProvider).complexFieldWatcher = null;
}
/**
* Sort the DataGrid by one or more columns, and refresh the display.
*
* <p>If the <code>dataProvider</code> is an ICollectionView, then it's <code>sort</code> property is
* set to a value based on each column's <code>dataField</code>, <code>sortCompareFunction</code>,
* and <code>sortDescending</code> flag.
* Then, the data provider's <code>refresh()</code> method is called. </p>
*
* <p>If the <code>dataProvider</code> is not an ICollectionView, then this method has no effect.</p>
*
* <p>If isInteractive is true then a <code>GridSortEvent.SORT_CHANGING</code> is dispatched before the
* sort is applied. Listeners can change modify the event to change the sort or cancel
* the event to cancel the sort. If isInteractive is true and the sort is not cancelled, then a
* <code>GridSortEvent.SORT_CHANGE</code> event is dispatched after the dataProvider's sort has been
* updated.</p>
*
* <p>If the sort has not be cancelled, the columnHeaderGroup's <code>visibleSortIndicatorIndices</code> is updated.</p>
*
* @param columnIndices The indices of the columns by which to sort the <code>dataProvider</code>.
*
* @param isInteractive If true, <code>GridSortEvent.SORT_CHANGING</code> and
* <code>GridSortEvent.SORT_CHANGE</code> events are dispatched.
*
* @return <code>true</code> if the <code>dataProvider</code> was sorted with the provided
* column indicies.
*
* @see spark.components.DataGrid#dataProvider
* @see spark.components.gridClasses.GridColumn#sortCompareFunction
* @see spark.components.gridClasses.GridColumn#sortDescending
* @see spark.components.gridClasses.GridColumn#sortField
* @see spark.components.GridColumnHeaderGroup#visibleSortIndicatorIndices
* @see spark.events.GridSortEvent
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function sortByColumns(columnIndices:Vector.<int>, isInteractive:Boolean=false):Boolean
{
const dataProvider:ICollectionView = this.dataProvider as ICollectionView;
if (!dataProvider)
return false;
var sort:ISort = dataProvider.sort;
if (sort)
sort.compareFunction = null;
else
sort = new Sort();
var sortFields:Array = createSortFields(columnIndices, sort.fields);
if (!sortFields || (sortFields.length == 0))
return false;
var oldSortFields:Array = (dataProvider.sort) ? dataProvider.sort.fields : null;
// Dispatch the "changing" event. If preventDefault() is called
// on this event, the sort operation will be cancelled. If columnIndices or
// sortFields are changed, the new values will be used.
if (isInteractive)
{
// This is a shallow copy which means only the pointers to the ISortField objects
// are copied to the new Array, not the ISortField objects themselves.
if (oldSortFields)
oldSortFields = oldSortFields.concat();
if (hasEventListener(GridSortEvent.SORT_CHANGING))
{
const changingEvent:GridSortEvent =
new GridSortEvent(GridSortEvent.SORT_CHANGING,
false, true,
columnIndices,
oldSortFields, /* intended to be read-only but no way to enforce this */
sortFields);
// The event was cancelled so don't sort.
if (!dispatchEvent(changingEvent))
return false;
// Update the sort columns since they might have changed.
columnIndices = changingEvent.columnIndices;
if (!columnIndices)
return false;
// Update the new sort fields since they might have changed.
sortFields = changingEvent.newSortFields;
if (!sortFields)
return false;
}
}
// Remove each old SortField that's not a member of the new sortFields Array
// as a "styleClient" of this DataGrid.
if (oldSortFields)
{
for each (var oldSortField:ISortField in oldSortFields)
{
var oldASC:IAdvancedStyleClient = oldSortField as IAdvancedStyleClient;
if (!oldASC || (oldASC.styleParent != this) || (sortFields.indexOf(oldASC) != -1))
continue;
removeStyleClient(oldASC);
}
}
// Add new SortFields as "styleClients" of this DataGrid so that they
// inherit this DataGrid's locale style.
for each (var newSortField:ISortField in sortFields)
{
var newASC:IAdvancedStyleClient = newSortField as IAdvancedStyleClient;
if (!newASC || (newASC.styleParent == this))
continue;
addStyleClient(newASC);
}
ensureComplexFieldsMonitoring(sortFields);
sort.fields = sortFields;
dataProvider.sort = sort;
dataProvider.refresh();
if (isInteractive)
{
// Dispatch the "change" event.
if (hasEventListener(GridSortEvent.SORT_CHANGE))
{
const changeEvent:GridSortEvent =
new GridSortEvent(GridSortEvent.SORT_CHANGE,
false, true,
columnIndices,
oldSortFields, sortFields);
dispatchEvent(changeEvent);
}
// Update the visible sort indicators.
if (columnHeaderGroup)
columnHeaderGroup.visibleSortIndicatorIndices = columnIndices;
}
return true;
}
/**
* @private
* Return an array of ISortFields, one per column. If a matching sort field is found in
* previousFields then it's used, otherwise a new sort field is created. Each sort field's
* sortDescending property is set to match its column's sortDescending property.
*/
private function createSortFields(columnIndices:Vector.<int>, previousFields:Array):Array
{
const fields:Array = []; // return value
for each (var columnIndex:int in columnIndices)
{
var col:GridColumn = this.getColumnAt(columnIndex);
if (!col || (!col.dataField && (col.labelFunction == null) && (col.sortCompareFunction == null)))
return null;
var sortField:ISortField = findSortField(col.dataField, previousFields);
if (!sortField)
{
//Constructs a new sortField from the columns own sortField property.
sortField = col.sortField;
}
else
{
sortField.descending = col.sortDescending;
}
fields.push(sortField);
}
return fields;
}
/**
* @private
* Finds a SortField using the provided dataField and returns it.
*
* @param dataField The dataField of the column.
* @param fields The array of SortFields to search through.
*/
private static function findSortField(dataField:String, fields:Array):ISortField
{
if (dataField == null)
return null;
for each (var field:ISortField in fields)
{
if (field.name == dataField)
return field;
}
return null;
}
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
/**
* @private
* @return True if there is an anchor position set.
*/
private function isAnchorSet():Boolean
{
if (!grid)
return false;
if (isRowSelectionMode())
return grid.anchorRowIndex != -1;
else
return grid.anchorRowIndex != -1 && grid.anchorRowIndex != -1;
}
/**
* @private
* Toggle the selection and set the caret to rowIndex/columnIndex.
*
* @return True if the selection has changed.
*/
private function toggleSelection(rowIndex:int, columnIndex:int):Boolean
{
var kind:String;
if (isRowSelectionMode())
{
if (grid.selectionContainsIndex(rowIndex))
kind = GridSelectionEventKind.REMOVE_ROW;
else if (selectionMode == GridSelectionMode.MULTIPLE_ROWS)
kind = GridSelectionEventKind.ADD_ROW;
else
kind = GridSelectionEventKind.SET_ROW;
}
else if (isCellSelectionMode())
{
if (grid.selectionContainsCell(rowIndex, columnIndex))
kind = GridSelectionEventKind.REMOVE_CELL;
else if (selectionMode == GridSelectionMode.MULTIPLE_CELLS)
kind = GridSelectionEventKind.ADD_CELL;
else
kind = GridSelectionEventKind.SET_CELL;
}
var success:Boolean =
commitInteractiveSelection(kind, rowIndex, columnIndex);
// Update the caret if the selection was not cancelled.
if (success)
commitCaretPosition(rowIndex, columnIndex);
return success;
}
/**
* @private
* Extends the selection from the anchor position to the given 'caret'
* position and updates the caret position.
*/
private function extendSelection(caretRowIndex:int,
caretColumnIndex:int):Boolean
{
if (!isAnchorSet())
return false;
const startRowIndex:int = Math.min(grid.anchorRowIndex, caretRowIndex);
const endRowIndex:int = Math.max(grid.anchorRowIndex, caretRowIndex);
var success:Boolean;
if (selectionMode == GridSelectionMode.MULTIPLE_ROWS)
{
success = commitInteractiveSelection(
GridSelectionEventKind.SET_ROWS,
startRowIndex, -1,
endRowIndex - startRowIndex + 1, 0);
}
else if (selectionMode == GridSelectionMode.SINGLE_ROW)
{
// Can't extend the selection so move it to the caret position.
success = commitInteractiveSelection(
GridSelectionEventKind.SET_ROW, caretRowIndex, -1, 1, 0);
}
else if (selectionMode == GridSelectionMode.MULTIPLE_CELLS)
{
const rowCount:int = endRowIndex - startRowIndex + 1;
const startColumnIndex:int =
Math.min(grid.anchorColumnIndex, caretColumnIndex);
const endColumnIndex:int =
Math.max(grid.anchorColumnIndex, caretColumnIndex);
const columnCount:int = endColumnIndex - startColumnIndex + 1;
success = commitInteractiveSelection(
GridSelectionEventKind.SET_CELL_REGION,
startRowIndex, startColumnIndex,
rowCount, columnCount);
}
else if (selectionMode == GridSelectionMode.SINGLE_CELL)
{
// Can't extend the selection so move it to the caret position.
success = commitInteractiveSelection(
GridSelectionEventKind.SET_CELL,
caretRowIndex, caretColumnIndex, 1, 1);
}
// Update the caret.
if (success)
commitCaretPosition(caretRowIndex, caretColumnIndex);
return success;
}
/**
* @private
* Sets the selection and updates the caret and anchor positions.
*/
private function setSelectionAnchorCaret(rowIndex:int, columnIndex:int):Boolean
{
// click sets the selection and updates the caret and anchor
// positions.
var success:Boolean;
if (isRowSelectionMode())
{
// Select the row.
success = commitInteractiveSelection(
GridSelectionEventKind.SET_ROW,
rowIndex, columnIndex);
}
else if (isCellSelectionMode())
{
// Select the cell.
success = commitInteractiveSelection(
GridSelectionEventKind.SET_CELL,
rowIndex, columnIndex);
}
// Update the caret and anchor positions unless cancelled.
if (success)
{
commitCaretPosition(rowIndex, columnIndex);
grid.anchorRowIndex = rowIndex;
grid.anchorColumnIndex = columnIndex;
}
return success;
}
/**
* @private
* Returns the new caret position based on the current caret position and
* the navigationUnit as a Point, where x is the columnIndex and y is the
* rowIndex. Assures there is a valid caretPosition.
*/
private function setCaretToNavigationDestination(navigationUnit:uint):CellPosition
{
var caretRowIndex:int = grid.caretRowIndex;
var caretColumnIndex:int = grid.caretColumnIndex;
const rowCount:int = dataProviderLength;
const columnCount:int = columnsLength;
const gridLayout:GridLayout = grid.layout as GridLayout;
const centerGridView:GridView = gridLayout.centerGridView;
const topGridView:GridView = gridLayout.topGridView;
var caretGridView:GridView;
switch (navigationUnit)
{
case NavigationUnit.LEFT:
{
if (isCellSelectionMode())
{
if (grid.caretColumnIndex > 0)
caretColumnIndex = grid.getPreviousVisibleColumnIndex(caretColumnIndex);
}
break;
}
case NavigationUnit.RIGHT:
{
if (isCellSelectionMode())
{
if (grid.caretColumnIndex + 1 < columnCount)
caretColumnIndex = grid.getNextVisibleColumnIndex(caretColumnIndex);
}
break;
}
case NavigationUnit.UP:
{
if (grid.caretRowIndex > 0)
caretRowIndex--;
break;
}
case NavigationUnit.DOWN:
{
if (grid.caretRowIndex + 1 < rowCount)
caretRowIndex++;
break;
}
case NavigationUnit.PAGE_UP:
{
// If the caret is below the first visible row, then just move the caret up to
// the first visible row. Otherwise scroll up far enough to put the caret row
// at the top of the view.
caretGridView = ((lockedRowCount > 0) && (caretRowIndex <= lockedRowCount)) ? topGridView : centerGridView;
const firstVisibleRowIndex:int = caretGridView.gridViewLayout.getFirstFullyVisibleRowIndex();
if (caretRowIndex > firstVisibleRowIndex)
{
caretRowIndex = firstVisibleRowIndex;
}
else if (caretRowIndex >= lockedRowCount)
{
// Attempt to synchronously scroll caretRowIndex to bottom of the view
// and then reset the caretRowIndex to whatever row actually ends up at the top.
const caretRowBounds:Rectangle = grid.getRowBounds(caretRowIndex);
const visibleBounds:Rectangle = centerGridView.gridViewLayout.getVisibleBounds();
const pageUpDelta:Number = visibleBounds.bottom - caretRowBounds.bottom;
grid.verticalScrollPosition -= pageUpDelta;
validateNow();
caretRowIndex = centerGridView.gridViewLayout.getFirstFullyVisibleRowIndex();
}
break;
}
case NavigationUnit.PAGE_DOWN:
{
// If the caret is on the last locked row, move to the first unlocked row. Otherwise:
// If the caret is above the last visible row, then just move the caret to the last visible row.
// Otherwise scroll down far enough to position the caret row at the top of the grid view and
// then reset the caret to the new last fully visible row.
caretGridView = ((lockedRowCount > 0) && (caretRowIndex < (lockedRowCount - 1))) ? topGridView : centerGridView;
const lastVisibleRowIndex:int = caretGridView.gridViewLayout.getLastFullyVisibleRowIndex();
if ((lockedRowCount > 0) && (rowCount > lockedRowCount) && (caretRowIndex == (lockedRowCount - 1)))
{
caretRowIndex = lockedRowCount;
}
else if (caretRowIndex < lastVisibleRowIndex)
{
caretRowIndex = lastVisibleRowIndex;
}
else if (caretRowIndex >= lockedRowCount)
{
// Attempt to synchronously scroll caretRowIndex to the top of the view
// and then reset the caretRowIndex to whatever row actually ends up at the bottom.
grid.verticalScrollPosition = grid.getRowBounds(caretRowIndex).y;
validateNow();
caretRowIndex = centerGridView.gridViewLayout.getLastFullyVisibleRowIndex();
}
break;
}
case NavigationUnit.HOME:
{
caretRowIndex = 0;
caretColumnIndex = isCellSelectionMode() ? grid.getNextVisibleColumnIndex(-1) : -1;
break;
}
case NavigationUnit.END:
{
caretRowIndex = rowCount - 1;
caretColumnIndex = isCellSelectionMode() ? grid.getPreviousVisibleColumnIndex(columnCount) : -1;
// The heights of any rows that have not been rendered yet are
// estimated. Force them to draw so the heights are accurate.
// TBD: is there a better way to do this?
grid.verticalScrollPosition = grid.contentHeight;
validateNow();
if (grid.contentHeight != grid.verticalScrollPosition)
{
grid.verticalScrollPosition = grid.contentHeight;
validateNow();
}
break;
}
default:
{
return null;
}
}
return new CellPosition(caretRowIndex, caretColumnIndex);
}
/**
* @copy spark.components.Grid#ensureCellIsVisible()
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function ensureCellIsVisible(rowIndex:int, columnIndex:int = -1):void
{
if (grid)
grid.ensureCellIsVisible(rowIndex, columnIndex);
}
/**
* @private
*
* Adjusts the caret and the selection based on what keystroke is used
* in combination with a ctrl/cmd key or a shift key. Returns false
* if the selection was not changed.
*/
protected function adjustSelectionUponNavigation(event:KeyboardEvent):Boolean
{
// Some unrecognized key stroke was entered, return.
if (!NavigationUnit.isNavigationUnit(event.keyCode))
return false;
// If rtl layout, need to swap Keyboard.LEFT and Keyboard.RIGHT.
var navigationUnit:uint = mapKeycodeForLayoutDirection(event);
const newPosition:CellPosition = setCaretToNavigationDestination(navigationUnit);
if (!newPosition)
return false;
// Cancel so another component doesn't handle this event.
event.preventDefault();
var selectionChanged:Boolean = false;
if (event.shiftKey)
{
// The shift key-nav key combination extends the selection and
// updates the caret.
selectionChanged =
extendSelection(newPosition.rowIndex, newPosition.columnIndex);
}
else if (event.ctrlKey)
{
// If its a ctrl/cmd key-nav key combination, there is nothing
// more to do then set the caret.
commitCaretPosition(newPosition.rowIndex, newPosition.columnIndex);
}
else
{
// Select the current row/cell.
setSelectionAnchorCaret(newPosition.rowIndex, newPosition.columnIndex);
}
// Ensure this position is visible.
ensureCellIsVisible(newPosition.rowIndex, newPosition.columnIndex);
return true;
}
/**
* @private
*
* Returns true if committing the given selection operation would change
* the current selection.
*/
private function doesChangeCurrentSelection(
selectionEventKind:String,
selectionChange:CellRegion):Boolean
{
var changesSelection:Boolean;
const rowIndex:int = selectionChange.rowIndex;
const columnIndex:int = selectionChange.columnIndex;
const rowCount:int = selectionChange.rowCount;
const columnCount:int = selectionChange.columnCount;
switch (selectionEventKind)
{
case GridSelectionEventKind.SET_ROW:
{
changesSelection =
!selectionContainsOnlyIndex(rowIndex);
break;
}
case GridSelectionEventKind.ADD_ROW:
{
changesSelection =
!grid.selectionContainsIndex(rowIndex);
break;
}
case GridSelectionEventKind.REMOVE_ROW:
{
changesSelection = requireSelection ?
!selectionContainsOnlyIndex(rowIndex) :
grid.selectionContainsIndex(rowIndex);
break;
}
case GridSelectionEventKind.SET_ROWS:
{
changesSelection =
!selectionContainsOnlyIndices(selectionChange);
break;
}
case GridSelectionEventKind.SET_CELL:
{
changesSelection =
!selectionContainsOnlyCell(rowIndex, columnIndex);
break;
}
case GridSelectionEventKind.ADD_CELL:
{
changesSelection =
!grid.selectionContainsCell(rowIndex, columnIndex);
break;
}
case GridSelectionEventKind.REMOVE_CELL:
{
changesSelection = requireSelection ?
!selectionContainsOnlyCell(rowIndex, columnIndex) :
grid.selectionContainsCell(rowIndex, columnIndex);
break;
}
case GridSelectionEventKind.SET_CELL_REGION:
{
changesSelection =
!selectionContainsOnlyCellRegion(
rowIndex, columnIndex, rowCount, columnCount);
break;
}
case GridSelectionEventKind.SELECT_ALL:
{
changesSelection = true;
break;
}
}
return changesSelection;
}
//--------------------------------------------------------------------------
//
// Grid event handlers
//
//--------------------------------------------------------------------------
/**
* @private
* Updating the hover is disabled if the mouse button was depressed
* while not over the grid. The common case for this is while
* scrolling. While the scroll thumb is depressed don't want the
* hover updated if the mouse drifts into the grid.
*/
protected function grid_rollOverHandler(event:GridEvent):void
{
if (event.isDefaultPrevented())
return;
// The related object is the object that was previously under
// the pointer.
if (event.buttonDown && event.relatedObject != grid)
updateHoverOnRollOver = false;
grid.hoverRowIndex = updateHoverOnRollOver ? event.rowIndex : -1;
grid.hoverColumnIndex = updateHoverOnRollOver ? event.columnIndex : -1;
event.updateAfterEvent();
}
/**
* @private
* If the mouse button is depressed while outside of the grid, the hover
* indicator is not enabled again until GRID_MOUSE_UP or GRID_ROLL_OUT.
*/
protected function grid_rollOutHandler(event:GridEvent):void
{
if (event.isDefaultPrevented())
return;
grid.hoverRowIndex = -1;
grid.hoverColumnIndex = -1;
updateHoverOnRollOver = true;
event.updateAfterEvent();
}
/**
* @private
* If the mouse button is depressed while outside of the grid, the hover
* indicator is not enabled again until GRID_MOUSE_UP or GRID_ROLL_OUT.
*/
protected function grid_mouseUpHandler(event:GridEvent):void
{
if (event.isDefaultPrevented())
return;
if (!updateHoverOnRollOver)
{
grid.hoverRowIndex = event.rowIndex;
grid.hoverColumnIndex = event.columnIndex;
updateHoverOnRollOver = true;
}
}
/**
* @private
*/
protected function grid_mouseDownHandler(event:GridEvent):void
{
if (event.isDefaultPrevented())
return;
const isCellSelection:Boolean = isCellSelectionMode();
const rowIndex:int = event.rowIndex;
const columnIndex:int = isCellSelection ? event.columnIndex : -1;
// Clicked on empty place in grid. Don't change selection or caret
// position.
if (rowIndex == -1 || isCellSelection && columnIndex == -1)
return;
if (event.ctrlKey)
{
// ctrl-click toggles the selection and updates caret and anchor.
if (!toggleSelection(rowIndex, columnIndex))
return;
grid.anchorRowIndex = rowIndex;
grid.anchorColumnIndex = columnIndex;
}
else
{
if (event.shiftKey)
{
// shift-click extends the selection and updates the caret.
if (grid.selectionMode == GridSelectionMode.MULTIPLE_ROWS || grid.selectionMode == GridSelectionMode.MULTIPLE_CELLS)
{
if (!extendSelection(rowIndex, columnIndex))
return;
}
}
else
{
// click sets the selection and updates the caret and anchor positions.
setSelectionAnchorCaret(rowIndex, columnIndex);
}
}
if (dragEnabled && isRowSelectionMode() && selectionContainsIndex(rowIndex))
{
pendingSelectionOnMouseUp = true;
}
// If selection is pending on mouse up then we have just moused down on
// an item, part of an already commited selection.
// However if we moused down on an item that's not currently selected,
// we must commit the selection before trying to start dragging since
// listeners may prevent the item from being selected.
if (!pendingSelectionOnMouseUp)
validateProperties();
mouseDownPoint = event.target.localToGlobal(new Point(event.localX, event.localY));
mouseDownObject = event.target as DisplayObject;
mouseDownRowIndex = rowIndex;
mouseDownColumnIndex = columnIndex;
var listenForDrag:Boolean = (dragEnabled &&
getStyle("interactionMode") == InteractionMode.MOUSE && selectedIndices &&
this.selectedIndices.indexOf(rowIndex) != -1);
// Handle any drag gestures that may have been started
if (listenForDrag)
{
// Listen for GRID_MOUSE_DRAG.
// The user may have cliked on the item renderer close
// to the edge of the list, and we still want to start a drag
// operation if they move out of the list.
grid.addEventListener(GridEvent.GRID_MOUSE_DRAG, grid_mouseDragHandler);
}
if (pendingSelectionOnMouseUp || listenForDrag)
{
// FIXME (dloverin): When dragging a proxy in an untrusted
// child application the cursor does not follow drag proxy.
// FIXME (dloverin): The listener on the sandbox root can be removed when:
// 1. MouseEventUtil.addDownDragUpListeners() dispatches
// a MOUSE_UP_SOMEWHERE.
// 2. Grid.grid_mouseDownDragUpHandler() changes its parameter from
// a MouseEvent to an event so a MOUSE_UP_SOMEWHERE event does
// not cause an RTE. Grid can then handle the MOUSE_UP_SOMEWHERE
// event by dispatching a MOUSE_UP or aGRID_MOUSE_UP_SOMEWHERE
// event.
systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, sandbox_mouseUpHandler, false, 0, true);
}
}
/**
* @private
* Redispatch the grid's "caretChange" event.
*/
protected function grid_caretChangeHandler(event:GridCaretEvent):void
{
if (hasEventListener(GridCaretEvent.CARET_CHANGE))
dispatchEvent(event);
}
/**
* @private
* Redispatch the grid's "valueCommit" event.
*/
protected function grid_valueCommitHandler(event:FlexEvent):void
{
if (hasEventListener(FlexEvent.VALUE_COMMIT))
dispatchEvent(event);
}
/**
* @private
*/
private function grid_invalidateDisplayListHandler(event:Event):void
{
// invalidate all IDataGridElements
if (columnHeaderGroup && grid.isInvalidateDisplayListReason("horizontalScrollPosition"))
columnHeaderGroup.invalidateDisplayList();
}
/**
* @private
*/
private function grid_invalidateSizeHandler(event:Event):void
{
// invalidate all IDataGridElements
if (columnHeaderGroup)
columnHeaderGroup.invalidateSize();
}
/**
* @private
*/
private function grid_gridViewsChangedHandler(event:Event):void
{
if (columnHeaderGroup)
columnHeaderGroup.configureGridColumnHeaderViews();
}
//--------------------------------------------------------------------------
//
// Header event handlers
//
//--------------------------------------------------------------------------
private var stretchCursorID:int = CursorManager.NO_CURSOR;
private var resizeColumn:GridColumn = null;
private var resizeAnchorX:Number = NaN;
private var resizeColumnWidth:Number = NaN;
private var nextColumn:GridColumn = null; // RTL layout only
private var nextColumnWidth:Number = NaN; // RTL layout only
/**
* @private
* Flip the specified column's sortDescending flag and and sort the dataProvider. If
* isMultiColumnSort is true, add the column to the set of columns being sorted (that's
* columnHeaderGroup.visibleSortIndicatorIndices) otherwise just sort per the specified column.
*
* If the sort was possible and was not cancelled (see sortByColumns()) then set the
* columnHeaderGroup's selectedColumnIndex and return true, otherwise return false.
*/
private function interactiveSortByColumns(column:GridColumn, isMultiColumnSort:Boolean):Boolean
{
if (!enabled || !sortableColumns || !column || !column.sortable || !columnHeaderGroup)
return false;
const columnIndex:int = column.columnIndex;
const currentSortColumnIndices:Vector.<int> = columnHeaderGroup.visibleSortIndicatorIndices;
const reverseSort:Boolean = currentSortColumnIndices.indexOf(columnIndex) != -1;
var sortColumnIndices:Vector.<int>;
if (isMultiColumnSort && multiColumnSortingEnabled)
{
sortColumnIndices = currentSortColumnIndices;
if (!reverseSort)
sortColumnIndices.push(columnIndex);
}
else
{
sortColumnIndices = new <int>[columnIndex];
}
if (reverseSort)
column.sortDescending = !column.sortDescending;
if (!sortByColumns(sortColumnIndices, true) && reverseSort)
{
column.sortDescending = !column.sortDescending; // sort was cancelled
return false;
}
columnHeaderGroup.selectedColumnIndex = column.columnIndex;
return true;
}
/**
* @private
*/
protected function columnHeaderGroup_clickHandler(event:GridEvent):void
{
interactiveSortByColumns(event.column, event.ctrlKey);
}
/**
* @private
*/
protected function columnHeaderGroup_rollOverHandler(event:GridEvent):void
{
if (resizeColumn || !enabled)
return;
columnHeaderGroup.hoverColumnIndex = event.columnIndex;
}
/**
* @private
*/
protected function columnHeaderGroup_rollOutHandler(event:GridEvent):void
{
if (!enabled)
return;
columnHeaderGroup.hoverColumnIndex = -1;
}
/**
* @private
*/
protected function separator_mouseDownHandler(event:GridEvent):void
{
const column:GridColumn = event.column;
if (!enabled || !grid.resizableColumns || !column || !column.resizable)
return;
resizeColumn = event.column;
resizeAnchorX = event.localX;
resizeColumnWidth = grid.getColumnWidth(resizeColumn.columnIndex);
// If we're laying out RTL then dragging to the left - which increases
// a column's width - and the Grid's width is unconstrained, then only
// allow the column's width to grow to the extent that the adjacent
// (next) column can shrink.
if (isNaN(explicitWidth) && (layoutDirection == LayoutDirection.RTL))
{
const nextColumnIndex:int = grid.getNextVisibleColumnIndex(resizeColumn.columnIndex);
nextColumn = getColumnAt(nextColumnIndex);
nextColumnWidth = Math.ceil(grid.getColumnWidth(nextColumnIndex));
}
else
{
nextColumn = null;
nextColumnWidth = NaN;
}
// Give all of the columns to the left of this one an explicit so that resizing
// this column doesn't change their width and, consequently, this column's location.
const resizeColumnIndex:int = resizeColumn.columnIndex;
for (var columnIndex:int = 0; columnIndex < resizeColumnIndex; columnIndex++)
{
var gc:GridColumn = getColumnAt(columnIndex);
if (gc.visible && isNaN(gc.width))
gc.width = grid.getColumnWidth(columnIndex);
}
}
/**
* @private
*/
protected function separator_mouseDragHandler(event:GridEvent):void
{
if (!resizeColumn)
return;
const widthDelta:Number = event.localX - resizeAnchorX;
const minWidth:Number = isNaN(resizeColumn.minWidth) ? 0 : resizeColumn.minWidth;
const maxWidth:Number = resizeColumn.maxWidth;
var newWidth:Number = Math.ceil(resizeColumnWidth + widthDelta);
// Layout is RTL (see sparator_mouseDownHandler). Make sure that the
// next column's width can shrink as much as the resizeColumn is growing,
// or vice versa.
if (nextColumn)
{
const nextMinWidth:Number = isNaN(nextColumn.minWidth) ? 0 : nextColumn.minWidth;
if (Math.ceil(nextColumnWidth - widthDelta) <= nextMinWidth)
return;
if (Math.ceil(resizeColumnWidth + widthDelta) <= minWidth)
return;
nextColumn.width = nextColumnWidth - widthDelta;
}
newWidth = Math.max(newWidth, minWidth);
if (!isNaN(maxWidth))
newWidth = Math.min(newWidth, maxWidth);
resizeColumn.width = newWidth;
validateNow(); // smooth out the drag
event.updateAfterEvent();
}
/**
* @private
*/
protected function separator_mouseUpHandler(event:GridEvent):void
{
if (!resizeColumn)
return;
resizeColumn = null;
cursorManager.removeCursor(stretchCursorID);
}
/**
* @private
*/
protected function separator_rollOverHandler(event:GridEvent):void
{
const column:GridColumn = event.column;
if (resizeColumn || !enabled || !grid.resizableColumns || !column || !column.resizable)
return;
var stretchCursorClass:Class = getStyle("stretchCursor") as Class;
if (stretchCursorClass)
stretchCursorID = cursorManager.setCursor(stretchCursorClass, CursorManagerPriority.HIGH, 0, 0);
}
/**
* @private
*/
protected function separator_rollOutHandler(event:GridEvent):void
{
if (!enabled || resizeColumn)
return;
cursorManager.removeCursor(stretchCursorID);
}
//--------------------------------------------------------------------------
//
// DataGrid event handlers
//
//--------------------------------------------------------------------------
/**
* @private
* The UIComponent's focusInHandler and focusOutHandler draw the
* focus. This handler exists only when there is a caretIndicator part.
*/
protected function dataGrid_focusHandler(event:FocusEvent):void
{
if (!grid)
return;
const isFocusIn:Boolean = event.type == FocusEvent.FOCUS_IN;
const isFocusOut:Boolean = event.type == FocusEvent.FOCUS_OUT;
if (isOurFocus(DisplayObject(event.target)))
grid.showCaret = isFocusIn && (selectionMode != GridSelectionMode.NONE);
if (isFocusIn)
internalFocusOwner = GRID_FOCUS_OWNER;
else if (isFocusOut)
internalFocusOwner = NO_FOCUS_OWNER;
}
//--------------------------------------------------------------------------
//
// Drag methods
//
//--------------------------------------------------------------------------
/**
* @private
* The default handler for the <code>dragStart</code> event.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function dragStartHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
var dragSource:DragSource = new DragSource();
addDragData(dragSource);
DragManager.doDrag(this,
dragSource,
event,
createDragIndicator(),
0 /*xOffset*/,
0 /*yOffset*/,
0.5 /*imageAlpha*/,
dragMoveEnabled);
}
/**
* @private
* Used to sort the selected indices during drag and drop operations.
*/
private function compareValues(a:int, b:int):int
{
return a - b;
}
/**
* @private
* Handles <code>DragEvent.DRAG_COMPLETE</code> events. This method
* removes the items from the data provider.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function dragCompleteHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
// Remove the dragged items only if they were drag moved to
// a different list. If the items were drag moved to this
// list, the reordering was already handles in the
// DragEvent.DRAG_DROP listener.
if (!dragMoveEnabled ||
event.action != DragManager.MOVE ||
event.relatedObject == this)
return;
// Clear the selection, but remember which items were moved
var movedIndices:Vector.<int> = selectedIndices;
selectedIndices = new Vector.<int>();
validateProperties(); // To commit the selection
// Remove the moved items
movedIndices.sort(compareValues);
var count:int = movedIndices.length;
for (var i:int = count - 1; i >= 0; i--)
{
dataProvider.removeItemAt(movedIndices[i]);
}
}
/**
* Creates an instance of a class that is used to display the visuals
* of the dragged items during a drag and drop operation.
* The default <code>DragEvent.DRAG_START</code> handler passes the
* instance to the <code>DragManager.doDrag()</code> method.
*
* @return The IFlexDisplayObject representing the drag indicator.
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function createDragIndicator():IFlexDisplayObject
{
var dragIndicator:IFlexDisplayObject;
var dragIndicatorClass:Class = Class(getStyle("dragIndicatorClass"));
if (dragIndicatorClass)
{
dragIndicator = new dragIndicatorClass();
if (dragIndicator is IVisualElement)
IVisualElement(dragIndicator).owner = this;
}
return dragIndicator;
}
/**
* Adds the selected items to the DragSource object as part of
* a drag-and-drop operation.
* Override this method to add other data to the drag source.
*
* @param ds The DragSource object to which to add the data.
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function addDragData(dragSource:DragSource):void
{
dragSource.addHandler(copySelectedItemsForDragDrop, "itemsByIndex");
// Calculate the index of the focus item within the vector
// of ordered items returned for the "itemsByIndex" format.
var caretIndex:int = 0;
var draggedIndices:Vector.<int> = selectedIndices;
var count:int = draggedIndices.length;
for (var i:int = 0; i < count; i++)
{
if (mouseDownRowIndex > draggedIndices[i])
caretIndex++;
}
dragSource.addData(caretIndex, "caretIndex");
}
/**
* @private
*/
private function copySelectedItemsForDragDrop():Vector.<Object>
{
// Copy the vector so that we don't modify the original
// since selectedIndices returns a reference.
var draggedIndices:Vector.<int> = selectedIndices.slice(0, selectedIndices.length);
var result:Vector.<Object> = new Vector.<Object>(draggedIndices.length);
// Sort in the order of the data source
draggedIndices.sort(compareValues);
// Copy the items
var count:int = draggedIndices.length;
for (var i:int = 0; i < count; i++)
result[i] = dataProvider.getItemAt(draggedIndices[i]);
return result;
}
/**
* @private
* Handles <code>MouseEvent.MOUSE_MOVE</code> events from any mouse
* targets contained in the list including the renderers. This method
* watches for a gesture that constitutes the beginning of a
* drag drop and send a <code>DragEvent.DRAG_START</code> event.
* It also checks to see if the mouse is over a non-target area of a
* renderer so that Flex can try to make it look like that renderer was
* the target.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function grid_mouseDragHandler(event:MouseEvent):void
{
if (!mouseDownPoint || !dragEnabled)
return;
var pt:Point = new Point(event.localX, event.localY);
pt = DisplayObject(event.target).localToGlobal(pt);
const DRAG_THRESHOLD:int = 5;
if (Math.abs(mouseDownPoint.x - pt.x) > DRAG_THRESHOLD ||
Math.abs(mouseDownPoint.y - pt.y) > DRAG_THRESHOLD)
{
var dragEvent:DragEvent = new DragEvent(DragEvent.DRAG_START);
dragEvent.dragInitiator = this;
var localMouseDownPoint:Point = this.globalToLocal(mouseDownPoint);
dragEvent.localX = localMouseDownPoint.x;
dragEvent.localY = localMouseDownPoint.y;
dragEvent.buttonDown = true;
// We're starting a drag operation, remove the handlers
// that are monitoring the mouse move, we don't need them anymore:
dispatchEvent(dragEvent);
var gridEvent:GridEvent = new GridEvent(GridEvent.GRID_MOUSE_UP, false, false, event.localX, event.localY,
event.relatedObject, event.ctrlKey, event.altKey, event.shiftKey, event.buttonDown, event.delta);
// Finally, remove the mouse handlers
removeMouseHandlersForDragStart(gridEvent);
}
}
private function removeMouseHandlersForDragStart(event:GridEvent):void
{
// If dragging failed, but we had a pending selection, commit it here
if (pendingSelectionOnMouseUp && !DragManager.isDragging)
{
const rowIndex:int = mouseDownRowIndex;
const columnIndex:int = mouseDownColumnIndex;
if (event.ctrlKey)
{
// ctrl-click toggles the selection and updates caret and anchor.
if (!toggleSelection(rowIndex, columnIndex))
return;
grid.anchorRowIndex = rowIndex;
grid.anchorColumnIndex = columnIndex;
}
else if (event.shiftKey)
{
// shift-click extends the selection and updates the caret.
if (grid.selectionMode == GridSelectionMode.MULTIPLE_ROWS ||
grid.selectionMode == GridSelectionMode.MULTIPLE_CELLS)
{
if (!extendSelection(rowIndex, columnIndex))
return;
}
}
else
{
// click sets the selection and updates the caret and anchor
// positions.
setSelectionAnchorCaret(rowIndex, columnIndex);
}
}
// Always clean up the flag, even if currently dragging.
pendingSelectionOnMouseUp = false;
mouseDownPoint = null;
mouseDownObject = null;
mouseDownRowIndex = -1;
mouseDownColumnIndex = -1;
grid.removeEventListener(GridEvent.GRID_MOUSE_DRAG, grid_mouseDragHandler);
systemManager.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, sandbox_mouseUpHandler, false);
}
/**
* @private
* Handles <code>SandboxMouseEvent.MOUSE_UP_SOMEWHERE</code> events.
*
* @param event The SandboxMouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
protected function sandbox_mouseUpHandler(event:SandboxMouseEvent):void
{
var sandboxMouseEvent:SandboxMouseEvent = SandboxMouseEvent(event);
var gridEvent:GridEvent = new GridEvent(GridEvent.GRID_MOUSE_UP, false, false, NaN, NaN, null,
sandboxMouseEvent.ctrlKey, sandboxMouseEvent.altKey, sandboxMouseEvent.shiftKey,
sandboxMouseEvent.buttonDown, 0);
removeMouseHandlersForDragStart(gridEvent);
}
//--------------------------------------------------------------------------
//
// Drop methods
//
//--------------------------------------------------------------------------
/**
* @private
* Used in drag n drop methods for drop location / drop indicators.
*/
private var _gridViewLayout:GridViewLayout = null;
/**
* Creates and instance of the dropIndicator class that is used to
* display the visuals of the drop location during a drag and drop
* operation. The instance is set in the layout's
* <code>dropIndicator</code> property.
*
* <p>If you override the <code>dragEnter</code> event handler,
* and call <code>preventDefault()</code> so that the default handler does not execute,
* call <code>createDropIndicator()</code> to create the drop indicator.</p>
*
* @return Returns the dropIndicator that was set in the layout.
*
* @see #destroyDropIndicator
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function createDropIndicator():DisplayObject
{
// Do we have a drop indicator already?
if (_gridViewLayout.dropIndicator)
return _gridViewLayout.dropIndicator;
var dropIndicatorInstance:DisplayObject;
if (dropIndicator)
{
dropIndicatorInstance = DisplayObject(createDynamicPartInstance("dropIndicator"));
}
else
{
var dropIndicatorClass:Class = Class(getStyle("dropIndicatorSkin"));
if (dropIndicatorClass)
dropIndicatorInstance = new dropIndicatorClass();
}
if (dropIndicatorInstance is IVisualElement)
IVisualElement(dropIndicatorInstance).owner = this;
// Set it in the layout
_gridViewLayout.dropIndicator = dropIndicatorInstance;
return dropIndicatorInstance;
}
/**
* Releases the <code>dropIndicator</code> instance that is currently set in the layout.
*
* <p>If you override the <code>dragExit</code> event handler,
* and call <code>preventDefault()</code> so that the default handler does not execute,
* call <code>destroyDropIndicator()</code> to delete the drop indicator.</p>
*
* @return Returns the dropIndicator that was removed.
*
* @see #createDropIndicator
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
public function destroyDropIndicator():DisplayObject
{
var dropIndicatorInstance:DisplayObject = _gridViewLayout.dropIndicator;
if (!dropIndicatorInstance)
return null;
// Release the reference from the layout
_gridViewLayout.dropIndicator = null;
// Release it if it's a dynamic skin part
var count:int = numDynamicParts("dropIndicator");
for (var i:int = 0; i < count; i++)
{
if (dropIndicatorInstance == getDynamicPartAt("dropIndicator", i))
{
// This was a dynamic part, remove it now:
removeDynamicPartInstance("dropIndicator", dropIndicatorInstance);
break;
}
}
return dropIndicatorInstance;
}
/**
* @private
* Handles <code>DragEvent.DRAG_ENTER</code> events. This method
* determines if the DragSource object contains valid elements and uses
* the <code>DragManager.showDropFeedback()</code> method to set up the
* UI feedback as well as the layout's <code>showDropIndicator()</code>
* method to display the drop indicator and initiate drag scrolling.
*
* @param event The DragEvent object.
*
* @see spark.layouts.LayoutBase#showDropIndicator
* @see spark.layouts.LayoutBase#hideDropIndicator
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function dragEnterHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
if (!_gridViewLayout)
{
//Store a current gridview layout.
_gridViewLayout = (grid.layout as GridLayout).centerGridView.layout as GridViewLayout;
}
var dropLocation:DropLocation = _gridViewLayout.calculateDropLocation(event);
if (dropLocation)
{
DragManager.acceptDragDrop(this);
// Create the dropIndicator instance. The layout will take care of
// parenting, sizing, positioning and validating the dropIndicator.
createDropIndicator();
// Show focus
drawFocusAnyway = true;
drawFocus(true);
// Notify manager we can drop
DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
// Show drop indicator
_gridViewLayout.showDropIndicator(dropLocation);
}
else
{
DragManager.showFeedback(DragManager.NONE);
}
}
/**
* @private
* Handles <code>DragEvent.DRAG_OVER</code> events. This method
* determines if the DragSource object contains valid elements and uses
* the <code>showDropFeedback()</code> method to set up the UI feedback
* as well as the layout's <code>showDropIndicator()</code> method
* to display the drop indicator and initiate drag scrolling.
*
* @param event The DragEvent object.
*
* @see spark.layouts.LayoutBase#showDropIndicator
* @see spark.layouts.LayoutBase#hideDropIndicator
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function dragOverHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
var dropLocation:DropLocation = _gridViewLayout.calculateDropLocation(event);
if (dropLocation)
{
// Show focus
drawFocusAnyway = true;
drawFocus(true);
// Notify manager we can drop
DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
// Show drop indicator
_gridViewLayout.showDropIndicator(dropLocation);
}
else
{
// Hide if previously showing
_gridViewLayout.hideDropIndicator();
// Hide focus
drawFocus(false);
drawFocusAnyway = false;
// Notify manager we can't drop
DragManager.showFeedback(DragManager.NONE);
}
}
/**
* @private
* Handles <code>DragEvent.DRAG_EXIT</code> events. This method hides
* the UI feedback by calling the <code>hideDropFeedback()</code> method
* and also hides the drop indicator by calling the layout's
* <code>hideDropIndicator()</code> method.
*
* @param event The DragEvent object.
*
* @see spark.layouts.LayoutBase#showDropIndicator
* @see spark.layouts.LayoutBase#hideDropIndicator
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function dragExitHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
// Hide if previously showing
_gridViewLayout.hideDropIndicator();
// Hide focus
drawFocus(false);
drawFocusAnyway = false;
// Destroy the dropIndicator instance
destroyDropIndicator();
}
/**
* @private
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
private function touchInteractionStartHandler(event:TouchInteractionEvent):void
{
// cancel actual selection
mouseDownRowIndex = -1;
mouseDownColumnIndex = -1;
mouseDownObject = null;
mouseDownPoint = null;
pendingSelectionOnMouseUp = false;
}
/**
* @private
* Handles <code>DragEvent.DRAG_DROP events</code>. This method hides
* the drop feedback by calling the <code>hideDropFeedback()</code> method.
*
* <p>If the action is a <code>COPY</code>,
* then this method makes a deep copy of the object
* by calling the <code>ObjectUtil.copy()</code> method,
* and replaces the copy's <code>uid</code> property (if present)
* with a new value by calling the <code>UIDUtil.createUID()</code> method.</p>
*
* @param event The DragEvent object.
*
* @see mx.utils.ObjectUtil
* @see mx.utils.UIDUtil
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function dragDropHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
// Hide the drop indicator
_gridViewLayout.hideDropIndicator();
destroyDropIndicator();
// Hide focus
drawFocus(false);
drawFocusAnyway = false;
// Get the dropLocation
var dropLocation:DropLocation = _gridViewLayout.calculateDropLocation(event);
if (!dropLocation)
return;
// Find the dropIndex
var dropIndex:int = dropLocation.dropIndex;
// Make sure the manager has the appropriate action
DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
var dragSource:DragSource = event.dragSource;
var items:Vector.<Object> = dragSource.dataForFormat("itemsByIndex") as Vector.<Object>;
var caretIndex:int = -1;
if (dragSource.hasFormat("caretIndex"))
caretIndex = event.dragSource.dataForFormat("caretIndex") as int;
// Clear the selection first to avoid extra work while adding and removing items.
// We will set a new selection further below in the method.
var indices:Vector.<int> = selectedIndices;
clearSelection();
validateProperties(); // To commit the selection
// If we are reordering the list, remove the items now,
// adjusting the dropIndex in the mean time.
// If the items are drag moved to this list from a different list,
// the drag initiator will remove the items when it receives the
// DragEvent.DRAG_COMPLETE event.
if (dragMoveEnabled &&
event.action == DragManager.MOVE &&
event.dragInitiator == this)
{
// Remove the previously selected items
indices.sort(compareValues);
for (var i:int = indices.length - 1; i >= 0; i--)
{
if (indices[i] < dropIndex)
dropIndex--;
dataProvider.removeItemAt(indices[i]);
}
}
// Drop the items at the dropIndex
var newSelection:Vector.<int> = new Vector.<int>();
// Update the selection with the index of the caret item
if (caretIndex != -1)
newSelection.push(dropIndex + caretIndex);
// Create dataProvider if needed
if (!dataProvider)
dataProvider = new ArrayCollection();
var copyItems:Boolean = (event.action == DragManager.COPY);
for (i = 0; i < items.length; i++)
{
// Get the item, clone if needed
var item:Object = items[i];
if (copyItems)
item = copyItemWithUID(item);
// Copy the data
dataProvider.addItemAt(item, dropIndex + i);
// Update the selection
if (i != caretIndex)
newSelection.push(dropIndex + i);
}
// Set the selection
selectedIndices = newSelection;
// Scroll the caret index in view
if (caretIndex != -1)
ensureCellIsVisible(dropIndex + items.length);
}
/**
* Makes a deep copy of the object by calling the
* <code>ObjectUtil.copy()</code> method, and replaces
* the copy's <code>uid</code> property (if present) with a
* new value by calling the <code>UIDUtil.createUID()</code> method.
*
* <p>This method is used for a drag and drop copy.</p>
*
* @param item The item to copy.
*
* @return The copy of the object.
*
* @see mx.utils.ObjectUtil
* @see mx.utils.UIDUtil
*
* @langversion 3.0
* @playerversion Flash 11
* @playerversion AIR 3.0
* @productversion Flex 5.0
*/
protected function copyItemWithUID(item:Object):Object
{
var copyObj:Object = ObjectUtil.copy(item);
if (copyObj is IUID)
{
IUID(copyObj).uid = UIDUtil.createUID();
}
else if (copyObj is Object && "mx_internal_uid" in copyObj)
{
copyObj.mx_internal_uid = UIDUtil.createUID();
}
return copyObj;
}
}
}