Menu

Spark DropDownList

SourceForge Editorial Staff

Jason Szeto (Dev)
Jacob Goldstein (QA)

Functional and Design Specification


Glossary


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.

Changes from Original DropDownList Design


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.

Summary and Background


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:

  • DropDownMenu
  • DropDownList
  • DropDownColorPicker
  • DropDownDateField
  • DropDownSlider
  • DropDownTextInput
  • ComboBox

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.

Usage Scenarios


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.

Detailed Description


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:

  • When the dropDown component openButton and dropDown skin parts are added, assign them to the DropDownController's openButton and dropDown properties.
  • Listen for the DropDownController DropDownEvent.OPEN and DropDownEvent.CLOSE events.
  • When the dropDown component is programmatically opened or closed, call the DropDownController's openDropDown or closeDropDown functions.
  • When the dropDown component receives a 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.
  • When the dropDown component loses focus, call the DropDownController's processFocusOut function.

Animation

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.

Differences between Halo ComboBox and Spark DropDownList

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

API Description


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

B Features


ComboBox is a DropDownList that contains an editable textInput instead of a button. It supports inputting a value not contained in the dataProvider.

Examples and Usage


<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>

Additional Implementation Details


Prototype Work


Compiler Work


No compiler changes are required

Web Tier Compiler Impact


No impact on web tier compiler

Flex Feature Dependencies


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:

  • The set of events currently dispatched by List is not complete
    • Missing a number of events defined in List
    • Need a mode where arrow key navigation doesn't select the item. Instead, it just highlights it.
  • The set of properties and functions defined by List is not complete

Backwards Compatibility


Syntax changes

None

Behavior

None

Warnings/Deprecation

None

Accessibility


This component needs support for accessibility.

Performance


Make the dataGroup skin part optional so that it only is created when the DropDownList is opened.

Globalization


Since the classes are composites of other components, it relies on these components to support globalization.

Localization


Compiler Features

N/A

Framework Features

None

Issues and Recommendations


Open Issues

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.

Resolved Issues

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.

Documentation


None

QA


None



Related

Wiki: Flex 4

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.