src/it/ht/rcs/console/utils/FilterComboBox.as
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010 Tink Ltd | http://www.tink.ws
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////
package it.ht.rcs.console.utils
{
import flash.display.DisplayObject;
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.text.TextFormat;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.ListCollectionView;
import mx.controls.ComboBase;
import mx.controls.List;
import mx.controls.TextInput;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListBase;
import mx.controls.listClasses.ListData;
import mx.controls.listClasses.ListItemRenderer;
import mx.core.ClassFactory;
import mx.core.EdgeMetrics;
import mx.core.FlexVersion;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IUITextField;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.effects.Tween;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DropdownEvent;
import mx.events.FlexEvent;
import mx.events.FlexMouseEvent;
import mx.events.InterManagerRequest;
import mx.events.ListEvent;
import mx.events.SandboxMouseEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.managers.ISystemManager;
import mx.managers.PopUpManager;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the ComboBox contents changes as a result of user
* interaction, when the <code>selectedIndex</code> or
* <code>selectedItem</code> property changes, and, if the ComboBox control
* is editable, each time a keystroke is entered in the box.
*
* @eventType mx.events.ListEvent.CHANGE
*/
[Event(name="change", type="mx.events.ListEvent")]
/**
* Dispatched when the drop-down list is dismissed for any reason such when
* the user:
* <ul>
* <li>selects an item in the drop-down list</li>
* <li>clicks outside of the drop-down list</li>
* <li>clicks the drop-down button while the drop-down list is
* displayed</li>
* <li>presses the ESC key while the drop-down list is displayed</li>
* </ul>
*
* @eventType mx.events.DropdownEvent.CLOSE
*/
[Event(name="close", type="mx.events.DropdownEvent")]
/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains an item from the
* dataProvider.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* @eventType mx.events.FlexEvent.DATA_CHANGE
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]
/**
* Dispatched if the <code>editable</code> property
* is set to <code>true</code> and the user presses the Enter key
* while typing in the editable text field.
*
* @eventType mx.events.FlexEvent.ENTER
*/
[Event(name="enter", type="mx.events.FlexEvent")]
/**
* Dispatched when user rolls the mouse out of a drop-down list item.
* The event object's <code>target</code> property contains a reference
* to the ComboBox and not the drop-down list.
*
* @eventType mx.events.ListEvent.ITEM_ROLL_OUT
*/
[Event(name="itemRollOut", type="mx.events.ListEvent")]
/**
* Dispatched when the user rolls the mouse over a drop-down list item.
* The event object's <code>target</code> property contains a reference
* to the ComboBox and not the drop-down list.
*
* @eventType mx.events.ListEvent.ITEM_ROLL_OVER
*/
[Event(name="itemRollOver", type="mx.events.ListEvent")]
/**
* Dispatched when the user clicks the drop-down button
* to display the drop-down list. It is also dispatched if the user
* uses the keyboard and types Ctrl-Down to open the drop-down.
*
* @eventType mx.events.DropdownEvent.OPEN
*/
[Event(name="open", type="mx.events.DropdownEvent")]
/**
* Dispatched when the user scrolls the ComboBox control's drop-down list.
*
* @eventType mx.events.ScrollEvent.SCROLL
*/
[Event(name="scroll", type="mx.events.ScrollEvent")]
//--------------------------------------
// Styles
//--------------------------------------
// include "../styles/metadata/FocusStyles.as"
// include "../styles/metadata/IconColorStyles.as"
// include "../styles/metadata/LeadingStyle.as"
// include "../styles/metadata/PaddingStyles.as"
// include "../styles/metadata/SkinStyles.as"
// include "../styles/metadata/TextStyles.as"
/**
* The set of BackgroundColors for drop-down list rows in an alternating
* pattern.
* Value can be an Array of two of more colors.
* If <code>undefined</code> then the rows will use the drop-down list's
* backgroundColor style.
*
* @default undefined
*/
[Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes")]
/**
* Width of the arrow button in pixels.
* @default 22
*/
[Style(name="arrowButtonWidth", type="Number", format="Length", inherit="no")]
/**
* The thickness of the border of the drop-down list, in pixels.
* This value is overridden if you define
* <code>borderThickness</code> when setting the
* <code>dropdownStyleName</code> CSSStyleDeclaration.
*
* @default 1
*/
[Style(name="borderThickness", type="Number", format="Length", inherit="no")]
/**
* The length of the transition when the drop-down list closes, in milliseconds.
* The default transition has the drop-down slide up into the ComboBox.
*
* @default 250
*/
[Style(name="closeDuration", type="Number", format="Time", inherit="no")]
/**
* An easing function to control the close transition. Easing functions can
* be used to control the acceleration and deceleration of the transition.
*
* @default undefined
*/
[Style(name="closeEasingFunction", type="Function", inherit="no")]
/**
* The color of the border of the ComboBox. If <code>undefined</code>
* the drop-down list will use its normal borderColor style. This style
* is used by the validators to show the ComboBox in an error state.
*
* @default undefined
*/
[Style(name="dropdownBorderColor", type="uint", format="Color", inherit="yes")]
/**
* The name of a CSSStyleDeclaration to be used by the drop-down list. This
* allows you to control the appearance of the drop-down list or its item
* renderers.
*
* [deprecated]
*
* @default "comboDropDown"
*/
[Style(name="dropDownStyleName", type="String", inherit="no", deprecatedReplacement="dropdownStyleName")]
/**
* The name of a CSSStyleDeclaration to be used by the drop-down list. This
* allows you to control the appearance of the drop-down list or its item
* renderers.
*
* @default "comboDropdown"
*/
[Style(name="dropdownStyleName", type="String", inherit="no")]
/**
* Length of the transition when the drop-down list opens, in milliseconds.
* The default transition has the drop-down slide down from the ComboBox.
*
* @default 250
*/
[Style(name="openDuration", type="Number", format="Time", inherit="no")]
/**
* An easing function to control the open transition. Easing functions can
* be used to control the acceleration and deceleration of the transition.
*
* @default undefined
*/
[Style(name="openEasingFunction", type="Function", inherit="no")]
/**
* Number of pixels between the control's bottom border
* and the bottom of its content area.
* When the <code>editable</code> property is <code>true</code>,
* <code>paddingTop</code> and <code>paddingBottom</code> affect the size
* of the ComboBox control, but do not affect the position of the editable text field.
*
* @default 0
*/
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
/**
* Number of pixels between the control's top border
* and the top of its content area.
* When the <code>editable</code> property is <code>true</code>,
* <code>paddingTop</code> and <code>paddingBottom</code> affect the size
* of the ComboBox control, but do not affect the position of the editable text field.
*
* @default 0
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
/**
* The rollOverColor of the drop-down list.
* @see mx.controls.List
*/
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The selectionColor of the drop-down list.
* @see mx.controls.List
*/
[Style(name="selectionColor", type="uint", format="Color", inherit="yes")]
/**
* The selectionDuration of the drop-down list.
*
* @default 250
*
* @see mx.controls.List
*/
[Style(name="selectionDuration", type="uint", format="Time", inherit="no")]
/**
* The selectionEasingFunction of the drop-down list.
*
* @default undefined
*
* @see mx.controls.List
*/
[Style(name="selectionEasingFunction", type="Function", inherit="no")]
/**
* The textRollOverColor of the drop-down list.
*
* @default #2B333C
*
* @see mx.controls.List
*/
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The textSelectedColor of the drop-down list.
*
* @default #2B333C
* @see mx.controls.List
*/
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]
/**
* Specifies the alpha transparency value of the focus skin.
*
* @default 0.4
*/
[Style(name="focusAlpha", type="Number", inherit="no")]
/**
* Specifies which corners of the focus rectangle should be rounded.
* This value is a space-separated String that can contain any
* combination of <code>"tl"</code>, <code>"tr"</code>, <code>"bl"</code>
* and <code>"br"</code>.
* For example, to specify that the right side corners should be rounded,
* but the left side corners should be square, use <code>"tr br"</code>.
* The <code>cornerRadius</code> style property specifies
* the radius of the rounded corners.
* The default value depends on the component class; if not overridden for
* the class, default value is <code>"tl tr bl br"</code>.
*/
[Style(name="focusRoundedCorners", type="String", inherit="no")]
/**
* The color for the icon in a skin.
* For example, this style is used by the CheckBoxIcon skin class
* to draw the check mark for a CheckBox control,
* by the ComboBoxSkin class to draw the down arrow of the ComboBox control,
* and by the DateChooserMonthArrowSkin skin class to draw the month arrow
* for the DateChooser control.
*
* The default value depends on the component class;
* if it is not overridden by the class, the default value is <code>0x111111</code>.
*/
[Style(name="iconColor", type="uint", format="Color", inherit="yes")]
/**
* The color for the icon in a disabled skin.
* For example, this style is used by the CheckBoxIcon skin class
* to draw the check mark for a disabled CheckBox control,
* by the ComboBoxSkin class to draw the down arrow of a disabled ComboBox control,
* and by the DateChooserMonthArrowSkin skin class to draw the month arrow
* for a disabled DateChooser control.
*
* The default value depends on the component class;
* if it is not overridden by the class, the default value is <code>0x999999</code>.
*/
[Style(name="disabledIconColor", type="uint", format="Color", inherit="yes")]
/**
* Additional vertical space between lines of text.
*
* <p>The default value is 2.</p>
* <p>The default value for the ComboBox control is 0.</p>
*/
[Style(name="leading", type="Number", format="Length", inherit="yes")]
/**
* Number of pixels between the component's left border
* and the left edge of its content area.
* <p>The default value is 0.</p>
* <p>The default value for a Button control is 10.</p>
* <p>The default value for the ComboBox control is 5.</p>
* <p>The default value for the Form container is 16.</p>
* <p>The default value for the Tree control is 2.</p>
*/
[Style(name="paddingLeft", type="Number", format="Length", inherit="no")]
/**
* Number of pixels between the component's right border
* and the right edge of its content area.
* <p>The default value is 0.</p>
* <p>The default value for a Button control is 10.</p>
* <p>The default value for the ComboBox control is 5.</p>
* <p>The default value for the Form container is 16.</p>
*/
[Style(name="paddingRight", type="Number", format="Length", inherit="no")]
/**
* Color of the border.
* The following controls support this style: Button, CheckBox,
* ComboBox, MenuBar,
* NumericStepper, ProgressBar, RadioButton, ScrollBar, Slider, and any
* components that support the <code>borderStyle</code> style.
* The default value depends on the component class;
* if not overriden for the class, the default value is <code>0xB7BABC</code>.
*/
[Style(name="borderColor", type="uint", format="Color", inherit="no")]
/**
* Radius of component corners.
* The following components support this style: Alert, Button, ComboBox,
* LinkButton, MenuBar, NumericStepper, Panel, ScrollBar, Tab, TitleWindow,
* and any component
* that supports a <code>borderStyle</code> property set to <code>"solid"</code>.
* The default value depends on the component class;
* if not overriden for the class, the default value is <code>0</code>.
*/
[Style(name="cornerRadius", type="Number", format="Length", inherit="no")]
/**
* Alphas used for the background fill of controls. Use [1, 1] to make the control background
* opaque.
*
* @default [ 0.6, 0.4 ]
*/
[Style(name="fillAlphas", type="Array", arrayType="Number", inherit="no")]
/**
* Colors used to tint the background of the control.
* Pass the same color for both values for a flat-looking control.
*
* @default [ 0xFFFFFF, 0xCCCCCC ]
*/
[Style(name="fillColors", type="Array", arrayType="uint", format="Color", inherit="no")]
/**
* Alpha transparencies used for the highlight fill of controls.
* The first value specifies the transparency of the top of the highlight and the second value specifies the transparency
* of the bottom of the highlight. The highlight covers the top half of the skin.
*
* @default [ 0.3, 0.0 ]
*/
[Style(name="highlightAlphas", type="Array", arrayType="Number", inherit="no")]
/**
* Color of text in the component, including the component label.
*
* @default 0x0B333C
*/
[Style(name="color", type="uint", format="Color", inherit="yes")]
/**
* Color of text in the component if it is disabled.
*
* @default 0xAAB3B3
*/
[Style(name="disabledColor", type="uint", format="Color", inherit="yes")]
/**
* Sets the <code>antiAliasType</code> property of internal TextFields. The possible values are
* <code>"normal"</code> (<code>flash.text.AntiAliasType.NORMAL</code>)
* and <code>"advanced"</code> (<code>flash.text.AntiAliasType.ADVANCED</code>).
*
* <p>The default value is <code>"advanced"</code>, which enables advanced anti-aliasing for the font.
* Set to <code>"normal"</code> to disable the advanced anti-aliasing.</p>
*
* <p>This style has no effect for system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you cannot apply it to some characters and not others.</p>
* @default "advanced"
*
* @see flash.text.TextField
* @see flash.text.AntiAliasType
*/
[Style(name="fontAntiAliasType", type="String", enumeration="normal,advanced", inherit="yes")]
/**
* Name of the font to use.
* Unlike in a full CSS implementation,
* comma-separated lists are not supported.
* You can use any font family name.
* If you specify a generic font name,
* it is converted to an appropriate device font.
*
* @default "Verdana"
*/
[Style(name="fontFamily", type="String", inherit="yes")]
/**
* Sets the <code>gridFitType</code> property of internal TextFields that represent text in Flex controls.
* The possible values are <code>"none"</code> (<code>flash.text.GridFitType.NONE</code>),
* <code>"pixel"</code> (<code>flash.text.GridFitType.PIXEL</code>),
* and <code>"subpixel"</code> (<code>flash.text.GridFitType.SUBPIXEL</code>).
*
* <p>This property only applies when you are using an embedded font
* and the <code>fontAntiAliasType</code> property
* is set to <code>"advanced"</code>.</p>
*
* <p>This style has no effect for system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you can't apply it to some characters and not others.</p>
*
* @default "pixel"
*
* @see flash.text.TextField
* @see flash.text.GridFitType
*/
[Style(name="fontGridFitType", type="String", enumeration="none,pixel,subpixel", inherit="yes")]
/**
* Sets the <code>sharpness</code> property of internal TextFields that represent text in Flex controls.
* This property specifies the sharpness of the glyph edges. The possible values are Numbers
* from -400 through 400.
*
* <p>This property only applies when you are using an embedded font
* and the <code>fontAntiAliasType</code> property
* is set to <code>"advanced"</code>.</p>
*
* <p>This style has no effect for system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you can't apply it to some characters and not others.</p>
*
* @default 0
*
* @see flash.text.TextField
*/
[Style(name="fontSharpness", type="Number", inherit="yes")]
/**
* Height of the text, in pixels.
*
* The default value is 10 for all controls except the ColorPicker control.
* For the ColorPicker control, the default value is 11.
*/
[Style(name="fontSize", type="Number", format="Length", inherit="yes")]
/**
* Determines whether the text is italic font.
* Recognized values are <code>"normal"</code> and <code>"italic"</code>.
*
* @default "normal"
*/
[Style(name="fontStyle", type="String", enumeration="normal,italic", inherit="yes")]
/**
* Sets the <code>thickness</code> property of internal TextFields that represent text in Flex controls.
* This property specifies the thickness of the glyph edges.
* The possible values are Numbers from -200 to 200.
*
* <p>This property only applies when you are using an embedded font
* and the <code>fontAntiAliasType</code> property
* is set to <code>"advanced"</code>.</p>
*
* <p>This style has no effect on system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you can't apply it to some characters and not others.</p>
*
* @default 0
*
* @see flash.text.TextField
*/
[Style(name="fontThickness", type="Number", inherit="yes")]
/**
* Determines whether the text is boldface.
* Recognized values are <code>normal</code> and <code>bold</code>.
* The default value for Button controls is <code>bold</code>.
* The default value for all other controls is <code>normal</code>.
*/
[Style(name="fontWeight", type="String", enumeration="normal,bold", inherit="yes")]
/**
* A Boolean value that indicates whether kerning
* is enabled (<code>true</code>) or disabled (<code>false</code>).
* Kerning adjusts the gap between certain character pairs
* to improve readability, and should be used only when necessary,
* such as with headings in large fonts.
* Kerning is supported for embedded fonts only.
* Certain fonts, such as Verdana, and monospaced fonts,
* such as Courier New, do not support kerning.
*
* @default false
*/
[Style(name="kerning", type="Boolean", inherit="yes")]
/**
* The number of additional pixels to appear between each character.
* A positive value increases the character spacing beyond the normal spacing,
* while a negative value decreases it.
*
* @default 0
*/
[Style(name="letterSpacing", type="Number", inherit="yes")]
/**
* Alignment of text within a container.
* Possible values are <code>"left"</code>, <code>"right"</code>,
* or <code>"center"</code>.
*
* <p>The default value for most components is <code>"left"</code>.
* For the FormItem component,
* the default value is <code>"right"</code>.
* For the Button, LinkButton, and AccordionHeader components,
* the default value is <code>"center"</code>, and
* this property is only recognized when the
* <code>labelPlacement</code> property is set to <code>"left"</code> or
* <code>"right"</code>.
* If <code>labelPlacement</code> is set to <code>"top"</code> or
* <code>"bottom"</code>, the text and any icon are centered.</p>
*/
[Style(name="textAlign", type="String", enumeration="left,center,right", inherit="yes")]
/**
* Determines whether the text is underlined.
* Possible values are <code>"none"</code> and <code>"underline"</code>.
*
* @default "none"
*/
[Style(name="textDecoration", type="String", enumeration="none,underline", inherit="yes")]
/**
* Offset of first line of text from the left side of the container, in pixels.
*
* @default 0
*/
[Style(name="textIndent", type="Number", format="Length", inherit="yes")]
/**
* The name of a CSS style declaration for controlling other aspects of
* the appearance of the column headers.
* @default "dataGridStyles"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="matchedTextStyleName", type="String", inherit="no")]
//--------------------------------------
// Other metadata
//--------------------------------------
[AccessibilityClass(implementation="mx.accessibility.ComboBoxAccImpl")]
[DataBindingInfo("acceptedTypes", "{ dataProvider: { label: "String" } }")]
[DefaultBindingProperty(source="selectedItem", destination="dataProvider")]
[DefaultProperty("dataProvider")]
[DefaultTriggerEvent("change")]
[IconFile("FilterComboBox.png")]
/**
* The ComboBox control contains a drop-down list
* from which the user can select a single value.
* Its functionality is very similar to that of the
* SELECT form element in HTML.
* The ComboBox can be editable, in which case
* the user can type entries into the TextInput portion
* of the ComboBox that are not in the list.
*
* <p>The ComboBox control has the following default sizing
* characteristics:</p>
* <table class="innertable">
* <tr>
* <th>Characteristic</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>Default size</td>
* <td>Wide enough to accommodate the longest entry in the
* drop-down list in the display area of the main
* control, plus the drop-down button. When the
* drop-down list is not visible, the default height
* is based on the label text size.
*
* <p>The default drop-down list height is five rows, or
* the number of entries in the drop-down list, whichever
* is smaller. The default height of each entry in the
* drop-down list is 22 pixels.</p></td>
* </tr>
* <tr>
* <td>Minimum size</td>
* <td>0 pixels.</td>
* </tr>
* <tr>
* <td>Maximum size</td>
* <td>5000 by 5000.</td>
* </tr>
* <tr>
* <td>dropdownWidth</td>
* <td>The width of the ComboBox control.</td>
* </tr>
* <tr>
* <td>rowCount</td>
* <td>5 rows.</td>
* </tr>
* </table>
*
* @mxml
*
* <p>The <code><mx:ComboBox></code> tag inherits all the tag attributes
* of its superclass, and adds the following tag attributes:</p>
*
* <pre>
* <mx:ComboBox
* <b>Properties</b>
* dataProvider="null"
* dropdownFactory="<i>ClassFactory that creates an mx.controls.List</i>"
* dropdownWidth="<i>100 or width of the longest text in the dataProvider</i>"
* itemRenderer="null"
* labelField="label"
* labelFunction="null"
* prompt="null"
* rowCount="5"
* selectedIndex="-1"
* selectedItem="null"
*
* <b>Styles</b>
* alternatingItemColors="undefined"
* arrowButtonWidth="22"
* borderColor="0xB7BABC"
* borderThickness="1"
* closeDuration="250"
* closeEasingFunction="undefined"
* color="0x0B333C"
* cornerRadius="0"
* disabledColor="0xAAB3B3"
* disabledIconColor="0x919999"
* dropdownBorderColor="undefined"
* dropdownStyleName="comboDropdown"
* fillAlphas="[0.6,0.4]"
* fillColors="[0xFFFFFF, 0xCCCCCC]"
* focusAlpha="0.4"
* focusRoundedCorners="tl tr bl br"
* fontAntiAliasType="advanced|normal"
* fontFamily="Verdana"
* fontGridFitType="pixel|none|subpixel"
* fontSharpness="0"
* fontSize="10"
* fontStyle="normal|italic"
* fontThickness="0"
* fontWeight="normal|bold"
* highlightAlphas="[0.3,0.0]"
* iconColor="0x111111"
* leading="0"
* openDuration="250"
* openEasingFunction="undefined"
* paddingTop="0"
* paddingBottom="0"
* paddingLeft="5"
* paddingRight="5"
* rollOverColor="<i>Depends on theme color"</i>
* selectionColor="<i>Depends on theme color"</i>
* selectionDuration="250"
* selectionEasingFunction="undefined"
* textAlign="left|center|right"
* textDecoration="none|underline"
* textIndent="0"
* textRollOverColor="0x2B333C"
* textSelectedColor="0x2B333C"
*
* <b>Events</b>
* change="<i>No default</i>"
* close="<i>No default</i>"
* dataChange="<i>No default</i>"
* enter="<i>No default</i>"
* itemRollOut="<i>No default</i>"
* itemRollOver="<i>No default</i>"
* open="<i>No default</i>"
* scroll="<i>No default</i>"
* />
* </pre>
*
* @includeExample examples/SimpleComboBox.mxml
*
* @see mx.controls.List
* @see mx.effects.Tween
* @see mx.managers.PopUpManager
*
*/
public class FilterComboBox extends ComboBase
implements IDataRenderer, IDropInListItemRenderer,
IListItemRenderer
{
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by ComboBoxAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*/
public function FilterComboBox()
{
super();
prompt = "Type to filter";
// It it better to start out with an empty data provider rather than
// an undefined one. Otherwise, code in getDropdown() sets it to []
// later, but via setDataProvider(). This API has side effects like
// setting selectionChanged, which causes the text in an editable
// ComboBox to be lost.
dataProvider = new ArrayCollection();
filterString = "";
_activeFilterFunction = filterDataFunction;
useFullDropdownSkin = true;
wrapDownArrowButton = false;
addEventListener("unload", unloadHandler);
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* A reference to the internal List that pops up to display a row
* for each dataProvider item.
*/
private var _dropdown:ListBase;
/**
* @private
* A int to track the oldIndex, used when the dropdown is dismissed using the ESC key.
*/
private var _oldIndex:int;
/**
* @private
* The tween used for showing and hiding the drop-down list.
*/
private var tween:Tween = null;
/**
* @private
* A flag to track whether the dropDown tweened up or down.
*/
private var tweenUp:Boolean = false;
/**
* @private
*/
private var preferredDropdownWidth:Number;
/**
* @private
*/
private var dropdownBorderStyle:String = "solid";
/**
* @private
* Is the dropdown list currently shown?
*/
private var _showingDropdown:Boolean = false;
/**
* @private
*/
private var _selectedIndexOnDropdown:int = -1;
/**
* @private
*/
private var bRemoveDropdown:Boolean = false;
/**
* @private
*/
private var inTween:Boolean = false;
/**
* @private
*/
private var bInKeyDown:Boolean = false;
/**
* @private
* Flag that will block default data/listData behavior
*/
private var selectedItemSet:Boolean;
/**
* @private
* Event that is causing the dropDown to open or close.
*/
private var triggerEvent:Event;
/**
* @private
* Whether the text property was explicitly set or not
*/
private var explicitText:Boolean;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property.
*/
private var _data:Object;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* The <code>data</code> property lets you pass a value
* to the component when you use it in an item renderer or item editor.
* You typically use data binding to bind a field of the <code>data</code>
* property to a property of this component.
*
* <p>The ComboBox control uses the <code>listData</code> property and the
* <code>data</code> property as follows. If the ComboBox is in a
* DataGrid control, it expects the <code>dataField</code> property of the
* column to map to a property in the data and sets
* <code>selectedItem</code> to that property. If the ComboBox control is
* in a List control, it expects the <code>labelField</code> of the list
* to map to a property in the data and sets <code>selectedItem</code> to
* that property.
* Otherwise, it sets <code>selectedItem</code> to the data itself.</p>
*
* <p>You do not set this property in MXML.</p>
*
* @see mx.core.IDataRenderer
*/
public function get data():Object
{
return _data;
}
/**
* @private
*/
public function set data(value:Object):void
{
var newSelectedItem:*;
_data = value;
if (_listData && _listData is DataGridListData)
newSelectedItem = _data[DataGridListData(_listData).dataField];
else if (_listData is ListData && ListData(_listData).labelField in _data)
newSelectedItem = _data[ListData(_listData).labelField];
else
newSelectedItem = _data;
if (newSelectedItem !== undefined && !selectedItemSet)
{
selectedItem = newSelectedItem;
selectedItemSet = false;
}
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
//----------------------------------
// listData
//----------------------------------
/**
* @private
* Storage for the listData property.
*/
private var _listData:BaseListData;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* When a component is used as a drop-in item renderer or drop-in item
* editor, Flex initializes the <code>listData</code> property of the
* component with the appropriate data from the List control. The
* component can then use the <code>listData</code> property and the
* <code>data</code> property to display the appropriate information
* as a drop-in item renderer or drop-in item editor.
*
* <p>You do not set this property in MXML or ActionScript; Flex sets it
* when the component
* is used as a drop-in item renderer or drop-in item editor.</p>
*
* @see mx.controls.listClasses.IDropInListItemRenderer
*/
public function get listData():BaseListData
{
return _listData;
}
/**
* @private
*/
public function set listData(value:BaseListData):void
{
_listData = value;
}
//----------------------------------
// dataProvider
//----------------------------------
/**
* @private
*/
private var collectionChanged:Boolean = false;
[Bindable("collectionChange")]
[Inspectable(category="Data", arrayType="Object")]
/**
* @inheritDoc
*/
override public function set dataProvider(value:Object):void
{
selectionChanged = true;
super.dataProvider = value;
destroyDropdown();
_showingDropdown = false;
invalidateProperties();
invalidateSize();
}
//----------------------------------
// itemRenderer
//----------------------------------
/**
* @private
* Storage for itemRenderer property.
*/
private var _itemRenderer:IFactory;
[Inspectable(category="Data")]
/**
* IFactory that generates the instances that displays the data for the
* drop-down list of the control. You can use this property to specify
* a custom item renderer for the drop-down list.
*
* <p>The control uses a List control internally to create the drop-down
* list.
* The default item renderer for the List control is the ListItemRenderer
* class, which draws the text associated with each item in the list,
* and an optional icon. </p>
*
* @see mx.controls.List
* @see mx.controls.listClasses.ListItemRenderer
*/
public function get itemRenderer():IFactory
{
return _itemRenderer;
}
/**
* @private
*/
public function set itemRenderer(value:IFactory):void
{
_itemRenderer = value;
if (_dropdown)
_dropdown.itemRenderer = value;
invalidateSize();
invalidateDisplayList();
dispatchEvent(new Event("itemRendererChanged"));
}
//----------------------------------
// selectedIndex
//----------------------------------
[Bindable("change")]
[Bindable("collectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="0")]
/**
* Index of the selected item in the drop-down list.
* Setting this property sets the current index and displays
* the associated label in the TextInput portion.
* <p>The default value is -1, but it set to 0
* when a <code>dataProvider</code> is assigned, unless there is a prompt.
* If the control is editable, and the user types in the TextInput portion,
* the value of the <code>selectedIndex</code> property becomes
* -1. If the value of the <code>selectedIndex</code>
* property is out of range, the <code>selectedIndex</code> property is set to the last
* item in the <code>dataProvider</code>.</p>
*/
override public function set selectedIndex(value:int):void
{
super.selectedIndex = value;
if (value >= 0)
selectionChanged = true;
implicitSelectedIndex = false;
invalidateDisplayList();
// value committed event needs the text to be set
if( textInput && !textChanged && value >= 0 )
{
textInput.text = selectedLabel;
filterString = ( _removeFilterOnSelection ) ? "" : selectedLabel;
}
else if( textInput && prompt && _removeInputOnFocusOut )
{
textInput.text = prompt;
filterString = "";
}
// [Matt] setting the text of the textInput should take care of this now
// Send a valueCommit event, which is used by the data model
//dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//----------------------------------
// selectedItem
//----------------------------------
[Bindable("change")]
[Bindable("collectionChange")]
[Bindable("valueCommit")]
[Inspectable(environment="none")]
/**
* Contains a reference to the selected item in the
* <code>dataProvider</code>.
* If the data is an object or class instance, modifying
* properties in the object or instance modifies the <code>dataProvider</code>
* and thus its views. Setting the selectedItem itself causes the
* ComboBox to select that item (display it in the TextInput portion and set
* the selectedIndex) if it exists in the dataProvider.
* <p>If the ComboBox control is editable, the <code>selectedItem</code>
* property is <code>null</code> if the user types any text
* into the TextInput.
* It has a value only if the user selects an item from the drop-down
* list, or if it is set programmatically.</p>
*/
override public function set selectedItem(value:Object):void
{
selectedItemSet = true;
// We do not want to apply an implicit default index in this case.
implicitSelectedIndex = false;
super.selectedItem = value;
// value committed event needs the text to be set
if( textInput && !textChanged && selectedItem )
{
textInput.text = selectedLabel;
filterString = ( _removeFilterOnSelection ) ? "" : selectedLabel;
}
else if( textInput && prompt && _removeInputOnFocusOut )
{
textInput.text = prompt;
filterString = "";
}
}
//----------------------------------
// showInAutomationHierarchy
//----------------------------------
/**
* @private
*/
override public function set showInAutomationHierarchy(value:Boolean):void
{
//do not allow value changes
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// dropdown
//----------------------------------
/**
* A reference to the List control that acts as the drop-down in the ComboBox.
*/
public function get dropdown():ListBase
{
return getDropdown();
}
//----------------------------------
// dropdownFactory
//----------------------------------
/**
* @private
* Storage for the dropdownFactory property.
*/
private var _dropdownFactory:IFactory = new ClassFactory( List );
[Bindable("dropdownFactoryChanged")]
/**
* The IFactory that creates a ListBase-derived instance to use
* as the drop-down.
* The default value is an IFactory for List
*
*/
public function get dropdownFactory():IFactory
{
return _dropdownFactory;
}
/**
* @private
*/
public function set dropdownFactory(value:IFactory):void
{
_dropdownFactory = value;
dispatchEvent(new Event("dropdownFactoryChanged"));
}
//----------------------------------
// dropDownStyleFilters
//----------------------------------
/**
* The set of styles to pass from the ComboBox to the dropDown.
* Styles in the dropDownStyleName style will override these styles.
* @see mx.styles.StyleProxy
* @review
*/
protected function get dropDownStyleFilters():Object
{
return null;
}
//----------------------------------
// dropdownWidth
//----------------------------------
/**
* @private
* Storage for the dropdownWidth property.
*/
private var _dropdownWidth:Number = 100;
[Bindable("dropdownWidthChanged")]
[Inspectable(category="Size", defaultValue="100")]
/**
* Width of the drop-down list, in pixels.
* <p>The default value is 100 or the width of the longest text
* in the <code>dataProvider</code>, whichever is greater.</p>
*
*/
public function get dropdownWidth():Number
{
return _dropdownWidth;
}
/**
* @private
*/
public function set dropdownWidth(value:Number):void
{
_dropdownWidth = value;
preferredDropdownWidth = value;
if (_dropdown)
_dropdown.setActualSize(value, _dropdown.height);
dispatchEvent(new Event("dropdownWidthChanged"));
}
//----------------------------------
// labelField
//----------------------------------
/**
* @private
* Storage for the labelField property.
*/
private var _labelField:String = "label";
/**
* @private
*/
private var labelFieldChanged:Boolean;
[Bindable("labelFieldChanged")]
[Inspectable(category="Data", defaultValue="label")]
/**
* Name of the field in the items in the <code>dataProvider</code>
* Array to display as the label in the TextInput portion and drop-down list.
* By default, the control uses a property named <code>label</code>
* on each Array object and displays it.
* <p>However, if the <code>dataProvider</code> items do not contain
* a <code>label</code> property, you can set the <code>labelField</code>
* property to use a different property.</p>
*
*/
public function get labelField():String
{
return _labelField;
}
/**
* @private
*/
public function set labelField(value:String):void
{
_labelField = value;
labelFieldChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("labelFieldChanged"));
}
//----------------------------------
// labelFunction
//----------------------------------
/**
* @private
* Storage for the labelFunction property.
*/
private var _labelFunction:Function;
/**
* @private
*/
private var labelFunctionChanged:Boolean;
[Bindable("labelFunctionChanged")]
[Inspectable(category="Data")]
/**
* User-supplied function to run on each item to determine its label.
* By default the control uses a property named <code>label</code>
* on each <code>dataProvider</code> item to determine its label.
* However, some data sets do not have a <code>label</code> property,
* or do not have another property that can be used for displaying
* as a label.
* <p>An example is a data set that has <code>lastName</code> and
* <code>firstName</code> fields but you want to display full names.
* You use <code>labelFunction</code> to specify a callback function
* that uses the appropriate fields and return a displayable String.</p>
*
* <p>The labelFunction takes a single argument which is the item
* in the dataProvider and returns a String:</p>
* <pre>
* myLabelFunction(item:Object):String
* </pre>
*
*/
public function get labelFunction():Function
{
return _labelFunction;
}
/**
* @private
*/
public function set labelFunction(value:Function):void
{
_labelFunction = value;
labelFunctionChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("labelFunctionChanged"));
}
//----------------------------------
// prompt
//----------------------------------
private var promptChanged:Boolean = false;
/**
* @private
* Storage for the prompt property.
*/
private var _prompt:String;
[Inspectable(category="General")]
/**
* The prompt for the ComboBox control. A prompt is
* a String that is displayed in the TextInput portion of the
* ComboBox when <code>selectedIndex</code> = -1. It is usually
* a String like "Select one...". If there is no
* prompt, the ComboBox control sets <code>selectedIndex</code> to 0
* and displays the first item in the <code>dataProvider</code>.
*/
public function get prompt():String
{
return _prompt;
}
/**
* @private
*/
public function set prompt(value:String):void
{
_prompt = value;
promptChanged = true;
invalidateProperties();
}
//----------------------------------
// rowCount
//----------------------------------
/**
* @private
* Storage for the rowCount property.
*/
private var _rowCount:int = 5;
[Bindable("resize")]
[Inspectable(category="General", defaultValue="5")]
/**
* Maximum number of rows visible in the ComboBox control list.
* If there are fewer items in the
* dataProvider, the ComboBox shows only as many items as
* there are in the dataProvider.
*
* @default 5
*/
public function get rowCount():int
{
return Math.max(1, Math.min(_filteredCollection.length, _rowCount));
}
/**
* @private
*/
public function set rowCount(value:int):void
{
_rowCount = value;
if (_dropdown)
_dropdown.rowCount = value;
}
//----------------------------------
// selectedLabel
//----------------------------------
/**
* The String displayed in the TextInput portion of the ComboBox. It
* is calculated from the data by using the <code>labelField</code>
* or <code>labelFunction</code>.
*/
public function get selectedLabel():String
{
var item:Object = selectedItem;
return itemToLabel(item);
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function initializeAccessibility():void
{
if (FilterComboBox.createAccessibilityImplementation != null)
FilterComboBox.createAccessibilityImplementation(this);
}
/**
* @private
*/
override public function styleChanged( styleProp:String ):void
{
destroyDropdown();
super.styleChanged( styleProp );
if( !styleProp || styleProp == "styleName" || styleProp == "matchedTextStyleName" )
{
var style:CSSStyleDeclaration = StyleManager.getStyleManager( moduleFactory ).getStyleDeclaration( "." + getStyle( "matchedTextStyleName" ) );
if( style )
{
_highlightTextFormat = new TextFormat();
if( !isNaN( style.getStyle( "color" ) ) ) _highlightTextFormat.color = style.getStyle( "color" )
if( style.getStyle( "fontFamily" ) ) _highlightTextFormat.font = style.getStyle( "fontFamily" );
if( style.getStyle( "fontWeight" ) ) _highlightTextFormat.bold = style.getStyle( "fontWeight" );
if( style.getStyle( "fontSize" ) ) _highlightTextFormat.size = style.getStyle( "fontSize" );
if( style.getStyle( "fontStyle" ) ) _highlightTextFormat.italic = style.getStyle( "fontStyle" );
if( style.getStyle( "textDecoration" ) ) _highlightTextFormat.underline = style.getStyle( "textDecoration" );
}
else
{
_highlightTextFormat = null;
}
}
}
/**
* Makes sure the control is at least 40 pixels wide,
* and tall enough to fit one line of text
* in the TextInput portion of the control but at least
* 22 pixels high.
*/
override protected function measure():void
{
super.measure();
// Make sure we're not too small
measuredMinWidth = Math.max(measuredWidth, DEFAULT_MEASURED_MIN_WIDTH);
// Make sure we're tall enough to hold our text.
// Text field height is text height + 4 pixels top/bottom
var textHeight:Number = measureText("M").height + 6;
var bm:EdgeMetrics = borderMetrics;
measuredMinHeight = measuredHeight =
Math.max(textHeight + bm.top + bm.bottom, DEFAULT_MEASURED_MIN_HEIGHT);
if (FlexVersion.compatibilityVersion >= FlexVersion.VERSION_3_0)
measuredMinHeight = measuredHeight += getStyle("paddingTop") + getStyle("paddingBottom");
}
/**
* @private
* Make sure the drop-down width is the same as the rest of the ComboBox
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
downArrowButton.visible = ( getStyle("arrowButtonWidth") != 0 );
// we toss the dropdown when resized
// except if we're opening the dropdown
// then we assume the updateDisplayList() call is spurious
// and will not affect the dropdown size
if (_dropdown && !inTween)
{
destroyDropdown();
}
else if (!_showingDropdown && inTween)
{
bRemoveDropdown = true;
}
var ddw:Number = preferredDropdownWidth;
if (isNaN(ddw))
ddw = _dropdownWidth = unscaledWidth;
if (labelFieldChanged)
{
if (_dropdown)
_dropdown.labelField = _labelField;
selectionChanged = true;
if (!explicitText)
textInput.text = selectedLabel;
labelFieldChanged = false;
}
if (labelFunctionChanged)
{
selectionChanged = true;
if (!explicitText)
textInput.text = selectedLabel;
labelFunctionChanged = false;
}
if (selectionChanged)
{
if (!textChanged)
{
if (selectedIndex == -1 && prompt )
{
if( _removeInputOnFocusOut ) textInput.text = prompt;
}
else if (!explicitText)
{
textInput.text = selectedLabel;
}
}
textInput.invalidateDisplayList();
textInput.validateNow();
if (editable)
{
TextInput( textInput ).getTextField().setSelection(0, textInput.text.length);
TextInput( textInput ).getTextField().scrollH = 0;
}
if (_dropdown)
_dropdown.selectedIndex = selectedIndex;
selectionChanged = false;
}
// We might need to decrease the number of rows.
if (_dropdown && _dropdown.rowCount != rowCount)
_dropdown.rowCount = rowCount;
}
/**
* @private
*/
override protected function commitProperties():void
{
explicitText = textChanged;
super.commitProperties();
if (collectionChanged)
{
if (selectedIndex == -1 && implicitSelectedIndex && _prompt == null)
selectedIndex = 0;
selectedIndexChanged = true;
collectionChanged = false;
}
if (promptChanged && prompt != null && selectedIndex == -1)
{
promptChanged = false;
textInput.text = prompt;
}
if( _filterChanged )
{
_filterChanged = false;
_filteredCollection.filterFunction = _activeFilterFunction;
_filteredCollection.refresh();
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Returns a string representing the <code>item</code> parameter.
*
* <p>This method checks in the following order to find a value to return:</p>
*
* <ol>
* <li>If you have specified a <code>labelFunction</code> property,
* returns the result of passing the item to the function.</li>
* <li>If the item is a String, Number, Boolean, or Function, returns
* the item.</li>
* <li>If the item has a property with the name specified by the control's
* <code>labelField</code> property, returns the contents of the property.</li>
* <li>If the item has a label property, returns its value.</li>
* </ol>
*
* @param item The object that contains the value to convert to a label. If the item is null, this method returns the empty string.
*/
public function itemToLabel(item:Object):String
{
// we need to check for null explicitly otherwise
// a numeric zero will not get properly converted to a string.
// (do not use !item)
if (item == null)
return "";
if (labelFunction != null)
return labelFunction(item);
if (typeof(item) == "object")
{
try
{
if (item[labelField] != null)
item = item[labelField];
}
catch(e:Error)
{
}
}
else if (typeof(item) == "xml")
{
try
{
if (item[labelField].length() != 0)
item = item[labelField];
}
catch(e:Error)
{
}
}
if (typeof(item) == "string")
return String(item);
try
{
return item.toString();
}
catch(e:Error)
{
}
return " ";
}
/**
* Displays the drop-down list.
*/
public function open():void
{
displayDropdown( true );
}
/**
* Hides the drop-down list.
*/
public function close( trigger:Event = null ):void
{
if( _showingDropdown )
{
setSelectedIndexFromDropDown();
displayDropdown( false, trigger ) ;
dispatchChangeEvent( new Event("dummy"), _selectedIndexOnDropdown, selectedIndex);
}
}
/**
* @private
*/
mx_internal function hasDropdown():Boolean
{
return _dropdown != null;
}
/**
* @private
*/
private function getDropdown():ListBase
{
if (!initialized)
return null;
if (!hasDropdown())
{
var dropDownStyleName:String = getStyle("dropDownStyleName");
if (dropDownStyleName == null )
dropDownStyleName = getStyle("dropdownStyleName");
_dropdown = dropdownFactory.newInstance();
_dropdown.visible = false;
_dropdown.focusEnabled = false;
_dropdown.owner = this;
if (itemRenderer)
_dropdown.itemRenderer = itemRenderer;
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
{
_dropdown.styleName = this;
}
if (dropDownStyleName)
{
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
{
var styleDecl:CSSStyleDeclaration =
StyleManager.getStyleManager( moduleFactory ).getStyleDeclaration("." + dropDownStyleName);
if (styleDecl)
_dropdown.styleDeclaration = styleDecl;
}
else
{
_dropdown.styleName = dropDownStyleName;
}
}
else if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
{
_dropdown.setStyle("cornerRadius", 0);
}
PopUpManager.addPopUp(_dropdown, this);
// Don't display a tween when the selection changes.
// The dropdown menu is about to appear anyway,
// and other processing can make the tween look choppy.
_dropdown.setStyle("selectionDuration", 0);
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0 && dropdownBorderStyle && dropdownBorderStyle != "")
_dropdown.setStyle("borderStyle", dropdownBorderStyle);
// Set up a data provider in case one doesn't yet exist,
// so we can share it with the dropdown listbox.
if (!dataProvider)
dataProvider = new ArrayCollection();
_dropdown.dataProvider = filteredCollection;
_dropdown.rowCount = rowCount;
_dropdown.width = _dropdownWidth;
_dropdown.selectedIndex = selectedIndex;
_oldIndex = selectedIndex;
_dropdown.verticalScrollPolicy = ScrollPolicy.AUTO;
_dropdown.labelField = _labelField;
_dropdown.labelFunction = itemToLabel;
_dropdown.allowDragSelection = true;
_dropdown.addEventListener("change", dropdown_changeHandler);
_dropdown.addEventListener( FlexEvent.VALUE_COMMIT, dropdown_valueCommitHandler);
_dropdown.addEventListener(ScrollEvent.SCROLL, dropdown_scrollHandler);
_dropdown.addEventListener(ListEvent.ITEM_ROLL_OVER, dropdown_itemRollOverHandler);
_dropdown.addEventListener(ListEvent.ITEM_ROLL_OUT, dropdown_itemRollOutHandler);
_dropdown.addEventListener( FlexEvent.UPDATE_COMPLETE, onDropdownDataChange, false, 0, true );
// the drop down should close if the user clicks on any item.
// add a handler to detect a click in the list
_dropdown.addEventListener(ListEvent.ITEM_CLICK, dropdown_itemClickHandler);
UIComponentGlobals.layoutManager.validateClient(_dropdown, true);
_dropdown.setActualSize(_dropdownWidth, _dropdown.getExplicitOrMeasuredHeight());
_dropdown.validateDisplayList();
_dropdown.cacheAsBitmap = true;
// weak reference to stage
systemManager.addEventListener(Event.RESIZE, stage_resizeHandler, false, 0, true);
}
_dropdown.scaleX = scaleX;
_dropdown.scaleY = scaleY;
return _dropdown;
}
/**
* @private
*/
private function displayDropdown(show:Boolean, trigger:Event = null):void
{
if( !enabled ) return;
if( textInput.text == prompt ) textInput.text = "";
if (!initialized || show == _showingDropdown)
return;
// Subclasses may extend to do pre-processing
// before the dropdown is displayed
// or override to implement special display behavior
// Show or hide the dropdown
var initY:Number;
var endY:Number;
var duration:Number;
var easingFunction:Function;
var point:Point = new Point(0, unscaledHeight);
point = localToGlobal(point);
var sm:ISystemManager = systemManager.topLevelSystemManager;
var sbRoot:DisplayObject = sm.getSandboxRoot();
var screen:Rectangle;
if (sm != sbRoot)
{
var request:InterManagerRequest = new InterManagerRequest(InterManagerRequest.SYSTEM_MANAGER_REQUEST,
false, false,
"getVisibleApplicationRect");
sbRoot.dispatchEvent(request);
screen = Rectangle(request.value);
}
else
screen = sm.getVisibleApplicationRect();
//opening the dropdown
if (show)
{
// Store the selectedIndex temporarily so we can tell
// if the value changed when the dropdown is closed
_selectedIndexOnDropdown = selectedIndex;
getDropdown();
_dropdown.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.addEventListener(FlexMouseEvent.MOUSE_WHEEL_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.addEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, dropdown_mouseOutsideHandler);
_dropdown.addEventListener(SandboxMouseEvent.MOUSE_WHEEL_SOMEWHERE, dropdown_mouseOutsideHandler);
if (_dropdown.parent == null) // was popped up then closed
PopUpManager.addPopUp(_dropdown, this);
else
PopUpManager.bringToFront(_dropdown);
// if we donot have enough space in the bottom display the dropdown
// at the top. But if the space there is also less than required
// display it below.
if (point.y + _dropdown.height > screen.bottom &&
point.y > screen.top + _dropdown.height)
{
// Dropdown will go below the bottom of the stage
// and be clipped. Instead, have it grow up.
point.y -= (unscaledHeight + _dropdown.height);
initY = -_dropdown.height;
tweenUp = true;
}
else
{
initY = _dropdown.height;
tweenUp = false;
}
point = _dropdown.parent.globalToLocal(point);
var sel:int = _dropdown.selectedIndex;
if (sel == -1)
sel = 0;
var pos:Number = _dropdown.verticalScrollPosition;
// try to set the verticalScrollPosition one above the selected index so
// it looks better when the dropdown is displayed
pos = sel - 1;
pos = Math.min(Math.max(pos, 0), _dropdown.maxVerticalScrollPosition);
_dropdown.verticalScrollPosition = pos;
if (_dropdown.x != point.x || _dropdown.y != point.y)
_dropdown.move(point.x, point.y);
if (!_dropdown.visible)
_dropdown.visible = true;
// Make sure we don't remove the dropdown at the end of the tween
bRemoveDropdown = false;
// Set up the tween and relevant variables.
_showingDropdown = show;
duration = getStyle("openDuration");
endY = 0;
easingFunction = getStyle("openEasingFunction") as Function;
}
// closing the dropdown
else if (_dropdown)
{
// Set up the tween and relevant variables.
endY = (point.y + _dropdown.height > screen.bottom || tweenUp
? -_dropdown.height
: _dropdown.height);
_showingDropdown = show;
initY = 0;
duration = getStyle("closeDuration");
easingFunction = getStyle("closeEasingFunction") as Function;
_dropdown.resetDragScrolling();
}
inTween = true;
UIComponentGlobals.layoutManager.validateNow();
// Block all layout, responses from web service, and other background
// processing until the tween finishes executing.
UIComponent.suspendBackgroundProcessing();
// Disable the dropdown during the tween.
if (_dropdown)
_dropdown.enabled = false;
duration = Math.max( 1, duration );
tween = new Tween( this, initY, endY, duration );
if( _showingDropdown && duration > 80 )
{
_dropdown.scrollRect = new Rectangle( 0, initY, _dropdown.width, _dropdown.height );
}
if (easingFunction != null && tween)
tween.easingFunction = easingFunction;
triggerEvent = trigger;
}
/**
* @private
*/
private function dispatchChangeEvent(oldEvent:Event, prevValue:int,
newValue:int):void
{
if (prevValue != newValue)
{
var newEvent:Event = oldEvent is ListEvent ?
oldEvent :
new ListEvent("change");
dispatchEvent(newEvent);
}
}
/**
* @private
*/
private function destroyDropdown():void
{
if (_dropdown && !_showingDropdown)
{
if (inTween)
{
tween.endTween();
}
else
{
PopUpManager.removePopUp(_dropdown);
_dropdown = null;
}
}
}
//--------------------------------------------------------------------------
//
// Overridden event handlers
//
//--------------------------------------------------------------------------
private var implicitSelectedIndex:Boolean = false;
/**
* @private
*/
override protected function collectionChangeHandler(event:Event):void
{
// Save a copy of the selectedIndex
var curSelectedIndex:int = selectedIndex;
super.collectionChangeHandler(event);
if (event is CollectionEvent)
{
var ce:CollectionEvent = CollectionEvent(event);
if (collection.length == 0)
{
// Special case: Empty dataProvider.
if (!selectedIndexChanged && !selectedItemChanged)
{
if (super.selectedIndex != -1)
super.selectedIndex = -1;
implicitSelectedIndex = true;
invalidateDisplayList();
}
// if the combobox is non-editable remove the text
// we don't want to remove the text if it is editable as user might
// have typed something.
if (textInput && !editable)
textInput.text = "";
}
else if (ce.kind == CollectionEventKind.ADD)
{
if (collection.length == ce.items.length)
{
// Special case: Adding the first item(s). Select item 0
// if there is no prompt
if (selectedIndex == -1 && _prompt == null)
selectedIndex = 0;
}
else
{
// we dont want to destroy the dropdown just
// because data got added. Especially true
// for paged data.
return;
}
}
else if (ce.kind == CollectionEventKind.UPDATE)
{
if (ce.location == selectedIndex ||
ce.items[0].source == selectedItem)
// unsorted lists don't have a valid location
// Force an update of the text input
selectionChanged = true;
}
else if (ce.kind == CollectionEventKind.REPLACE)
{
// bail on a replace, no need to change anything,
// especially for paged data
return;
}
else if (ce.kind == CollectionEventKind.RESET)
{
collectionChanged = true;
if (!selectedIndexChanged && !selectedItemChanged)
selectedIndex = prompt ? -1 : 0;
invalidateProperties();
}
_filteredCollection = new ListCollectionView( ListCollectionView( collection ) );
_filteredCollection.filterFunction = _activeFilterFunction;
_filteredCollection.refresh();
invalidateDisplayList();
destroyDropdown();
_showingDropdown = false;
}
}
private function popup_moveHandler(event:Event):void
{
destroyDropdown();
}
/**
* @private
*/
override protected function textInput_changeHandler(event:Event):void
{
super.textInput_changeHandler(event);
if( !isShowingDropdown )
{
open();
}
else
{
dropdown.selectedIndex = -1;
}
// if( dropdown.selectedIndex != -1 ) dropdown.selectedIndex = -1;
// _textInputChange = true;
filterString = ( _caseSensitive ) ? textInput.text : textInput.text.toLowerCase();
// _filteredCollection.refresh();
//
// if( !isShowingDropdown ) open();
// Force a change event to be dispatched
dispatchChangeEvent(event, -1, -2);
}
/**
* @private
*/
override protected function downArrowButton_buttonDownHandler(
event:FlexEvent):void
{
// The down arrow should always toggle the visibility of the dropdown.
if (_showingDropdown)
{
close(event);
}
else
{
displayDropdown(true, event);
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function dropdown_mouseOutsideHandler(event:Event):void
{
if (event is MouseEvent)
{
var mouseEvent:MouseEvent = MouseEvent(event);
if (mouseEvent.target != _dropdown)
// the dropdown's items can dispatch a mouseDownOutside
// event which then bubbles up to us
return;
if (!hitTestPoint(mouseEvent.stageX, mouseEvent.stageY, true))
{
close(event);
}
}
else if (event is SandboxMouseEvent)
{
close(event);
}
}
/**
* @private
*/
private function dropdown_itemClickHandler(event:ListEvent):void
{
if (_showingDropdown)
{
close();
}
}
/**
* @private
*/
override protected function focusOutHandler(event:FocusEvent):void
{
// Note: event.relatedObject is the object getting focus.
// It can be null in some cases, such as when you open
// the dropdown and then click outside the application.
// If the dropdown is open...
if (_showingDropdown && _dropdown)
{
// If focus is moving outside the dropdown...
if (!event.relatedObject ||
!_dropdown.contains(event.relatedObject))
{
// Close the dropdown.
close();
}
}
super.focusOutHandler(event);
}
private function stage_resizeHandler(event:Event):void
{
if (_dropdown)
{
_dropdown.$visible = false;
_showingDropdown = false;
}
}
/**
* @private
*/
private function dropdown_scrollHandler(event:Event):void
{
// TextField.scroll bubbles so you might see it here
if (event is ScrollEvent)
{
var se:ScrollEvent = ScrollEvent(event);
if (se.detail == ScrollEventDetail.THUMB_TRACK ||
se.detail == ScrollEventDetail.THUMB_POSITION ||
se.detail == ScrollEventDetail.LINE_UP ||
se.detail == ScrollEventDetail.LINE_DOWN)
dispatchEvent(se);
onDropdownDataChange( new FlexEvent( FlexEvent.DATA_CHANGE ) );
}
}
/**
* @private
*/
private function dropdown_itemRollOverHandler(event:Event):void
{
dispatchEvent(event);
updateHighlightedText();
}
/**
* @private
*/
private function dropdown_itemRollOutHandler(event:Event):void
{
dispatchEvent(event);
updateHighlightedText();
}
private function dropdown_valueCommitHandler(event:FlexEvent):void
{
}
/**
* @private
*/
private function dropdown_changeHandler(event:Event):void
{
var prevValue:int = selectedIndex;
// This assignment will also assign the label to the text field.
// See setSelectedIndex().
//setSelectedIndexFromDropDown();
updateHighlightedText();
// If this was generated by the dropdown as a result of a keystroke, it is
// likely a Page-Up or Page-Down, or Arrow-Up or Arrow-Down.
// If the selection changes due to a keystroke,
// we leave the dropdown displayed.
// If it changes as a result of a mouse selection,
// we close the dropdown.
if (!_showingDropdown)
dispatchChangeEvent(event, prevValue, selectedIndex);
else if (!bInKeyDown)
{
// this will also send a change event if needed
close();
}
}
/**
* @private
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
// If the combo box is disabled, don't do anything
if(!enabled)
return;
// If a the editable field currently has focus, it is handling
// all arrow keys. We shouldn't also scroll this selection.
if (event.target == textInput)
return;
if (event.ctrlKey && event.keyCode == Keyboard.DOWN)
{
displayDropdown(true, event);
event.stopPropagation();
}
else if (event.ctrlKey && event.keyCode == Keyboard.UP)
{
close(event);
event.stopPropagation();
}
else if (event.keyCode == Keyboard.ESCAPE)
{
if (_showingDropdown)
{
if (_oldIndex != _dropdown.selectedIndex)
selectedIndex = _oldIndex;
displayDropdown(false);
event.stopPropagation();
}
}
else if (event.keyCode == Keyboard.ENTER)
{
if (_showingDropdown)
{
close();
event.stopPropagation();
}
}
else
{
if (!editable ||
event.keyCode == Keyboard.UP ||
event.keyCode == Keyboard.DOWN ||
event.keyCode == Keyboard.PAGE_UP ||
event.keyCode == Keyboard.PAGE_DOWN)
{
var oldIndex:int = selectedIndex;
// Make sure we know we are handling a keyDown,
// so if the dropdown sends out a "change" event
// (like when an up-arrow or down-arrow changes
// the selection) we know not to close the dropdown.
bInKeyDown = _showingDropdown;
// Redispatch the event to the dropdown
// and let its keyDownHandler() handle it.
dropdown.dispatchEvent(event.clone());
event.stopPropagation();
bInKeyDown = false;
}
}
}
/**
* @private
* This acts as the destructor.
*/
private function unloadHandler(event:Event):void
{
if (inTween)
{
UIComponent.resumeBackgroundProcessing();
inTween = false;
}
if (_dropdown)
_dropdown.parent.removeChild(_dropdown);
}
/**
* @private
*/
private function removedFromStageHandler(event:Event):void
{
// Ensure we've unregistered ourselves from PopupManager, else
// we'll be leaked.
destroyDropdown();
}
//--------------------------------------------------------------------------
//
// Tween handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
mx_internal function onTweenUpdate(value:Number):void
{
trace( "onTweenUpdate", value );
if (_dropdown)
{
_dropdown.scrollRect = new Rectangle(0, value,
_dropdown.width, _dropdown.height);
}
}
/**
* @private
*/
mx_internal function onTweenEnd(value:Number):void
{
if (_dropdown)
{
// Clear the scrollRect here. This way if drop shadows are
// assigned to the dropdown they show up correctly
_dropdown.scrollRect = null;
inTween = false;
_dropdown.enabled = true;
_dropdown.visible = _showingDropdown;
}
if (bRemoveDropdown)
{
_dropdown.removeEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.removeEventListener(FlexMouseEvent.MOUSE_WHEEL_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.removeEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, dropdown_mouseOutsideHandler);
_dropdown.removeEventListener(SandboxMouseEvent.MOUSE_WHEEL_SOMEWHERE, dropdown_mouseOutsideHandler);
PopUpManager.removePopUp(_dropdown);
_dropdown = null;
bRemoveDropdown = false;
}
UIComponent.resumeBackgroundProcessing();
var cbdEvent:DropdownEvent =
new DropdownEvent(_showingDropdown ? DropdownEvent.OPEN : DropdownEvent.CLOSE);
cbdEvent.triggerEvent = triggerEvent;
dispatchEvent(cbdEvent);
}
/**
* Determines default values of the height and width to use for each
* entry in the drop-down list, based on the maximum size of the label
* text in the first <code>numItems</code> items in the data provider.
*
* @param count The number of items to check to determine the value.
*
* @return An Object containing two properties: width and height.
*/
override protected function calculatePreferredSizeFromData(count:int):Object
{
var maxW:Number = 0;
var maxH:Number = 0;
var bookmark:CursorBookmark = iterator ? iterator.bookmark : null;
iterator.seek(CursorBookmark.FIRST, 0);
var more:Boolean = iterator != null;
var lineMetrics:TextLineMetrics;
for (var i:int = 0; i < count; i++)
{
var data:Object;
if (more)
data = iterator ? iterator.current : null;
else
data = null;
var txt:String = itemToLabel(data);
lineMetrics = measureText(txt);
maxW = Math.max(maxW, lineMetrics.width);
maxH = Math.max(maxH, lineMetrics.height);
if (iterator)
iterator.moveNext();
}
if (prompt)
{
lineMetrics = measureText(prompt);
maxW = Math.max(maxW, lineMetrics.width);
maxH = Math.max(maxH, lineMetrics.height);
}
maxW += getStyle("paddingLeft") + getStyle("paddingRight");
if (iterator)
iterator.seek(bookmark, 0);
return { width: maxW, height: maxH };
}
/**
* @private
*/
mx_internal function get isShowingDropdown():Boolean
{
return _showingDropdown;
}
private var _filterChanged : Boolean;
private var _filterString : String;
private var _filterFunction : Function;
private var _activeFilterFunction : Function;
private var _filteredCollection : ICollectionView;
private var _caseSensitive : Boolean;
private var _selectSingleMatch : Boolean;
private var _removeFilterOnSelection : Boolean;
private var _removeInputOnFocusOut : Boolean = true;
private var _highlightTextFormat : TextFormat;
public function get filteredCollection():ICollectionView
{
return _filteredCollection;
}
private function filterDataFunction( item:Object ):Boolean
{
var label:String = ( _caseSensitive ) ? itemToLabel( item ) : itemToLabel( item ).toLowerCase();
return ( label.indexOf( _filterString ) != -1 );
}
public function get caseSensitive():Boolean
{
return _caseSensitive;
}
public function set caseSensitive( value:Boolean ):void
{
if( _caseSensitive == value ) return;
_caseSensitive = value;
if( textInput.text != _prompt ) filterString = textInput.text;
}
public function get selectSingleMatch():Boolean
{
return _selectSingleMatch;
}
public function set selectSingleMatch( value:Boolean ):void
{
if( _selectSingleMatch == value ) return;
_selectSingleMatch = value;
if( _filteredCollection.length == 1 && _selectSingleMatch ) dropdown.selectedIndex = 0;
}
public function get removeFilterOnSelection():Boolean
{
return _removeFilterOnSelection;
}
public function set removeFilterOnSelection( value:Boolean ):void
{
if( _removeFilterOnSelection == value ) return;
_removeFilterOnSelection = value;
if( !dropdown && selectedIndex > -1 ) filterString = "";
}
public function get removeInputOnFocusOut():Boolean
{
return _removeInputOnFocusOut;
}
public function set removeInputOnFocusOut( value:Boolean ):void
{
if( _removeInputOnFocusOut == value ) return;
_removeInputOnFocusOut = value;
}
public function get filterString():String
{
return _filterString;
}
public function set filterString( value:String ):void
{
var newFilter:String = ( _caseSensitive ) ? value : value.toLowerCase();
if( _filterString == newFilter ) return;
_filterString = newFilter;
_filteredCollection.refresh();
if( _dropdown ) _dropdown.rowCount = rowCount;
}
override public function set editable( value:Boolean ):void
{
// do nothing
}
override protected function createChildren():void
{
super.editable = true;
super.createChildren();
// textInput.addEventListener( FocusEvent.FOCUS_IN, onTextInputFocusIn, false, 0, true );
textInput.addEventListener( FocusEvent.FOCUS_OUT, onTextInputFocusOut, false, 0, true );
textInput.addEventListener( MouseEvent.MOUSE_DOWN, onTextInputMouseDown, false, 0, true );
}
// private function onTextInputFocusIn( event:FocusEvent ):void
// {
// if( !isShowingDropdown ) open();
// }
private function onTextInputFocusOut( event:FocusEvent ):void
{
if( textInput.text == "" )
{
_filterString = "";
_filteredCollection.refresh();
textInput.text = prompt;
}
}
private function onTextInputMouseDown( event:MouseEvent ):void
{
if( !isShowingDropdown ) open();
}
protected function onDropdownDataChange( event:FlexEvent ):void
{
if( _filteredCollection.length == 1 && _selectSingleMatch ) dropdown.selectedIndex = 0;
if( !inTween && isShowingDropdown )
{
if( _dropdown.localToGlobal( new Point() ).y < localToGlobal( new Point() ).y )
{
var point:Point = localToGlobal(new Point() );
_dropdown.y = point.y-_dropdown.height;
}
}
updateHighlightedText();
}
private function updateHighlightedText():void
{
var renderer:ListItemRenderer;
var index:int;
var label:IUITextField;
var labelString:String;
var filterLength:int = _filterString.length;
var numItems:int = dropdown.rendererArray.length;
var i:int;
if( _filterString == "" )
{
for( i = 0; i < numItems; i++ )
{
renderer = ListItemRenderer( dropdown.rendererArray[ i ][ 0 ] );
if( renderer )
{
label = renderer.getLabel();
label.setSelection( 0, 0 );
label.setTextFormat( label.defaultTextFormat );
}
}
return;
}
for( i = 0; i < numItems; i++ )
{
renderer = ListItemRenderer( dropdown.rendererArray[ i ][ 0 ] );
if( renderer )
{
label = renderer.getLabel();
label.alwaysShowSelection = true;
labelString = ( _caseSensitive ) ? label.text : label.text.toLowerCase();
if( _highlightTextFormat )
{
label.setSelection( 0, 0 );
setHighlightTextFormatonLabel( label, labelString, 0, filterLength );
}
else
{
index = labelString.indexOf( _filterString );
if( index != -1 ) label.setSelection( index, index + filterLength );
}
}
}
}
private function setHighlightTextFormatonLabel( label:IUITextField, labelString:String, startIndex:int, length:int ):void
{
var index:int = labelString.indexOf( _filterString, startIndex );
if( index != -1 )
{
label.setTextFormat( _highlightTextFormat, index, index + length );
setHighlightTextFormatonLabel( label, labelString, index + length, length );
}
}
private function setSelectedIndexFromDropDown():void
{
if( _dropdown )
{
if( _dropdown.selectedIndex == -1 )
{
selectedIndex = _dropdown.selectedIndex;
}
else
{
if( selectedItem && dropdown.selectedItem == selectedItem ) return;
iterator.seek( CursorBookmark.FIRST );
var numItems:int = collection.length;
for( var i:int = 0; i < numItems; i++ )
{
if( iterator.current == _dropdown.selectedItem )
{
selectedIndex = i;
break;
}
iterator.moveNext();
}
}
}
}
}
}