Jason Szeto (Dev)
Jacob Goldstein (QA)
Anchor - the button or component that when pressed, opens the dropDown.
dropDown component - general term referring to a component that contains an anchor button and a dropDown
DropDownController - class that contains the user interaction logic for a dropDown component
DropDownList - a dropDown component that consists of a labeled button and a List dropDown.
dropDown - component that is displayed when the user presses the button of a dropDown component.
dropDown animation - the visual effect that occurs when a dropDown is displayed or hidden. A common effect is to slide or zoom the dropDown into view.
PopUpAnchor - Spark component that is used to position a dropDown in layout. Since a dropDown is added to the display list via the PopUpManager, it doesn't normally participate in layout. The PopUpAnchor component is a UIComponent that does get added to a container and thus is laid out. It is responsible for then sizing and positioning the dropDown relative to itself.
This is a revised version of the original spec. The main change is that DropDownList subclasses List instead of DropDownBase. DropDownBase has been removed and its functionality will be moved into a controller class that is used in each dropDown component class. Thanks go to Iwo Banas for his design suggestions.
Since DropDownList subclasses List instead of DropDownBase, its skin no longer has a dropDown
skin part. Instead, it inherits the dataGroup
skin part from List.
DropDownList is an example of a broader category of components that we call button dropDown components. These components consist of a button with a label/icon and a dropDown component. Each dropDown component subclasses the class that is dropped down. For example, DropDownList subclasses List while DropDownSlider subclasses Slider.
Examples of possible dropDown components:
Each dropDown component will contain a required button skin part. A controller class called DropDownController has logic for opening and closing the dropDown based on keyboard and mouse interactions with the button.
The size and position of the dropDown is defined by the skin. The skin also provides animations when the dropDown is shown and hidden.
DropDownList is a non-editable, selectable dropDown List with a labeled button. It subclasses List and adds the prompt property. It also modifies the user interaction behavior found in List.
Mick is creating an online survey for the PitchSpoon music site. One of the questions is to select the "Best Rock Band of All Time." Since the list of choices is long and he doesn't want to take too much screen real estate, he uses a DropDownList to capture the answer. He adds the DropDownList in his application, hooks up the dataProvider to his back end data.
Mick decides to also add in a question for "Best Rock Album of All Time." He again decides to use a DropDownList. But he wants to enhance the experience by including a thumbnail of the album along with the title and band name. He creates an itemRenderer with the thumbnail on the left and on the right, the album title and band name. The itemRenderer is taller than the standard one line renderer. He doesn't want the DropDownList button to take up that much space, so he adds an Image tag and SimpleText into the skin to display the album cover and the title, using binding to populate the data.
Flea is building a red hot music player. The design calls for small, minimal controls. He wants the user to be able to control the volume, but he doesn't have the space to put a slider control. Instead, he creates a dropDown Slider component. Since one is not included in the Spark component set, he needs to create a custom component.
He creates DropDownSlider which subclasses Slider. He creates a custom skin which creates a button with an icon and a vertical track and thumb dropDown skin part. He incorporates a DropDownController instance to control the user interaction. He then includes an instance of his new class in his music player, being sure to pass in a volume icon to the DropDownSlider. This volume icon will be assigned to the button's icon property.
The DropDownList class extends List. It has an optional skin part called labelElement
. This part is of type TextGraphicElement
and its text
property is always set to the selectedItem
's string representation. The string representation is calculated by passing the data to the static List.itemToLabel
function which takes a labelFunction, labelField and the data.
The prompt
property contains the text to display when the DropDownList has no selection. The behavior of prompt
is slightly different from the Halo ComboBox. In ComboBox, if prompt is defined, then selectedIndex
defaults to -1. Otherwise, selectedIndex
defaults to 0. In the Spark DropDownList, the value of prompt
does not affect the default value of selectedIndex
. Instead, the developer can use the requiredSelection
property to change the default value of selectedIndex
.
DropDownList inherits the dataGroup
skin part from SkinnableDataContainer. This skin part is typically contained in a PopUpAnchor class or is adjacent to the anchor button. This skin part should only be visible when the skin is in the open state. It will be common to only include this skin part in the open state so that it has no memory footprint when the DropDownList is closed.
The default skin contains the three skin parts, openButton
, labelElement
and dataGroup
. Because the dropDown button looks different than a regular button, there is a separate skin for it called DropDownListButtonSkin.
The DropDownController class handles the mouse, keyboard, and focus interactions for an anchor button and its dropDown. This helper class should be used by other dropDown components to handle the opening and closing of the dropDown due to user interactions.
DropDownController contains an openButton
property and a dropDown
property, which typically are set to the corresponding skin parts in the host dropDown component. It has an isOpen
property which is true when the dropDown is visible. The openDropDown
and closeDropDown
functions control whether the dropDown is open or not. The DropDownController will dispatch a DropDownEvent.OPEN
event when the dropDown is opened and a DropDownEvent.CLOSE
event when the dropDown is closed. Note that the DropDownController isn't responsible for putting the dropDown into the open or close state. Instead, the dropDown component listens for the DropDownEvent.OPEN
and DropDownEvent.CLOSE
events and takes the approriate action. DropDownList changes its skin state to normal or open in response to the DropDownController events.
DropDownController has a processFocusOut
function which is called by the host component when it loses focus. It also has a processKeyDown
event which is called by the host component when it receives a keyDown event.
DropDownController listens for different user interactions which either open or close the dropDown. The controller will open on a FlexEvent.BUTTON_DOWN
event from the openButton
. If rollOverOpenDelay
is not NaN, then it will open on a MouseEvent.ROLL_OVER
instead of the button down event. The controller will also open in response to the CTRL-DOWN key combination.
Clicking anywhere outside of the dropDown
will cause the DropDownController to close is rollOverOpenDelay
is NaN. Otherwise, releasing the mouse button and moving the mouse anywhere outside of the openButton
or dropDown
will close the DropDownController.
Pressing the CTRL-UP key combination or the ENTER key will close the dropDown. Pressing the ESC key will close the dropDown and cancel the selection. It is the responsibility of the dropDown component to cancel the selection if the DropDownEvent.CLOSE
dispatched from the DropDownController has been cancelled.
If the dropDown
loses focus or the stage is resized, then the DropDownController will close.
A typical dropDown component will incorporate a DropDownController instance by performing the following steps:
openButton
and dropDown
skin parts are added, assign them to the DropDownController's openButton
and dropDown
properties. DropDownEvent.OPEN
and DropDownEvent.CLOSE
events.openDropDown
or closeDropDown
functions. KeyboardEvent.KEY_DOWN
event, call the DropDownController's processKeyDown
function. If this function returns true, that means the controller handled this event and the dropDown component should probably ignore it. processFocusOut
function. Since the skin has several skin states, the animations for opening and closing the dropDown can be defined in the skin as transitions between the normal and open states.
ComboBox
DropDownList
Notes
Public Properties
arrowButtonStyleFilters
Skin defines appearance
borderMetrics
Skin defines appearance
collection
Used internally to hold dataProvider
data
Support for using class as itemRenderer
dataProvider
dataProvider
inherited from List
dropdown
dropDown is visual-only
dropdownFactory
DropDownList doesn't use a dropDown factory
dropdownWidth
Skin defines appearance
editable
Not editable
imeMode
Do we need to support?
itemRenderer
itemRenderer
inherited from List
iterator
Used internally to hold collection iterator
labelField
labelField
inherited from List
labelFunction
labelFunction
inherited from List
listData
Used for drop-in renderer
prompt
prompt
restrict
Not editable
rowCount
Expose layout object instead?
selectedIndex
selectedIndex
inherited from List
selectedItem
selectedItem
inherited from List
selectedLabel
Not currently defined on List
text
Do we need? Equivalent to selectedLabel
textInput
button
Replace TextInput with Button skin part
textInputStyleFilters
No textInput
value
Use selectedItem
Protected Properties
dropDownStyleFilters
Skin defines appearance
Public Methods
close
closeDropDown
itemToLabel
itemToLabel
inherited from List
open
openDropDown
Protected Methods
calculatePreferredSizeFromData
Helper function to find dropdown size
collectionChangeHandler
downArrowButton_buttonDownHandler
buttonDownHandler
itemToUID
Not sure how this is used
textInput_changeHandler
No textInput
Events
change
change
close
close
dataChange
Event when data property changes
enter
Not editable
itemRollOut
Not currently defined on List
itemRollOver
Not currently defined on List
open
open
scroll
Not currently defined on List
Styles
alternatingItemColors
alternatingItemColors
arrowButtonWidth
Modify the skin
borderColor
Modify the skin
borderThickness
Modify the skin
closeDuration
Close transition defined in skin
closeEasingFunction
Close transition defined in skin
color
color
cornerRadius
Modify the skin
disabledColor
color.disabled
Use stateful styles
disabledIconColor
symbolColor.disabled
Use stateful styles
dropdownBorderColor
Modify the skin
dropdownStyleName
Modify the skin. Inheriting styles will pass through
fillAlphas
Modify the skin
fillColors
Modify the skin (or change baseColor)
focusAlpha
?
focusRoundedCorners
?
font / text Styles
Supports all text styles
highlightAlphas
Modify the skin
iconColor
symbolColor
openDuration
Open transition defined in skin
openEasingFunction
Open transition defined in skin
paddingBottom
Modify the skin
paddingLeft
Modify the skin
paddingRight
Modify the skin
paddingTop
Modify the skin
rollOverColor
rollOverColor
selectionColor
selectionColor
selectionDuration
Modify the skin
selectionEasingFunction
Modify the skin
textFieldClass
Text field is defined in the skin
package spark.components
{
/**
* Dispatched when the dropDown is dismissed for any reason such when
* the user:
*
* selects an item in the dropDown
* clicks outside of the dropDown
* clicks the dropDown button while the dropDown is
* displayed
*
*
* @eventType mx.events.DropdownEvent.CLOSE
*/
<a href="Event%28name%3D%26quot%3Bclose%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DropdownEvent%26quot%3B%29">Event(name="close", type="mx.events.DropdownEvent")</a>
/**
* Dispatched when the user clicks the dropDown button
* to display the dropDown. It is also dispatched if the user
* uses the keyboard and types Ctrl-Down to open the dropDown.
*
* @eventType mx.events.DropdownEvent.OPEN
*/
<a href="Event%28name%3D%26quot%3Bopen%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DropdownEvent%26quot%3B%29">Event(name="open", type="mx.events.DropdownEvent")</a>
/**
* Normal State of the DropDown component
*/
<a href="SkinState%28%26quot%3Bnormal%26quot%3B%29">SkinState("normal")</a>
/**
* Open State of the DropDown component
*/
<a href="SkinState%28%26quot%3Bopen%26quot%3B%29">SkinState("open")</a>
/**
* Disabled State of the DropDown component
*/
<a href="SkinState%28%26quot%3Bdisabled%26quot%3B%29">SkinState("disabled")</a>
/**
* DropDownList 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.
*
*/
public class DropDownList extends List
{
/**
* An optional skin part that holds the prompt or the text of the selectedItem
*/
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a>
public var labelElement:TextGraphicElement;
/**
* A skin part that defines the anchor button.
*/
<a href="SkinPart%28required%3D%26quot%3Btrue%26quot%3B%29">SkinPart(required="true")</a>
public var openButton:ButtonBase;
/**
* Instance of the helper class that handles all of the mouse, keyboard
* and focus user interactions.
*/
protected function get dropDownController():DropDownController;
protected function set dropDownController(value:DropDownController);
/**
* The prompt for the DropDownList control. A prompt is
* a String that is displayed in the TextInput portion of the
* DropDownList when selectedIndex= -1. It is usually
* a String like "Select one...". If there is no
* prompt, the DropDownList control set selectedIndex to 0
* and displays the first item in the dataProvider.
*/
public function get prompt():String;
public function set prompt(value:String):void;
/**
* Initializes the dropDown and changes the skin state to open.
*/
public function openDropDown():void;
/**
* Changes the skin state to normal, commits the data from the dropDown and
* performs some cleanup.
*
* The user can close the dropDown either in a committing or non-committing manner
* based on their interaction gesture. If the user has performed a committing
* gesture, then set commitData to true.
*
* @param commitData Flag indicating if the component should commit the selected
* data from the dropDown. If this flag is true, then commitDropDownData will be called.
*/
public function closeDropDown(commitData:Boolean):void;
/**
* Event handler for the <code>dropDownController</code>
* <code>DropdownEvent.OPEN</code> event. Updates the skin's state and
* ensures that the selectedItem is visible.
*/
protected function dropDownController_openHandler(event:DropdownEvent):void;
/**
* Event handler for the <code>dropDownController</code>
* <code>DropdownEvent.CLOSE</code> event. Updates the skin's state.
*/
protected function dropDownController_closeHandler(event:DropdownEvent):void;
}
}
/**
* DropDownController handles the mouse, keyboard, and focus
* interactions for an anchor button and its dropDown. This helper class
* should be used by other dropDown components to handle the opening
* and closing of the dropDown due to user interactions.
*/
public class DropDownController extends EventDispatcher
/**
* Reference to the openButton skin part of the dropDown component.
*/
public function set openButton(value:ButtonBase):void
public function get openButton():ButtonBase
/**
* Reference to the dropDown skin part of the dropDown component.
*/
public function set dropDown(value:DisplayObject):void
public function get dropDown():DisplayObject
/**
* Whether the dropDown is open or not.
*/
public function get isOpen():Boolean
/**
* Specifies the delay, in milliseconds, to wait for opening the drop down
* when the anchor button is rolled over.
* If set to NaN, then the drop down opens on a click, not a rollover.
*/
public function get rollOverOpenDelay():Number
public function set rollOverOpenDelay(value:Number):void
/**
* Opens the dropDown and dispatches a <code>DropdownEvent.OPEN</code> event.
*/
public function openDropDown():void
/**
* Closes the dropDown and dispatches a <code>DropdownEvent.CLOSE</code> event.
*
* @param commitData Flag indicating if the component should commit the selected
* data from the dropDown.
*/
public function closeDropDown(commitData:Boolean):void
/**
* Called when the buttonDown event is dispatched. This function opens or closes
* the dropDown depending upon the dropDown state.
*/
protected function openButton_buttonDownHandler(event:Event):void
/**
* Called when the openButton's rollOver event is dispatched. This function opens
* the dropDown, or opens the drop down after the rollOverOpenDelay.
*/
protected function openButton_rollOverHandler(event:MouseEvent):void
/**
* Called when the systemManager receives a mouseDown event. This closes
* the dropDown if the target is outside of the dropDown.
*/
protected function systemManager_mouseDownHandler(event:Event):void
/**
* Called when the dropdown is popped up from a rollover and the mouse moves
* anywhere on the screen. If the mouse moves over the openButton or the dropdown,
* the popup will stay open. Otherwise, the popup will close.
*/
protected function systemManager_mouseMoveHandler(event:Event):void
/**
* Close the dropDown if the stage has been resized.
*/
protected function systemManager_resizeHandler(event:Event):void
/**
* Closes the dropDown if focus it is no longer in focus.
*/
public function processFocusOut(event:FocusEvent):void
/**
* Handles the keyboard user interactions.
*
* @return Returns true if the <code>keyCode</code> was
* recognized and handled.
*/
public function processKeyDown(event:KeyboardEvent):Boolean
ComboBox is a DropDownList that contains an editable textInput instead of a button. It supports inputting a value not contained in the dataProvider.
<DropDownList width="140" >
<dataProvider>
<ArrayCollection>
<String>1. Alabama</String>
<String>2. Alaska</String>
<String>3. Arizona</String>
<String>4. Arkansas</String>
<String>5. California</String>
</ArrayCollection>
</dataProvider>
</DropDownList>
\
<SparkSkin xmlns="http://ns.adobe.com/mxml/2009">
<!-- host component -->
<Metadata>
<a href="HostComponent%28%26quot%3Bspark.components.DropDownList%26quot%3B%29">HostComponent("spark.components.DropDownList")</a>
</Metadata>
<states>
<State name="normal"/>
<State name="open"/>
<State name="disabled"/>
</states>
<Popup includeIn="open" width="100%" height="100%" placement="below">
<Group maxHeight="100">
<!-- border -->
<Rect left="0" right="0" top="0" bottom="0">
<stroke>
<SolidColorStroke color="0x686868" weight="1"/>
</stroke>
</Rect>
<!-- fill -->
<Rect id="background" left="1" right="1" top="1" bottom="1" >
<fill>
<SolidColor id="bgFill" color="0xFFFFFF" />
</fill>
</Rect>
<Scroller left="1" top="1" right="1" bottom="1" id="scroller">
<DataGroup id="dataGroup" itemRenderer="spark.skins.default.DefaultItemRenderer">
<layout>
<VerticalLayout gap="0" horizontalAlign="contentJustify" />
</layout>
</DataGroup>
</Scroller>
<filters>
<DropShadowFilter blurX="20" blurY="20" distance="5" angle="90" alpha="0.6" />
</filters>
</Group>
</Popup>
<Button id="button" width="100%" height="100%" skinClass="spark.skins.default.DropDownListButtonSkin"/>
<SimpleText id="labelElement" left="5" right="5" top="5" bottom="5" />
</SparkSkin>
\
<SparkSkin xmlns="http://ns.adobe.com/mxml/2009">
<!-- host component -->
<Metadata>
<a href="HostComponent%28%26quot%3Bspark.components.DropDownList%26quot%3B%29">HostComponent("spark.components.DropDownList")</a>
</Metadata>
<states>
<State name="normal"/>
<State name="open"/>
<State name="disabled"/>
</states>
<Popup includeIn="open" width="100%" height="100%" placement="below">
<Group maxHeight="100">
<!-- border -->
<Rect left="0" right="0" top="0" bottom="0">
<stroke>
<SolidColorStroke color="0x686868" weight="1"/>
</stroke>
</Rect>
<!-- fill -->
<Rect id="background" left="1" right="1" top="1" bottom="1" >
<fill>
<SolidColor id="bgFill" color="0xFFFFFF" />
</fill>
</Rect>
<Scroller left="1" top="1" right="1" bottom="1" id="scroller">
<DataGroup id="dataGroup" itemRenderer="spark.skins.default.DefaultItemRenderer">
<layout>
<VerticalLayout gap="0" horizontalAlign="contentJustify" />
</layout>
</DataGroup>
</Scroller>
<filters>
<DropShadowFilter blurX="20" blurY="20" distance="5" angle="90" alpha="0.6" />
</filters>
</Group>
</Popup>
<Button id="button" width="100%" height="100%" skinClass="spark.skins.default.DropDownListButtonSkin"/>
<Group left="5" right="5" top="5" bottom="5" mouseEnabled="false" mouseChildren="false" >
<layout>
<HorizontalLayout gap="5"/>
</layout>
<Rect width="10" height="10">
<stroke>
<SolidColorStroke weight="1" color="0x000000"/>
</stroke>
<fill>
<SolidColor color="{hostComponent.selectedItem.color}"/>
</fill>
</Rect>
<SimpleText width="100%" text="{hostComponent.selectedItem.label}"/>
</Group>
</SparkSkin>
No compiler changes are required
No impact on web tier compiler
There is a dependency on the PopUpAnchor component feature. The default DropDownList skin requires it.
There is a dependency on List for the following issues:
None
None
None
This component needs support for accessibility.
Make the dataGroup skin part optional so that it only is created when the DropDownList is opened.
Since the classes are composites of other components, it relies on these components to support globalization.
N/A
None
How does DropDownList calculate is measured size? Should the measuredWidth be the same as the dropdown's width? Should the DropDownList iterate through the dataProvider, apply each item to the anchor data renderer and set measuredWidth to the largest width? Another option is to add a typicalItem property. The measured size would be the size of the DropDownList when displaying the typicalItem.
Should we make dataGroup skin part an optional part? This would allow us to include the dataGroup skin part only in the open state, thus saving memory while the DropDownList is closed.
The dataGroup skin part is now an optional skin part
How should the DropDownList pass the selectedItem and the selectedItem's text representation (applying labelField/labelFunction to the data) to the skin? Currently it sets the labelElement skin part to the text representation and doesn't pass the selectedItem. Should we get rid of the labelElement skin part and pass the string to the anchor button's label? There are numerous ways to either push or pull this information.
The DropDownList will set the labelElement.text property.
We need to modify List and ListBase to support non-committing selection. For DropDownList, we only want the selectedIndex to change when the dropDown is closed. For example, when the user uses the arrow keys to choose a selection, this should not change the selectedIndex of the DropDownList until the dropDown is closed. Pressing the ESC button should reset the selectedIndex back to the value it had when the dropDown was opened.
Do we need a helper class to handle opening and closing the dropDown? Or can this logic just get embedded in each dropDown component class?
The DropDownController class is the helper class that handles user interaction
What should happen with a DropDownList that has a prompt when it is selected? Can it then be unselected? Would this just show the prompt again? What user gestures would unselect the item?
Setting the selectedIndex to -1 programmatically will show the prompt. The prompt is the text that is passed to the labelElement when there is no selection. It is possible to unselect a selection via keyboard gestures, but not via mouse gestures.
None
None