Menu

Enhanced States Syntax

SourceForge Editorial Staff

Enhanced States Syntax - Functional and Design Specification


Glossary


state - A state (or 'view-state') is a collection of changes (called overrides) to a view. The changes within a state can comprise additions or removals of components as well as changes to their properties, styles, and behavior.

overlay - An overlay is a special class of state that can be applied concurrently with other states and/or overlays. Overlays are triggered via a user defined predicate or "condition", and are cleared when the associated condition no longer holds. Note: This language feature will be detailed in a follow up specification.

Summary and Background


Goals

  • Reduce the complexity of the Flex view-states feature by (1) providing a new syntax that allows state-specific object and/or property changes to be specified inline, as opposed to with somewhat more verbose, and out-of-context override tags, and (2) eliminating the concept of hierarchical states.

  • Refine and evolve the view states model such that we support a means of sharing component instances between states without necessitating the use of state hierarchies as we have today.

  • Promote the states functionality to an MXML language feature, expanding the set of runtime objects that are supported by the stateful document model.

Usage Scenario


Flex 4 will target all of the legacy usage scenarios of classic Flex states functionality (stateful components, states as application "views" or "pages", effects and transitions between view states, etc.). This document outlines what is primarily a syntax change for the existing functionality.

Detailed Description


Inline States Syntax

States will be promoted to a full language feature of MXML during the Flex 4 time frame. A more terse and flexible inline syntax will be provided for expressing all state-specific changes to a component instance. The classic syntax will be deprecated as of MXML version 4.0.

First, a brief description of how the states model will change for version 4.0 components:

  • A set of states within a component definition is still defined via the states array property of UIComponent, however individual overrides are no longer declared as children of the states tags themselves. The legacy AddChild, RemoveChild, SetProperty, SetStyle, and SetEventHandler overrides have been replaced in favor of language keywords that can be used inline within the document.

AddChild, RemoveChild are deprecated in favor of the the 'includeIn' and 'excludeFrom' compile-time attributes. These attributes can be used to annotate document nodes directly inline, in order to specify the set of states for which they are to be realized.

