Saturday, June 4, 2011

Flex 4: Working with States

Summary

Flex 4's new syntax simplifies working with application states. This article provides a tutorial introduction into UI state management with Flex 4, and includes a complete example.

Stateless Web applications aim to store as little state on the server as possible. Reducing, or even eliminating, reliance on server-side state makes it easier to implement a scalable infrastructure, at least in principle.

At the same time, only the simplest Web applications can function completely without state. Dispatching a request to an online weather reporting service may work in a purely stateless fashion, but more sophisticated applications must keep track of where a user is in a complex data entry process, possible data validation errors, and so on. Data-intensive applications may also need to cache a user's frequently accessed data in order to reduce response time.

Client-Side State

Instead of storing state data on the server, rich-client applications may choose to store such information on the client. A key benefit of rich-client frameworks is that they provide APIs and programming language constructs that make it easier to work with application state on the client. In addition to client-side collections APIs and language support for data processing, rich-client frameworks typically offer high-level constructs to manage application state related to display logic as well.

Consider, for example, a sign-in component typical of many Web applications: A user is presented with text input boxes to enter a user name and a password, as well as a button to initiate the sign-in. A login box may also provide a handy way for the user to create a new account. If the user chooses to register a new account, another view is presented with text boxes for additional information, such as first and last names, and so forth:

Click on "Need to register?" to toggle between UI states. To view the source code, right-click or control-click on the application.

In a traditional, server-generated HTML user interface, the server may return an entirely newly rendered page when a user chooses to register instead of signing in. By contrast, a rich-client can affect the change in the user interface entirely on the client side, without interaction from the server.

The simplest way to code that is to write display logic that inserts and removes new labels and text boxes in the UI at runtime, for instance, by modifying the UI's DOM in the browser. More sophisticated rich-client frameworks, however, provide a much handier abstraction to accomplish the same goal: UI states.

UI States in Flex

A UI state is the set of all components and component properties in effect in a component at any given point in time. Multiple UI states can be associated with a component: Switching from one state to another causes the rich-client runtime to add, remove, or modify components based on the differences between states. 

The login form, for instance, could have "login" and "register" states: The register state defines additional input boxes, changes the label of the button component from "Sign in" to "Register", and also causes the button click to invoke a register() method instead of signin().

In Flex, defining and working with application states has been possible since the 2.0 version of the Flex SDK. In Flex versions 2 and 3, states were defined inside a states array in an MXML document; inside each state definition, in turn, were declarations of what should be added, removed, or modified, related to some base state. It was also possible to build one state on another.

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

  <mx:Script>
      <![CDATA[
            private function signin():void {
                // Implementation of signin
            }

            private function register():void {
                // Implementation of register
            }
        ]]>
  </mx:Script>

  <mx:states>

    <mx:State name="Register">       

        <mx:AddChild relativeTo="{loginForm}" position="lastChild" creationPolicy="all">
            <mx:FormItem id="frmPasswordConfirm" label="Confirm:">
                <mx:TextInput id="passwordConfirm" displayAsPassword="true"/>
            </mx:FormItem>       
        </mx:AddChild>

        <mx:AddChild relativeTo="{loginForm}" position="lastChild">
            <mx:FormItem label="First name:" id="frmFirstName">
                <mx:TextInput id="firstName" width="220"/>
            </mx:FormItem>
        </mx:AddChild>

        <mx:AddChild relativeTo="{loginForm}" position="lastChild">           
            <mx:FormItem label="Last name:" id="frmLastName">
                <mx:TextInput id="lastName" width="220"/>
            </mx:FormItem>
        </mx:AddChild>

        <mx:AddChild relativeTo="{loginButton}" position="after">
            <mx:LinkButton label="Return to login" click="setCurrentUIState()"/>
        </mx:AddChild>           

        <mx:RemoveChild target="{registerLink}"/>           

        <mx:SetProperty target="{loginLabel}" name="text" value="Create a new account:"/>   
        <mx:SetProperty target="{loginButton}" name="enabled" value="false"/>
        <mx:SetProperty target="{loginButton}" name="label" value="Register"/>
           
     </mx:State>       

  </mx:states>
   
 <mx:Label text="Sign In:"
            fontWeight="bold" paddingLeft="12" fontSize="14"
            id="loginLabel" width="100%"/>
       
        <mx:Form id="loginForm" width="100%">

            <mx:FormItem label="Email:" id="frmEmail">
                <mx:TextInput id="email" width="220"/>
            </mx:FormItem>           

            <mx:FormItem label="Password:" id="frmPassword">
                <mx:TextInput id="password" displayAsPassword="true" width="220"/>
            </mx:FormItem>       
        </mx:Form>

        <mx:CheckBox id="rememberMe" label="Remember me on this computer"   
            paddingLeft="77" selected="true"/>

        <mx:ControlBar width="100%"    paddingLeft="84">
            <mx:Button label="Sign in" id="loginButton" click="signin()" enabled="false"/>           
            <mx:LinkButton label="Need to Register?" id="registerLink"
                click="currentState='Register'"/>           
        </mx:ControlBar>