SetProperty, SetStyle, and SetEventHandler overrides are replaced with a new syntax that allows for the attribute identifier of a property or style to denote which state(s) that value applies.

  • During the scope of this feature iteration, as with Flex 3, only UIComponent instances support being stateful. We will support generic use of the new states syntax with arbitrary MXML component documents in a follow-up iteration (that will be spec'd out separately and implemented within the Flex 4 timeframe). See the implementation details for more.
  • A convenient 'Reparent' tag will allow for the ability to specify an alternate parent for a given document node, in the context of a specific state.

  • Once a "stateful" component has been defined (one where the states language tag has one or more valid State children), the component must at any given time be in one of the defined states (it is no longer valid for a component to be in a 'base' or 'null' state). By default, the first declared state is the initial state (the initial value of currentState), unless currentState is explicitly set.
    On a related note, unlike the classic states model, if there is a enter transition defined for the state serving as the initial state, the transition will not apply until subsequent activations of that state.

  • State names are no longer arbitrary strings, specifically for a state name to be valid, the identifier must be consistent with a valid XML attribute excluding the dot '.' and colon ':' characters. For tooling purposes one can utilize the new Flex 4 custom namespace functionality to annotate individual states with design time attributes (for example a "friendly" name), if a non-restrictive identifier is required.

  • All state-aware Flex features (effects, transitions, etc.) continue to function as they have previously.

  • For the first phase of the development of this feature, the stateful document model can be augmented or extended at runtime by accessing the states array and contained set of Overrides for each state as with Flex 3. In a later phase during the Flex 4 development path, we will revisit this API and provide something more consistent with the new model. (See B Features section).

  • The presence of an 'MXML language namespace' determines the language version for an MXML document. Note that the legacy states syntax is only applicable for the documents in the old MXML language namespace http://www.adobe.com/2006/mxml. The new MXML language namespace http://ns.adobe.com/mxml/2009 will support the new syntax only. There are no scenarios where we allow mixing of the two. Note: The details of the new language namespace are documented in the Flex 4 MXML 2009 language specification.

API Description


A detailed overview of the new inline syntax follows:

State-Specific Component Instances

The AddChild and RemoveChild override mechanism will be replaced by a new includeIn language attribute that can be used to decorate inline MXML component instance tags with the set of states for which the specific instance is to be realized.

The new includeIn attribute accepts a comma delimited list of state names, all of which must have been previously declared within the document's states array. Optionally, excludeFrom can be used to define an explicit "black-list" of states where the instance will not apply.

The excludeFrom and includeIn attributes are mutually exclusive. If both are defined on a single tag, it is considered an error and an appropriate compiler error will be issued.

An example:

<!-- Given the states A,B,C -->
<m:states>
    <m:State name="A"/>
    <m:State name="B"/>
    <m:State name="C"/>
</m:states>

<!-- This button will appear in only states A and B -->
<Button label="Click Me" includeIn="A, B"/>

<!-- This button will appear in states A and B -->
<Button label="Button C" excludeFrom="C"/>

The includeIn and excludeFrom attributes will be treated as reserved language keywords by the MXML compiler.

The includeIn and excludeFrom attributes can be legally specified on any MXML object within a document, with the exception of:

  • The root node of an MXML document (e.g. Application).
  • Property tags (special MXML tags that represent properties of their parent object, e.g. mx:label).
  • Descendants of the XML, XMLList, or Model tags. (See B Features section).
  • Any "language" tags declared within the new Flex 4 language namespace (including any descendants of language tags). The full set of language tags are detailed in the Flex 4 MXML 2009 language specification, but they include (for example), Script, Binding, Metadata, Style, etc.

In addition to referencing mutually exclusive states, both attributes also support composite states (currently known as overlays). A more detailed specification of how Overlays are supported in the new state model will be incorporated into the Overlays specification (not yet drafted).

EXAMPLE (1): An AddChild Equivalent

BEFORE - Classic states syntax (O'Reilly Programming Flex 2):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:states>
        <mx:State name="newCheckbox">
            <mx:AddChild relativeTo="{vbox}">
                <mx:CheckBox id="checkbox" label="Checkbox" />
            </mx:AddChild>
        </mx:State>
        <mx:State name="newTextArea" basedOn="newCheckBox">
            <mx:AddChild relativeTo="{vbox}">
                <mx:TextArea id="textarea" />
            </mx:AddChild>
        </mx:State>
    </mx:states>
    <mx:VBox id="vbox">
            <mx:Button label="Click" click="currentState='newCheckbox'" />
            <mx:Button label="Click" click="currentState='newTextArea'" />
    </mx:VBox>
</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo"
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <m:states>
        <m:State name="default"/>
        <m:State name="newCheckbox"/>
        <m:State name="newTextArea"/>
    </m:states>
    <mx:VBox>
        <mx:Button label="Click" click="currentState='newCheckbox'" />
        <mx:Button label="Click" click="currentState='newTextArea'" />
        <mx:CheckBox id="checkbox" label="Checkbox" includeIn="newCheckbox, newTextArea"/>
        <mx:TextArea id="textarea" includeIn="newTextArea"/>
    </mx:VBox>
</mx:Application>

Transient component instances (instances that appear across one or more states), are declared inline with the rest of the document, essentially at their "natural" location in the DOM. The context of a state-specific instance is no longer explicitly defined by the relativeTo/position properties of AddChild. Location is instead inferred by the inline placement.

Note that for a state-specific node to be realized within a state, its ancestors must also be defined within the same state as well. For example, a child is not visible in StateA if its parent is excluded from StateA. If such a scenario is detected, a compiler warning will be issued.

EXAMPLE (2): Child Insertion

BEFORE - Classic states syntax (O'Reilly Programming Flex 2):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:states>
        <mx:State name="newTextInput">
            <mx:AddChild relativeTo="{checkbox1}" position="after">
                <mx:TextInput id="textinput" />
            </mx:AddChild>
        </mx:State>
    </mx:states>
    <mx:VBox id="vbox">
        <mx:CheckBox id="checkbox1" label="One" />
        <mx:CheckBox id="checkbox2" label="Two" />
        <mx:Button id="button" label="Click" click="currentState='newTextInput'" />
    </mx:VBox>
</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <m:states>
        <m:State name="default"/>
        <m:State name="newTextInput"/>
    </m:states>
    <mx:VBox>
        <mx:CheckBox label="One" />
        <mx:TextInput includeIn="newTextInput"/>
        <mx:CheckBox label="Two" />
        <mx:Button id="button" label="Click" click="currentState='newTextInput'" />
    </mx:VBox>
</mx:Application>

And another example, demonstrating how the use of RemoveChild in the legacy syntax, translates to the new:

EXAMPLE (3): A RemoveChild Equivalent

BEFORE - Classic states syntax (O'Reilly Programming Flex 2):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:states>
        <mx:State name="noCheckboxes">
            <mx:RemoveChild target="{checkbox1}" />
            <mx:RemoveChild target="{checkbox2}" />
        </mx:State>
    </mx:states>
    <mx:VBox id="vbox">
        <mx:CheckBox id="checkbox1" label="One" />
        <mx:CheckBox id="checkbox2" label="Two" />
        <mx:Button id="button" label="Click" click="currentState='noCheckboxes'" />
    </mx:VBox>
</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <m:states>
        <m:State name="default"/>
        <m:State name="noCheckboxes"/>
    </m:states>
    <mx:VBox id="vbox">
        <mx:CheckBox label="One" excludeFrom="noCheckboxes"/>
        <mx:CheckBox label="Two" includeIn="default"/>  <!-- Works as above -->
        <mx:Button id="button" label="Click" click="currentState='noCheckboxes'" />
    </mx:VBox>
</mx:Application>

Reparenting

Where one would generally have used a RemoveChild followed by an AddChild to reparent instances within a state (Flex 3), a new Reparent language tag will be provided that can be used as an inline insertion point for a given component instance.

The Reparent tag itself provides a target attribute which specifies the component instance to reparent. The includeIn attribute used in combination with the Reparent tag specifies the context for which the reparenting is to take place. There is no formal runtime representation of the Reparent tag itself. The compiler will convert the Reparent into explicit remove and insertion overrides for the given state(s).

Note that for a Reparent tag to be valid, it must refer to a document node that is not already realized within the state that the reparenting is to take place. Additionally, two or more Reparent tags must not target the same DOM node within the same state. The set of reparent operations in a given state must, when combined, result in a valid DOM (no circular references, etc.). If any of these error conditions are detected, a compiler error will be issued.

Additional restrictions on the Reparent tag are as follows:

  • A reparent operation can only reparent document nodes to a type-compatible destination. The compiler will issue an error if the reparent operation is invalid due to type incompatiblity.
  • Being a 'language' tag, Reparent has no runtime representation per se, the compiler ultimate generates the code necessary to carry out the reparenting operation for the specified state.

See the #Media Chat example later for an example Reparent use case.

State-Specific Property Values

The SetProperty, SetStyle, and SetEventHandler overrides will be replaced by an inline attribute notation.

State-specific property values can be expressed by utilizing the 'dot' operator on any writable XML attribute to essentially provide a hint to the compiler that the attribute is to be 'scoped' to a specific state.

<!-- Button's label in base state is "XXX', in state A, "YYY", and in state B, "ZZZ" -->
<Button label="XXX" label.A="YYY" label.B="ZZZ" />

The unqualified property/value pair (e.g property=value) is assumed to be the default base value and the value within all states, unless specifically overridden.

<!-- Default value, 'YYY' applies to all named states, except the 'A' state -->
<Button label="YYY" label.A="XXX" />

The "drill-down" (dot) syntax can be utilized with most component properties, styles, or event handlers. The state-specific attribute syntax cannot however be used with built-in language attributes. The full set of language attributes are detailed in the Flex 4 MXML 2009 language specification, but they include (for example), id, states, frameRate, etc.

To facilitate the ability to "clear" a property value within a given state, a new directive '@Clear()" will be introduced. This essentially results in property being unset. e.g. For a style property, it would result in a clearStyle() being invoked (thus allowing the natural cascade to take effect). The @Clear() value directive can be used with properties, styles, and event handlers.

<!-- In state A, the color style is cleared.  Any CSS selectors can now take effect. -->
<Button color="0xFF0000" color.A="@Clear()" />

Some additional examples of state-specific property syntax:

EXAMPLE (4): State-Specific Styles

BEFORE - Classic states syntax (O'Reilly Programming Flex 2):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:states>
        <mx:State name="landState">
            <mx:SetStyle target="{car}" name="color" value="0xFF0000" />
            <mx:SetStyle target="{train}" name="color" value="0xFF0000" />
            <mx:SetStyle target="{motorcycle}" name="color" value="0xFF0000" />
        </mx:State>
        <mx:State name="airState">
            <mx:SetStyle target="{helicopter}" name="color" value="0xFF0000" />
            <mx:SetStyle target="{airplane}" name="color" value="0xFF0000" />
        </mx:State>
        <mx:State name="waterState">
            <mx:SetStyle target="{boat}" name="color" value="0xFF0000" />
            <mx:SetStyle target="{submarine}" name="color" value="0xFF0000" />
        </mx:State>
    </mx:states>
    <mx:VBox id="vbox">
        <mx:HBox>
            <mx:Button id="land" label="Land" click="currentState='landState'" />
            <mx:Button id="air" label="Air" click="currentState='airState'" />
            <mx:Button id="water" label="Water" click="currentState='waterState'" />
        </mx:HBox>
        <mx:CheckBox id="helicopter" label="Helicopter" />
        <mx:CheckBox id="motorcycle" label="Motorcycle" />
        <mx:CheckBox id="car" label="Car" />
        <mx:CheckBox id="airplane" label="Airplane" />
        <mx:CheckBox id="train" label="Train" />
        <mx:CheckBox id="boat" label="Boat" />
        <mx:CheckBox id="submarine" label="Submarine" />
    </mx:VBox>
</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <m:states>
        <m:State name="landState"/>
        <m:State name="airState"/>
        <m:State name="waterState"/>
    </m:states>
    <mx:VBox id="vbox">
        <mx:HBox>
            <mx:Button id="land" label="Land" click="currentState='landState'" />
            <mx:Button id="air" label="Air" click="currentState='airState'" />
            <mx:Button id="water" label="Water" click="currentState='waterState'" />
        </mx:HBox>
        <mx:CheckBox label="Helicopter" color.airState="0xFF0000"/>
        <mx:CheckBox label="Motorcycle" color.landState="0xFF0000" />
        <mx:CheckBox label="Car" color.landState="0xFF0000" />
        <mx:CheckBox label="Airplane" color.airState="0xFF0000"/>
        <mx:CheckBox label="Train" color.landState="0xFF0000" />
        <mx:CheckBox label="Boat" color.waterState="0xFF0000"/>
        <mx:CheckBox label="Submarine" color.waterState="0xFF0000"/>
    </mx:VBox>
</mx:Application>

EXAMPLE (5): State-Specific Properties and Event Handlers

BEFORE - Classic states syntax (O'Reilly Programming Flex 2):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:states>
        <mx:State name="enabled">
            <mx:SetProperty target="{textinput}" name="enabled" value="{true}" />
            <mx:SetEventHandler target="{button}" name="click"
                handler="currentState=''" />
            <mx:SetProperty target="{button}" name="label" value="Disable" />
        </mx:State>
    </mx:states>
    <mx:HBox id="hbox">
        <mx:Button id="button" label="Enable" click="currentState='enabled'" />
        <mx:TextInput id="textinput" enabled="false" text="example text" />
    </mx:HBox>
</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <m:states>
        <m:State name="disabledState"/>
        <m:State name="enabledState"/>
    </m:states>
    <mx:HBox>
        <mx:Button id="button" label="Enable" label.enabledState="Disable"
            click="currentState='enabled'"
            click.enabledState="currentState=''" />
        <mx:TextInput enabled="false" enabled.enabledState="true"
             text="example text" />
    </mx:HBox>
</mx:Application>

The state-specific property value syntax can also be used with object based properties…

EXAMPLE (6): Object-Based Properties

BEFORE - Classic states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:states>
      <mx:State name="glow">
        <mx:SetProperty target="{b}" name="filters">
            <mx:value>
                <mx:Array>
                    <mx:GlowFilter />
                </mx:Array>
            </mx:value>
        </mx:SetProperty>
        </mx:State>
    </mx:states>

    <mx:Button label="Button" id="b" click="currentState=currentState=='glow'?'':'glow'">
        <mx:filters>
            <mx:DropShadowFilter distance="9" />
        </mx:filters>
    </mx:Button>

</mx:Application>

AFTER - Inline states syntax:

<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009">
    <m:states>
      <m:State name="default"/>
      <m:State name="glow"/>
    </m:states>

    <mx:Button label="Button" click="currentState=currentState=='glow'?'':'glow'">
        <mx:filters>
            <mx:DropShadowFilter distance="9" />
        </mx:filters>

        <mx:filters.glow>
            <mx:GlowFilter/>
        </mx:filters.glow>
    </mx:Button>
</mx:Application>

<!-- Alternatively the above code could be written using
     state specific nodes -->

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:m="http://ns.adobe.com/mxml/2009"
xmlns:mx="library:ns.adobe.com/flex/halo">
    <m:states>
      <m:State name="default"/>
      <m:State name="glow"/>
    </m:states>

    <mx:Button label="Button" click="currentState=currentState=='glow'?'':'glow'">
        <mx:filters>
            <mx:DropShadowFilter distance="9" includeIn="default" />
            <mx:GlowFilter includeIn="glow"/>
        </mx:filters>
    </mx:Button>
</mx:Application>

When state-specific attributes are used with composite states (Overlays), along with mutually exclusive states, the order of precedence is as follows:

  • Overlay-specific values in order of declaration of the conditions take priority.
  • State-specific values.
  • Default value.

State Groups

If several states within a stateful document are quite similar, and are commonly used together when setting state-specific properties, a "state group" can be created. State groups are essentially state macros that can be used directly within an includeIn or excludeFrom, or a state-specific property's scope.

For example, given this case:

<m:states>
  <m:State name="A"/>
  <m:State name="B"/>
  <m:State name="C"/>
  <m:State name="D"/>
</m:states>

...

<mx:Button label.A="SomeLabel" label.C="SomeLabel" label.D="SomeLabel"/>
<mx:Button includeIn="A,C,D"/>
<mx:Button label.A="SomeLabel" label.B="SomeLabel"/>
<mx:Button includeIn="A,B"/>

If one discoveres a pattern of treating a set of states identically in some cases, a state group representing these states can be created and referred to as appropriate.

<m:states>
  <m:State name="A" stateGroups="G1,G2"/>
  <m:State name="B" stateGroups="G2"/>
  <m:State name="C" stateGroups="G1"/>
  <m:State name="D" stateGroups="G1"/>
</m:states>

...

<mx:Button label.G1="SomeLabel"/>
<mx:Button includeIn="G1"/>
<mx:Button label.G2="SomeLabel"/>
<mx:Button includeIn="G2"/>

The stateGroups attribute accepts a comma delimited list of valid state identifiers (whitespace ignored).

Custom Creation and Destruction Policies

itemCreationPolicy

Historically if one wanted a custom creation policy (immediate vs. deferred instantiated) for a state-specific component instance they would utilize the creationPolicy attribute of the AddChild override.

To provide for a similar level of control, a per-item based creation policy language attribute (itemCreationPolicy) will be introduced.

Supported values for itemCreationPolicy are ItemCreationPolicy.DEFERRED, ItemCreationPolicy.IMMEDIATE. The itemCreationPolicy language attribute can be specified on any MXML object supporting the includeIn and excludeFrom attributes.

An item creation policy of ItemCreationPolicy.DEFERRED is the default, the instance is created when needed.

An item creation policy of ItemCreationPolicy.IMMEDIATE means that the object will be created as soon as the owning document context is initialized.

EXAMPLE (7.1): Item Creation Policy

BEFORE - Classic states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:states>
        <mx:State name="newTextInput">
            <mx:AddChild relativeTo="{checkbox1}" position="after" creationPolicy="all">
                <mx:TextInput id="textinput" />
            </mx:AddChild>
        </mx:State>
    </mx:states>
    <mx:VBox id="vbox">
        <mx:CheckBox id="checkbox1" label="One" />
        <mx:CheckBox id="checkbox2" label="Two" />
        <mx:Button id="button" label="Click" click="currentState='newTextInput'" />
    </mx:VBox>
</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <mx:states>
        <mx:State name="default"/>
        <mx:State name="newTextInput"/>
    </mx:states>
    <mx:VBox id="vbox">
        <mx:CheckBox label="One" />
        <mx:TextInput includeIn="newTextInput" itemCreationPolicy="immediate"/>
        <mx:CheckBox label="Two" />
        <mx:Button id="button" label="Click" click="currentState='newTextInput'" />
    </mx:VBox>
</mx:Application>

itemDestructionPolicy

By default, after the framework initially creates a state-specific instance, the instance is cached indefinitely, even after switching to a state where the item is excluded.

An optional itemDestructionPolicy language attribute is provided as a means of ensuring a state-specific instance is destroyed upon leaving a state in which it exists. Typically it is more efficient to allow items to be cached, however there are times, such as for rarely visited states, that you may wish not to take the memory hit for a cached instance.

Supported values for itemDestructionPolicy are "never" and "auto". The itemDestructionPolicy language attribute can be specified on any MXML object supporting the includeIn and excludeFrom attributes.

An item destruction policy of "never" is the default, all state-specific instances are realized when appropriate and cached indefinitely.

An item destruction policy of "auto" means that the object will be removed from the DOM and all document and framework references to the object cleared, upon leaving a state where the instance exists.

In the example below, the text input instance will be released upon switching to then from, the 'newTextInput' state:

EXAMPLE (7.2): Item Destruction Policy

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" layout="absolute">
    <mx:states>
        <mx:State name="default"/>
        <mx:State name="newTextInput"/>
    </mx:states>
    <mx:VBox id="vbox">
        <mx:CheckBox label="One" />
        <mx:TextInput includeIn="newTextInput" itemDestructionPolicy="auto"/>
        <mx:CheckBox label="Two" />
        <mx:Button id="button" label="Click" click="currentState='newTextInput'" />
    </mx:VBox>
</mx:Application>

Some Additional Examples

Here is an example that ties everything together. A real world use case (Flex Media Chat, www.flashcomguru.com)...

EXAMPLE (8): Flex Media Chat Window

BEFORE - Classic states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:states>
        <mx:State name="chatState">
            <mx:SetProperty target="{mainPanel}" name="width" value="80%"/>
            <mx:SetProperty target="{mainPanel}" name="height" value="80%"/>
            <mx:RemoveChild target="{submit}"/>
            <mx:RemoveChild target="{female}"/>
            <mx:RemoveChild target="{hbox1}"/>
            <mx:RemoveChild target="{hbox2}"/>
            <mx:RemoveChild target="{username}"/>

            <mx:AddChild relativeTo="{mainPanel}" position="lastChild">
                <mx:VBox width="100%" height="100%" verticalGap="8" verticalAlign="middle" 
                    horizontalAlign="left" id="vbox2">
                    <mx:HDividedBox width="100%" height="100%" liveDragging="true">
                        <Chat:ChatTextarea id="hist" editable="false" width="75%" height="100%" 
                            paddingLeft="3" focusAlpha="0" htmlText="{chat.chatHistory}">
                        </Chat:ChatTextarea>
                        <mx:List id="userlist" height="100%" width="25%" itemRenderer="usersRenderer"
                            dataProvider="{chat.dpUsers}"></mx:List>
                    </mx:HDividedBox>

                    <mx:HBox width="100%" verticalAlign="middle" horizontalAlign="center" id="hbox6">
                    <mx:HBox width="75%" verticalAlign="middle" horizontalAlign="center" id="hbox5">
                        <mx:ColorPicker id="picker" toolTip="Font color"/>
                        <mx:TextInput width="100%" id="msg" paddingLeft="3" focusAlpha="0"
                            keyUp="checkSend(event)" color="{picker.selectedColor}" />
                        <mx:Button label="Send" id="send" click="sendMsg()"/>
                    </mx:HBox>
                    <mx:HBox width="25%" verticalAlign="middle" horizontalAlign="center" id="hbox7">
                    </mx:HBox>
                    </mx:HBox>
                </mx:VBox>
            </mx:AddChild>
            <mx:SetProperty target="{text1}" name="text" value="FlexMediaChat"/>
            <mx:RemoveChild target="{vbox1}"/>
            <mx:SetProperty target="{hbox4}" name="width" value="75%"/>
            <mx:SetStyle target="{mainPanel}" name="horizontalAlign" value="left"/>
            <mx:SetStyle target="{text1}" name="color"/>
            <mx:RemoveChild target="{image1}"/>
            <mx:AddChild relativeTo="{hbox8}" position="lastChild" target="{image1}"/>
            <mx:RemoveChild target="{text1}"/>
            <mx:AddChild relativeTo="{hbox8}" position="lastChild" target="{text1}"/>
            <mx:RemoveChild target="{hbox4}"/>
            <mx:AddChild relativeTo="{hbox9}" position="lastChild" target="{hbox4}"/>
            <mx:SetProperty target="{hbox9}" name="width" value="100%"/>
            <mx:AddChild relativeTo="{hbox4}" position="lastChild">
                <mx:Spacer width="80%" height="100%"/>
            </mx:AddChild>
        </mx:State>
    </mx:states>

    <mx:Panel width="410" height="210" layout="vertical" id="mainPanel" horizontalAlign="center" 
        headerHeight="10" verticalAlign="middle" fontSize="12" paddingTop="10" paddingLeft="10" 
        paddingRight="10"paddingBottom="10" verticalScrollPolicy="off" horizontalScrollPolicy="off">

                <mx:HBox width="97%" id="hbox9">
                    <mx:HBox width="97%" id="hbox4" horizontalAlign="left">
                        <mx:Image source="assets/internet-group-chat.png" id="image1"/>
                        <mx:Text text="User Login" fontWeight="bold" fontSize="18" id="text1"
                            selectable="false" color="#333333"/>
                    </mx:HBox>
                    <mx:HBox id="hbox8" horizontalAlign="right">
                    </mx:HBox>
                </mx:HBox>


                <mx:VBox height="92%" width="95%" horizontalScrollPolicy="off" id="vbox1">
                <mx:Spacer width="100%" height="6"/>
                    <mx:HBox width="100%" id="hbox1">
                            <mx:Text text="Username:"/>
                            <mx:TextInput
                                id="username" width="269" focusAlpha="0" enter="connect(event)"/>

                    </mx:HBox>
                    <mx:HBox width="100%" id="hbox2">
                        <mx:RadioButtonGroup id="gender"/>
                        <mx:Text text="Gender:"/>
                        <mx:Spacer width="16" height="100%"/>
                        <mx:Image id="maleicon" source="assets/male_login.png"/>
                        <mx:RadioButton label="male" groupName="gender" selected="true" id="male"/>
                        <mx:Image id="femaleicon" source="assets/female_login.png"/>
                        <mx:RadioButton label="female" groupName="gender" id="female" 
                            selected="false"/>
                    </mx:HBox>
                    <mx:Spacer width="100%" height="7"/>
                    <mx:HBox width="100%" id="hbox3" horizontalAlign="right">
                        <mx:Image source="assets/dialog-warning.png" id="warning"/>
                        <mx:Text color="#e60000" id="status" text="Text"/>
                        <mx:Spacer width="100%"/>
                        <mx:Button label="Connect" click="connect( event )"
                            id="submit" width="110" height="34" icon="@Embed('assets/go-next.png')"/>
                    </mx:HBox>
                </mx:VBox>

    </mx:Panel>

</mx:Application>

AFTER - Inline states syntax:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" m:version="4">
    <m:states>
        <m:State name="defaultState"/>
        <m:State name="chatState"/>
    </m:states>

    <mx:Panel width="410" width.chatState="80%" height="110" height.chatState="80%" layout="vertical"
        id="mainPanel" horizontalAlign="center" horizontalAlign.chatState="left"  headerHeight="10"
        verticalAlign="middle" fontSize="12" paddingTop="10" paddingLeft="10" paddingRight="10" 
        paddingBottom="10" verticalScrollPolicy="off" horizontalScrollPolicy="off">

                <mx:HBox width="97%" width.chatState="100%">
                    <mx:HBox width="97%" width.chatState="80%" id="hbox4" horizontalAlign="left">
                        <mx:Image source="assets/internet-group-chat.png" id="image1"/>
                        <mx:Text text="User Login" text.chatState="FlexMediaChat" fontWeight="bold"
                            fontSize="18" id="text1" selectable="false" color="#333333" 
                            color.chatState=""/>
                         <mx:Spacer width="80%" height="100%" includeIn="chatState"/>
                    </mx:HBox>
                    <mx:HBox horizontalAlign="right">
                        <mx:Reparent target="{image1}" includeIn="chatState"/>
                        <mx:Reparent target="{text1}" includeIn="chatState"/>
                    </mx:HBox>
                    <mx:Reparent target="{hbox4}" includeIn="chatState"/>
                </mx:HBox>


                <mx:VBox height="92%" width="95%" horizontalScrollPolicy="off"  
                    includeIn="defaultState">
                <mx:Spacer width="100%" height="6"/>
                    <mx:HBox width="100%" includeIn="defaultState">
                            <mx:Text text="Username:"/>
                            <mx:TextInput id="username" width="269" focusAlpha="0" 
                                enter="connect(event)" includeIn="defaultState"/>
                    </mx:HBox>
                    <mx:HBox width="100%" includeIn="defaultState">
                        <mx:RadioButtonGroup/>
                        <mx:Text text="Gender:"/>
                        <mx:Spacer width="16" height="100%"/>
                        <mx:Image id="maleicon" source="assets/male_login.png"/>
                        <mx:RadioButton label="male" groupName="gender" selected="true" id="male"/>
                        <mx:Image source="assets/female_login.png"/>
                        <mx:RadioButton label="female" groupName="gender" selected="false"
                            includeIn="defaultState"/>
                    </mx:HBox>
                    <mx:Spacer width="100%" height="7"/>
                    <mx:HBox width="100%" horizontalAlign="right">
                        <mx:Image source="assets/dialog-warning.png" id="warning"/>
                        <mx:Text color="#e60000" id="status" text="Text"/>
                        <mx:Spacer width="100%"/>
                        <mx:Button label="Connect" click="connect( event )"
                            id="submit" width="110" height="34" icon="@Embed('assets/go-next.png')"
                            includeIn="defaultState"/>
                    </mx:HBox>
                </mx:VBox>

                <!-- chatState content -->
                <mx:VBox width="100%" height="100%" verticalGap="8" verticalAlign="middle" 
                    horizontalAlign="left" includeIn="chatState">
                    <mx:HDividedBox width="100%" height="100%" liveDragging="true">
                        <Chat:ChatTextarea editable="false" width="75%" height="100%" 
                            paddingLeft="3" focusAlpha="0" htmlText="{chat.chatHistory}">
                        </Chat:ChatTextarea>
                        <mx:List height="100%" width="25%" itemRenderer="usersRenderer"
                            dataProvider="{chat.dpUsers}"></mx:List>
                    </mx:HDividedBox>

                    <mx:HBox width="100%" verticalAlign="middle" horizontalAlign="center">
                    <mx:HBox width="75%" verticalAlign="middle" horizontalAlign="center" >
                        <mx:ColorPicker id="picker" toolTip="Font color"/>
                        <mx:TextInput width="100%" id="msg" paddingLeft="3" focusAlpha="0"
                            keyUp="checkSend(event)" color="{picker.selectedColor}" />
                        <mx:Button label="Send" id="send" click="sendMsg()"/>
                    </mx:HBox>
                    <mx:HBox width="25%" verticalAlign="middle" horizontalAlign="center">
                    </mx:HBox>
                    </mx:HBox>
                </mx:VBox>

    </mx:Panel>

</mx:Application>

And lastly, a Flex 4 button skin example, noting first the classic syntax, followed by the inline syntax.

EXAMPLE (9): Flex 4 Button Skin

BEFORE - Classic states syntax:

<?xml version="1.0" encoding="utf-8"?>
<Skin xmlns:mx="http://www.adobe.com/2006/mxml">
    <states>
        <mx:State name="up" />
        <mx:State name="over">
            <mx:SetProperty target="{s}" name="color" value="0x6666BB" />
            <mx:SetProperty target="{e2}" name="color" value="0xF0F0F0" />
        </mx:State>
        <mx:State name="down" basedOn="over">
            <mx:SetProperty target="{e1}" name="color" value="0xBBBBDD" />
            <mx:SetProperty target="{e2}" name="color" value="0xBBBBDD" />
        </mx:State>
        <mx:State name="disabled" >
            <mx:SetProperty target="{s}" name="color" value="0xAAAAAA" />
            <mx:SetProperty target="{e2}" name="color" value="0xF0F0F0" />
        </mx:State>
    </states>
    <content>
        <mx:Graphic left="0" top="0" right="0" bottom="0"
                scaleGridLeft="6" scaleGridTop="6" scaleGridRight="44" scaleGridBottom="14">
            <mx:Rect width="50" height="20" radiusX="6" radiusY="6">
                <mx:fill>
                    <mx:SolidColor id="s" color="0x666666" />
                </mx:fill>
            </mx:Rect>
            <mx:Rect width="48" height="18" x="1" y="1" radiusX="5" radiusY="5">
                <mx:fill>
                    <mx:LinearGradient rotation="90">
                        <mx:GradientEntry id="e2" color="0xFFFFFF" alpha="0.8" />
                        <mx:GradientEntry id="e1" color="0xDEDEDE" alpha="0.6" />
                    </mx:LinearGradient>
                </mx:fill>
            </mx:Rect>
        </mx:Graphic>
        <ContentHolder horizontalCenter="0" verticalCenter="1" left="10" right="10" 
            content="{data.content}" layout="flex.layout.HorizontalLayout" />
    </content>
</Skin>

AFTER - Inline states syntax:

<Skin xmlns:mx="library:ns.adobe.com/flex/halo" 
   xmlns:m="http://ns.adobe.com/mxml/2009" >
    <m:states>
        <m:State name="up" />
        <m:State name="over"/>
        <m:State name="down"/>
        <m:State name="disabled" />
    </m:states>
    <content>
        <mx:Graphic left="0" top="0" right="0" bottom="0"
                scaleGridLeft="6" scaleGridTop="6" scaleGridRight="44" scaleGridBottom="14">
            <mx:Rect width="50" height="20" radiusX="6" radiusY="6">
                <mx:fill>
                    <mx:SolidColor color="0x666666" color.over="0x6666BB"
                      color.down="0x6666BB" color.disabled="0xAAAAAA"/>
                </mx:fill>
            </mx:Rect>
            <mx:Rect width="48" height="18" x="1" y="1" radiusX="5" radiusY="5">
                <mx:fill>
                    <mx:LinearGradient rotation="90">
                        <mx:GradientEntry color="0xFFFFFF" color.down="0xBBBBDD"
                         color.over="0xF0F0F0" color.disabled="0xF0F0F0" alpha="0.8" />
                        <mx:GradientEntry color="0xDEDEDE" color.down="0xBBBBDD"
                          alpha="0.6" />
                    </mx:LinearGradient>
                </mx:fill>
            </mx:Rect>
        </mx:Graphic>
        <ContentHolder horizontalCenter="0" verticalCenter="1" left="10" right="10" 
            content="{data.content}" layout="flex.layout.HorizontalLayout" />
    </content>
</Skin>

B Features


Stateful XML/E4X Nodes - There is an ask to allow descendants of XML and XMLList tags to support the new states syntax. If the feature is green-lit at a later date, an associated mini-spec will be published.

Stateful Model Nodes - There is an ask to allow descendants of MXML datamodel (mx:Model) nodes to support the new states syntax. If the feature is green-lit at a later date, an associated mini-spec will be published.

Enhanced Runtime API - As a fit and finish task, it would be prudent to update the runtime states API to follow closer to the way the new syntax works. e.g. A helper would be provided that would allow a developer to setPropertyForState(object, property, state) for instance. Or specifically set the include or exclude list for a given component instance. Essentially an API divorced from the notion of the current runtime States and Overrides model. When the work is done on this feature, a mini-spec will be circulated.

Additional Implementation Details


Implementation Design and Implications

A Phased Approach

As mentioned previously, the states concept as first introduced in Flex 2.x and carried forward in Flex 3.0 will be promoted to a first-class MXML language feature for Flex 4.0. This means, for instance, that state-specific objects will no longer be limited to visual children (UIComponents).

In order to get to a point where our tooling products can begin utilizing the new language features, we will phase in the functionality in stages.

  1. Introduce the new syntax, and retrofit the compiler to be 'state-aware'. Initially the compiler will lean on our existing States infrastructure and will automatically generate the states and overrides to represent the dynamic state-specific content. The current IOverride based classes will be modified and extended as needed during this phase to account for the new functionality.
    \ \ During this phase it is expected that the current runtime API to manipulate states will remain intact (e.g. developers will still be able to selectively create and overrides and add/insert them into state override arrays.) The special attributes includeIn, excludeFrom, and state-specific attribute syntax are not valid at runtime. \ \

  2. The second phase will be focused on allowing other custom components (non UIComponents) to leverage the states language features, (e.g. any component implementing an IStateClient2 interface for instance) such that state changes relative to that component can be interpreted in a custom manner. UIComponent will be migrated to utilize the more generic interface, and we will explore further runtime representations of state change (beyond the state and override runtime objects) to improve on runtime performance. Additionally we will enhance the runtime states APIs to be more consistent with the new syntax. An addendum to this specification (a mini-spec) will be distributed prior to development detailing the changes in the second phase of work.

Phase One Compiler and Framework Changes

Compiler Changes

The MXML sub-compiler will be enhanced with direct support for states as follows:

  • During the interface compilation phase, the set of states will be noted and used for validation in successive compilation phases.

  • During the implementation compilation phase, the compiler will collect and coallesce all scattered shared objects (including their parent context), and state-specific attribute values. IDs for shared object parents will automatically be generated via MxmlDocument.ensureId() and slots allocated for AddItems targets. Additionally, all Reparent tags will be parsed and noted. All special attributes (includeIn, excludeFrom, and state-specific node properties) are detected and handled primarily within the ComponentBuilder module. All state-specific objects and properties are registered with the root MxmlDocument and its underlying stateful model prior to code generation.

  • Any data-bound state-specific values will be modified such that the binding target becomes the property or style override instance, rather than the object instance.
  • The compiler will flag syntactic errors when/as detected:
    • Any ambiguous or invalid object DOM issue (e.g. An instance is reparented to two locations at once) will be flagged during the builder phase.
    • Any reference to a non-declared state will be considered invalid.
    • All state-specific values will be type checked.
  • During the code generation phase, state related code and initialization will be generated within the component's initialize() routine, consisting of the collection of states and overrides gathered in the earlier phase. State-specific object instances that are shared between states will be declared once and shared between AddItems instances.
  • In order to determine what currentState semantics to adhere to at runtime (e.g. whether or not the null state is valid for a given stateful component), the compiler will automatically ensure all Flex 4 stateful component instances support the IStateClient2 interface. The presence of this interface at runtime dictates Flex 4 runtime states semantics.
SDK/API Changes
  • All overrides will be modified to allow for their 'destination' and/or 'target' properties to accept a string based id (vs. binding reference), this is consistent with the approach that AddItems uses, and allows for reduction of the number of binding instances per state.
  • An itemCreationPolicy will be introduced to allow for per display object creation policy, since the equivalent AddChild mechanism is no longer applicable.
  • In order to allow for any element of an array based property to make use of state-specific instancing, we will make use of the new Flex 4 override AddItems.
  • All overrides will be modified to allow for initialization from a properties descriptor, this will allow for a more compact form of state related code generation.

Nice To Have:

  • The set of available overrides will be expanded to provide for batch property and style setting (e.g. SetProperties, SetStyles). This is primarily only a modest performance related change, to help minimize the number of overrides within a given state.

Prototype Work


The bulk of the necessary compiler changes have been prototyped and passed along to the compiler team for review. An analysis of the changes with regards to performance was also done, using our stock performance automation tests, with good results.

Compiler Work


Please refer to the preceding #Implementation Design and Implications section.

Web Tier Compiler Impact


Not Applicable.

Flex Feature Dependencies


This feature is dependent on any state-related features such as Overlays, Effects, Transitions.

Backwards Compatibility


Syntax changes

The legacy states syntax will be deprecated in favor of what is detailed here. A mechanism will be provided to utilize the classic states syntax if desired but only by opting out of the Flex 4 model altogether (e.g. via -compatibility-version or by removing any MXML 2009 (Flex 4) namespace declarations).

Behavior

Not applicable

Warnings/Deprecation

A deprecation related warning will be issued if the use of Override children of states are used within a Flex 4 document.

Accessibility


Not Applicable.

Performance


The new syntax as interpreted by the compiler results in generated code comparable to the current Flex 3 runtime states model, so no performance regressions or injections should be introduced (e.g. the IOverride based classes are still utilized at runtime to accomplish state changes).

Globalization


Not Applicable.

Localization


Compiler Features

The following state syntax related error and warning messages have been added to compiler_en.properties and require translation:

StateResolutionError - Cannot resolve state '$(name)'.

AmbiguousStateFilterError - The includeIn and excludeFrom keywords may not be specified concurrently.

...more will be added here as necessary.

There are no help description or linker message additions at this time.

Framework Features

Not Applicable.

Issues and Recommendations


  • What about validation, and other XML legalities, around the new syntax? The 'dot' character is a valid in XML attributes so the documents will be well-formed. However, as with Flex 2 and Flex 3, MXML continues to not be compatible with validation due to the dynamic nature of the language. e.g. Custom component tags, custom styles, etc. We aren't necessarily making things worse. An additional note on the subject, XAML chose to utilize dot notation to scope attributes to parent containers with their "attached properties" feature, so there is a precedent.
  • When porting from the old syntax to new, specifically SetStyle instances, it appears that the new syntax encourages setting inline styles? Isn't this a bad thing? If there is heavy use of styles in a stateful document, the developer is strongly encouraged to migrate their styles to a stylesheet, and apply state-specific class names to 'styleName' instead. Flex 4 will also most likely support pseudo-selectors that would be state aware which will allow for authoring state-aware style cascades.

Documentation


The new states functionality represents a significant overhaul to the way stateful documents and components are authored with MXML. An equally as significant documentation revision around the view states feature is required, detailing not only the new language keywords, but also providing comprehensive examples.

QA


Suggested focus areas and test cases to consider (not exhaustive):

  • Components without 2009 language namespace (MXML 4) declared should support only legacy states syntax.
  • Components with 2009 language namespace (MXML 4) declared should support only newer states syntax.
  • includeIn property can only reference states previously declared within the mx:states array.
  • excludeFrom property can only reference states previously declared within the mx:states array.
  • Test that includeIn/excludeFrom work on any UIComponent instance that is an instance of the pseudo-array property 'children' (dom child or a parent container or another UIComponent).
  • Test that includeIn/excludeFrom works on any array element node (ContentHolder child, filters, graphics tag segments, etc.).
  • Test that includeIn/excludeFrom, when used on a scalar object property value, generates an error message.
  • State names are limited to identifiers that can be both valid XML attributes (per XML 1.0 specification), and valid AS3 identifiers (e.g. limited to primarily alpha numeric characters with some exceptions (underscore, etc)).
  • State-specific event-handlers.
  • State-specific effect triggers.
  • State-specific styles.
  • State-specific styles should be able to 'reset' a style to null, such that cascade applies it's value (vs. default style). e.g. color="0xFF0000" color.stateA=""
  • State-specific properties where value is a binding instance.
  • State-specific attribute syntax cannot be used with 'special' built-in language attributes (frameRate, id, etc.).
  • Test that state-specific attribute syntax works on object based properties and when child node attributes syntax is used (vs. inline xml attributes).
  • Historically State property and style overrides utilizing binding, would not update after a binding changes (only when state changed). We should ensure this legacy 'bug' is addressed with the newer changes.
  • State specific properties.
  • The Reparent tag must not describe invalid DOM (circular references, invalid child/parent relationships).
  • The Reparent tag can only reparent a target to another location that accepts the type of instance.
  • A state-specific node cannot be reparented to multiple parents within the same state.
  • Test that if currentState is null by default, the first defined state becomes the 'initial' value of currentState automatically.


Related

Wiki: Flex 4
Wiki: Gumbo Component Architecture
Wiki: MXML 2009

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.