</mx:VBox>

Listing 1: Login box with Flex 3

In the above Flex 3.0 implementation, AddChild and RemoveChild tags indicate that the runtime should add or remove child components, respectively, in the register state. The SetProperty tag, in turn, changes the button's label to "Register," and the SetEventHandler tag ensures that a register() method is invoked when the UI is the register state. Clicking on the LinkButton component toggles between the signin and register states.

While the Flex 2 and 3 state syntax works well, adding and removing components is rather cumbersome: For instance, you have to specify where new components are to be added apart from the new components' parents. UI components supporting a large number of states end up becoming unwieldy, with significant portions of related UI logic scattered across a large MXML file.

Flex 4's State Improvements

To simplify state definition, Flex 4 introduces a new states syntax. Although the various component states are still declared inside the states tag, the definition of each state is specified inline. To see how that works, consider the Flex 4 implementation of the login component. To make the Flex 4 implementation mirror the Flex 3 version as closely as possible, we didn't define separate component and skin files, a Flex 4 best practice:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/halo">
       
    <fx:Script>
        <![CDATA[
            private function signin(): void {
                 // Implementation of signin
            }

            private function register(): void {
                // Implementation of register
            }
        ]]>
    </fx:Script>
       
    <s:states>
        <s:State name="signin"/>
        <s:State name="register"/>
    </s:states>
       
    <s:Group id="mainGroup">
        <s:layout>
            <s:VerticalLayout/>
        </s:layout>
              
    <mx:Label text="Sign in:" fontWeight="bold" paddingLeft="12" fontSize="14" id="loginLabel" width="100%"/>
              
    <mx:Form id="loginForm" width="100%" paddingTop="3" paddingBottom="3">

        <mx:FormItem label="First name:" includeIn="register" id="firstNameItem" alpha="0.0">
            <s:TextInput id="firstName" width="220"/>
        </mx:FormItem>

        <mx:FormItem label="Last name:" includeIn="register" id="lastNameItem" alpha="0.0">
            <s:TextInput id="lastName" width="220"/>
        </mx:FormItem>

        <mx:FormItem label="Email:" id="emailItem">
            <s:TextInput id="email" width="220"/>                   
        </mx:FormItem>

        <mx:FormItem label="Password:" id="passwordItem">
            <s:TextInput id="password" displayAsPassword="true" width="220"/>
        </mx:FormItem>

        <mx:FormItem label="Confirm:" includeIn="register" id="confirmItem" alpha="0.0">
             <s:TextInput id="passwordConfirmation" displayAsPassword="true" width="220"/>          
        </mx:FormItem>
    </mx:Form>

    <s:Group>
        <s:layout>
            <s:HorizontalLayout paddingLeft="100"/>
        </s:layout>
        <s:Button label="Sign in" label.register="Register" id="loginButton"
            enabled="true" click.signin="signin()" click.register="register()"/>
                      
        <mx:LinkButton label="Need to register?"
            click="currentState = 'register'" includeIn="signin"/>
        <mx:LinkButton label="Return to signin"
            click="currentState = 'signin'" includeIn="register"/>
     </s:Group>
              
    <mx:CheckBox id="rememberMe" label="Remember me on this computer" paddingLeft="110"/>
 </s:Group>
</s:Application>

Listing 2: Login box with Flex 4 state syntax

Instead of AddChild and RemoveChild methods, components that should be visible in the register state only are added an includeIn="register" tag. You can specify a comma-separated list of state names for the includeIn tag. Flex 4 also provides an excludeFrom tag, if you need to declare what states a component should be removed from. This new syntax allows you to declare components in place, inside the parent.

Properties can similarly be set inline using a new dot-separated syntax. For example, the Button component's label property is defined as follows:
... label="Sign in" label.register="Register"
This tells Flex to set the button's label property to "Register" in the register state, and to "Sign in" in other states (the default). 

And listeners can be declared similarly: When the component is in the register state, the register() method is invoked on a button click, otherwise the signin() method is called:
click="signin()" click.register="register()"

The new component syntax makes it much easier to define sophisticated components with relatively straightforward, declarative code. The Flex 4 component states syntax also supports state groups as well as the ability to assign a new parent to a component. It is important to note that the new syntax only impacts MXML declarations—which is what the Flex compiler uses to generate ActionScript code. States can be defined in ActionScript as well, but that syntax does not change in Flex 4.

Flex gives you convenient ways not only to define component states, but also to specify transitions between states. To make the transition between the signin and register states smoother, this example includes a transition effect between those states. A forthcoming Artima article will describe Flex 4 transitions in greater detail.

No comments :

Post a Comment