In this lesson, you will:
• Use states as the basis for implementing navigation
• Learn about two-way binding
• Work with the Form class
Imperative to any application is a navigation system. Users should be able to move easily around in an application and locate the functionality they need. Some navigation will be completely at the user’s discretion, such as clicking a button to move to the home page or the checkout process. Other navigation can be tightly controlled by the developer—for example, a checkout process in which users cannot proceed to the next screen until certain conditions are met on an existing screen.
In Lesson 3, “Laying Out the Interface,” you learned how you can use states to change the appearance of a component at runtime. In this lesson, you’ll take the same states idea, but instead of just changing the appearance, you’ll change which of several components are shown to the user, so that they can see only one child at a time. To implement this, you’ll use a container with a basic layout (so the children can all be positioned at the 0, 0 point), and use the includeIn
property, so only one of the children is available in each state.
In Lesson 8, “Using Data Binding and Collections,” you learned about data binding, which updates the view components when data changes. Flex 4 introduces a second type of data binding called two-way binding. Two-way binding is most effective when combined with user input forms.
In this type of data binding, view components are updated when data changes; however, the data is also updated when the component changes. You can think of regular data binding as moving in one direction: When the data changes, the view changes. You can think of two-way binding as bidirectional: If either changes, the other updates.
In practice, two-way binding is extremely easy to use. There is simply a syntax difference when declaring a control as bound to data.
To bind a TextInput to a piece of data using traditional data binding, your code would look like this:
<s:TextInput text="{someData}"/>
To use two-way binding, you simply prepend the expression with the @
symbol:
<s:TextInput text="@{someData}"/>
Two-way binding does have one limitation at this time: The types of both the source and destination must be the same. With traditional data binding, you can bind a variable declared as a Number to the text input of a Label, even though that Label is expecting a String instance. Traditional data binding will attempt to convert the Number to a String on your behalf. Two-way data binding cannot work unless both the source and destination are the same.
Throughout this chapter, you’ll be building the checkout process for the FlexGrocer application. In this process, users will be presented with a series of components to allow them to enter various types of information (shipping info, billing info) and a confirmation screen. The information users enter will be stored in a value object. Using two-way data binding, the value object can be populated automatically as the user fills out the form. The first step in this process is to create the OrderInfo object.
Alternatively, if you didn’t complete the previous lesson or your code is not functioning properly, you can import the FlexGrocer.fxp project from the Lesson14/start folder. Please refer to the appendix for complete instructions on importing a project should you skip a lesson or if you have a code issue you cannot resolve.
id
of shoppingCart
.
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
<services:CategoryService id="categoryService"/>
<services:ProductService id="productService"/>
<cart:ShoppingCart id="shoppingCart"/>
</fx:Declarations>
You’ll need access to the same shopping cart from both the shopping and checkout experience, so you’ll create it here in the main application, and pass it into the places where it is used.
shoppingCart
property with a binding to the shoppingCart
instance you created in the previous step.
<views:ShoppingView id="bodyGroup"
width="100%" height="100%"
groceryInventory="{productService.products}"
shoppingCart="{shoppingCart}" />
shoppingCart
property. Remove the equal sign and everything after it, so the property is declared, but not instantiated:
public var shoppingCart:ShoppingCart;
Since the cart is now passed in from the main application, you no longer want to create it here.
package valueObjects {
[Bindable]
public class OrderInfo {
public function OrderInfo() {
}
}
}
billingName
, billingAddress
, billingCity
, billingState
, billingZip
, cardType
, cardNumber
, cardExpirationMonth
, and cardExpirationYear
. Define all nine of these properties to use a String as a data type.
package valueObjects {
[Bindable]
public class OrderInfo {
public var billingName:String;
public var billingAddress:String;
public var billingCity:String;
public var billingState:String;
public var billingZip:String;
public var cardType:String;
public var cardNumber:String;
public var cardExpirationMonth:String;
public var cardExpirationYear:String;
public function OrderInfo() {
}
}
}
This completes the OrderInfo class. In the next exercise, you’ll create the CheckoutView component, which will use an instance of this class.
Next, you’ll create the CheckoutView component, which contains the views a user will experience in the checkout process.
<s:states>
tag (with a lower case s
in states), as you are addressing the states
property of this new component.
<s:layout>
<s:BasicLayout/>
</s:layout>
<s:states>
</s:states>
<s:State>
(capital S
) instances to define the states for the component. The first should have a name of customerInfo
, the second billingInfo
, and the third review
.
<s:states>
<s:State name="customerInfo"/>
<s:State name="billingInfo"/>
<s:State name="review"/>
</s:states>
Here you are defining the three states of this component, which will show the individual views of the Checkout process.
valueObject
of OrderInfo. Give the instance an id
of orderInfo
.
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
<valueObjects:OrderInfo id="orderInfo"/>
</fx:Declarations>
You now have an instance of the OrderInfo class that can be populated by the views to which it is passed. Next you’ll add a few methods to ease the process of navigating between the views.
currentView
, data typed as an int
, with a default value of 0
.
</s:states>
<fx:Script>
<![CDATA[
private var currentView:int=0;
]]>
</fx:Script>
<fx:Declarations>
This will be used to keep track of which views users are on, so when they click a next button, you know what view to show them next.
setViewByIndex
, which accepts an integer as a parameter and returns void
. In the method, create a switch
statement that will set the currentState
to customerInfo
if the value 1 is passed, billingInfo
if a 2 is passed, and review
if 3 is passed in.
private function setViewByIndex(index:int):void{
switch(index){
case 0:
currentState="customerInfo";
break;
case 1:
currentState="billingInfo";
break;
case 2:
currentState="review";
break;
}
}
handleProceed
that accepts an event
as an argument, and returns void
. In this method, increment the value of currentView
, and then pass currentView
to the setViewByIndex
method.
private function handleProceed( event:Event ):void {
currentView++;
setViewByIndex(currentView);
}
Now, whenever a proceed event is heard, the currentState will be changed to the next state in the order. Next, you’ll create the CustomerInfo view, and set it to be shown in the proper state.
orderInfo
data typed as valueObject
of the OrderInfo.
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import valueObjects.OrderInfo;
[Bindable]
public var orderInfo:OrderInfo;
]]>
</fx:Script>
<s:Label text="Checkout Page 1 of 3"/>
<s:Form>
tag.
<s:Label text="Checkout Page 1 of 3"/>
<s:Form >
<s:FormHeading label="Customer Information"/>
<s:FormItem label="Customer Name">
<s:TextInput/>
</s:FormItem>
<s:FormItem label="Address">
<s:TextInput/>
</s:FormItem>
<s:FormItem label="City">
<s:TextInput/>
</s:FormItem>
<s:FormItem label="State">
<s:TextInput/>
</s:FormItem>
<s:FormItem label="Zip">
<s:TextInput/>
</s:FormItem>
<s:FormItem>
<s:Button label="Continue"/>
</s:FormItem>
</s:Form>
text
property of this TextInput to be a two-way binding to the billingName
property of the orderInfo object.
<s:TextInput text="@{orderInfo.billingName}"/>
As discussed earlier in the lesson, this syntax will create a two-way binding, so the control will initially be populated with the value of the billingName
property of the orderInfo object, and if the user makes a change, the change will be stored automatically back in the orderInfo object.
billingAddress
, billingCity
, billingState
, and billingZip
.
<s:Form >
<s:FormHeading label="Customer Information"/>
<s:FormItem label="Customer Name">
<s:TextInput text="@{orderInfo.billingName}"/>
</s:FormItem>
<s:FormItem label="Address">
<s:TextInput text="@{orderInfo.billingAddress}"/>
</s:FormItem>
<s:FormItem label="City">
<s:TextInput text="@{orderInfo.billingCity}"/>
</s:FormItem>
<s:FormItem label="State">
<s:TextInput text="@{orderInfo.billingState}"/>
</s:FormItem>
<s:FormItem label="Zip">
<s:TextInput text="@{orderInfo.billingZip}"/>
</s:FormItem>
<s:FormItem>
<s:Button label="Continue"/>
</s:FormItem>
</s:Form>
Now your whole form is set to be bound to the billing properties of orderInfo.
click
hander on the Continue button, which calls the handleProceed
method and passes the event
as an argument. With your cursor after the closing parentheses, which are after the word event, press Ctrl-1 and choose Generate event handler.
proceed
.
protected function handleProceed(event:MouseEvent):void{
dispatchEvent( new Event( "proceed" ) );
}
proceed
and a type of flash.events.Event
.
<fx:Metadata>
[Event(name="proceed", type="flash.events.Event")]
</fx:Metadata>
This completes the CustomerInfo component. The complete source for this component should appear like the following code.
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Metadata>
[Event(name="proceed", type="flash.events.Event")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import valueObjects.OrderInfo;
[Bindable]
public var orderInfo:OrderInfo;
protected function handleProceed(event:MouseEvent):void{
dispatchEvent( new Event( "proceed" ) );
}
]]>
</fx:Script>
<s:Label text="Checkout Page 1 of 3"/>
<s:Form >
<s:FormHeading label="Customer Information"/>
<s:FormItem label="Customer Name">
<s:TextInput text="@{orderInfo.billingName}"/>
</s:FormItem>
<s:FormItem label="Address">
<s:TextInput text="@{orderInfo.billingAddress}"/>
</s:FormItem>
<s:FormItem label="City">
<s:TextInput text="@{orderInfo.billingCity}"/>
</s:FormItem>
<s:FormItem label="State">
<s:TextInput text="@{orderInfo.billingState}"/>
</s:FormItem>
<s:FormItem label="Zip">
<s:TextInput text="@{orderInfo.billingZip}"/>
</s:FormItem>
<s:FormItem>
<s:Button label="Continue" click="handleProceed(event)"/>
</s:FormItem>
</s:Form>
</s:Group>
Next, you’ll instantiate this component in the CheckoutView component.
width
and height
of 100%, bind the orderInfo object to the orderInfo
property of the component, listen for the proceed
event, and call the handleProceed
method you wrote in a previous exercise. Lastly, set this component to be shown only in the customerInfo state, by using the includeIn
attribute. The final code for CheckoutView should read like this:
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" width="400" height="300" xmln
s:valueObjects="valueObjects.*" xmlns:checkout="views.checkout.*">
<s:layout>
<s:BasicLayout/>
</s:layout>
<s:states>
<s:State name="customerInfo"/>
<s:State name="billingInfo"/>
<s:State name="review"/>
</s:states>
<fx:Script>
<![CDATA[
private var currentView:int=0;
private function setViewByIndex(index:int):void{
switch(index){
case 0:
currentState="customerInfo";
break;
case 1:
currentState="billingInfo";
break;
case 2:
currentState="review";
break;
}
}
private function handleProceed( event:Event ):void {
currentView++;
setViewByIndex(currentView);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
<valueObjects:OrderInfo id="orderInfo"/>
</fx:Declarations>
<checkout:CustomerInfo width="100%" height="100%"
orderInfo="{orderInfo}"
proceed="handleProceed( event )"
includeIn="customerInfo"/>
</s:Group>
<s:states>
<s:State name="shopping"/>
<s:State name="checkout"/>
</s:states>
width
and height
to 100%. Set the includeIn
attribute to only show this component in the checkout state. Set the includeIn
for the ShoppingView to be shopping
.
<views:ShoppingView id="bodyGroup"
width="100%" height="100%"
groceryInventory="{productService.products}"
shoppingCart="{shoppingCart}"
includeIn="shopping"/>
<views:CheckoutView width="100%" height="100%"
includeIn="checkout"/>
startCheckout
that accepts a MouseEvent
as a parameter and returns void
. The body of the function should set the currentState to checkout. Call this function from the click
handler of btnCheckout
.
protected function startCheckout(event:MouseEvent):void{
this.currentState="checkout";
}
...
<s:Button id="btnCheckout" y="10" right="10" label="Checkout"
click="startCheckout(event)"/>
Now your shopping application allows the user to add products to the shopping cart and to move to the checkout process.
The next step is to add a second page to the checkout process, which will allow the user to enter their billing info.
<fx:Metadata>
[Event(name="proceed", type="flash.events.Event")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import valueObjects.OrderInfo;
[Bindable]
public var orderInfo:OrderInfo;
protected function handleProceed(event:MouseEvent):void{
dispatchEvent( new Event( "proceed" ) );
}
]]>
</fx:Script>
Both components will use the orderInfo object, and both will broadcast a proceed
event to indicate the user has finished with this page.
text
property set to “Checkout Page 2 of 3”.
<s:Label text="Checkout Page 2 of 3"/>
<s:Label text="Checkout Page 2 of 3"/>
<s:Form>
<s:FormHeading label="Billing Information"/>
<s:FormItem label="Credit Card Type">
</s:FormItem>
<s:FormItem label="Card Number">
</s:FormItem>
<s:FormItem label="Expiration">
</s:FormItem>
<s:FormItem>
</s:FormItem>
</s:Form>
selectedItem
property to the orderInfo’s cardType
property. Set the requireSelection
property to true
. Add a child tag to the DropDownList to address the dataProvider
property. Inside the dataProvider, create an ArrayList that contains these five strings: American Express, Diners Club, Discover, Mastercard, and Visa.
<s:FormItem label="Credit Card Type">
<s:DropDownList selectedItem="@{orderInfo.cardType}"
requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>American Express</fx:String>
<fx:String>Diners Club</fx:String>
<fx:String>Discover</fx:String>
<fx:String>MasterCard</fx:String>
<fx:String>Visa</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:DropDownList>
</s:FormItem>
text
property has a two-way binding to the cardNumber
property of orderInfo:
<s:FormItem label="Card Number">
<s:TextInput text="@{orderInfo.cardNumber}"/>
</s:FormItem>
<s:FormItem label="Expiration">
<s:layout>
<s:HorizontalLayout/>
</s:layout>
</s:FormItem>
selectedItem
property to the cardExpirationMonth
property of orderInfo. Set requireSelection
to true
. For a dataProvider, create an array list that contains strings for the names of the twelve months.
<s:DropDownList selectedItem="@{orderInfo.cardExpirationMonth}"
requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>January</fx:String>
<fx:String>February</fx:String>
<fx:String>March</fx:String>
<fx:String>April</fx:String>
<fx:String>May</fx:String>
<fx:String>June</fx:String>
<fx:String>July</fx:String>
<fx:String>August</fx:String>
<fx:String>September</fx:String>
<fx:String>October</fx:String>
<fx:String>November</fx:String>
<fx:String>December</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:DropDownList>
selectedItem
property to the cardExpirationYear
property of orderInfo. Set requireSelection
to true
. For a dataProvider, create an array list that contains strings with the years 2011 through 2016.
<s:DropDownList selectedItem="@{orderInfo.cardExpirationYear}"
requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>2011</fx:String>
<fx:String>2012</fx:String>
<fx:String>2013</fx:String>
<fx:String>2014</fx:String>
<fx:String>2015</fx:String>
<fx:String>2016</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:DropDownList>
Proceed
, and a clickHandler
that calls the handleProceed
method and passes the event as an argument. The full CreditCardInfo component should read like this:
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Metadata>
[Event(name="proceed", type="flash.events.Event")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import valueObjects.OrderInfo;
[Bindable]
public var orderInfo:OrderInfo;
protected function handleProceed(event:MouseEvent):void{
dispatchEvent( new Event( "proceed" ) );
}
]]>
</fx:Script>
<s:Label text="Checkout Page 2 of 3"/>
<s:Form>
<s:FormHeading label="Billing Information"/>
<s:FormItem label="Credit Card Type">
<s:DropDownList selectedItem="@{orderInfo.cardType}"
requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>American Express</fx:String>
<fx:String>Diners Club</fx:String>
<fx:String>Discover</fx:String>
<fx:String>MasterCard</fx:String>
<fx:String>Visa</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:DropDownList>
</s:FormItem>
<s:FormItem label="Card Number">
<s:TextInput text="@{orderInfo.cardNumber}"/>
</s:FormItem>
<s:FormItem label="Expiration">
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<s:DropDownList selectedItem=
"@{orderInfo.cardExpirationMonth}" requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>January</fx:String>
<fx:String>February</fx:String>
<fx:String>March</fx:String>
<fx:String>April</fx:String>
<fx:String>May</fx:String>
<fx:String>June</fx:String>
<fx:String>July</fx:String>
<fx:String>August</fx:String>
<fx:String>September</fx:String>
<fx:String>October</fx:String>
<fx:String>November</fx:String>
<fx:String>December</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:DropDownList>
<s:DropDownList selectedItem="@{orderInfo.cardExpirationYear}"
requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>2011</fx:String>
<fx:String>2012</fx:String>
<fx:String>2013</fx:String>
<fx:String>2014</fx:String>
<fx:String>2015</fx:String>
<fx:String>2016</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:DropDownList>
</s:FormItem>
<s:FormItem>
<s:Button label="Proceed" click="handleProceed( event )"/>
</s:FormItem>
</s:Form>
</s:Group>
width
and height
to be 100%
. Bind the orderInfo
property to the orderInfo
instance. Add an event listener for the proceed
event that calls the handleProceed
method, and passes the event
object. Set the includeIn
to be billingInfo
.
<checkout:CreditCardInfo width="100%" height="100%"
orderInfo="{orderInfo}"
proceed="handleProceed( event )"
includeIn="billingInfo"/>
There is just one page left to create for the checkout process: the page that will let the user see their information and choose to either complete the order or go back and make changes.
orderInfo
of type OrderInfo
<fx:Script>
<![CDATA[
import valueObjects.OrderInfo;
[Bindable]
public var orderInfo:OrderInfo;
]]>
</fx:Script>
<s:Label text="Checkout Page 3 of 3"/>
width
of 100% and a height
of 90%
<s:Label text="Checkout Page 3 of 3"/>
<s:HGroup width="100%" height="90%">
</s:HGroup>
This HGroup will show the customer and billing information on the left and the shopping cart on the right.
<s:HGroup width="100%" height="90%">
<s:Form>
<s:FormHeading label="Review and Checkout"/>
<s:FormItem label="Name">
</s:FormItem>
<s:FormItem label="Address">
</s:FormItem>
<s:FormItem label="Card Type">
</s:FormItem>
<s:FormItem>
</s:FormItem>
</s:Form>
</s:HGroup>
billingName
property of the orderInfo object.
<s:FormItem label="Name">
<s:Label text="{orderInfo.billingName}"/>
</s:FormItem>
You don’t need a two-way binding here, because the user is not able to edit the object.
orderInfo.billingCity
with a comma, space, orderInfo
.billingState
, space, and then orderInfo
.billingZip
.
<s:FormItem label="Address">
<s:Label text="{orderInfo.billingAddress}"/>
<s:Label text="{orderInfo.billingCity}, {orderInfo.billingState}
{orderInfo.billingZip}"/>
</s:FormItem>
orderInfo.cardType
.
<s:FormItem label="Card Type">
<s:Label text="{orderInfo.cardType}"/>
</s:FormItem>
click
handlers for both to call methods you’ll write shortly called handleComplete
and handleEdit
.
<s:FormItem>
<s:Button label="Complete Order" click="handleComplete( event )"/>
<s:Button label="Edit Information" click="handleEdit( event )"/>
</s:FormItem>
If you prefer, you can use quick assist (Ctrl+1) to create these methods for you, as you have done in previous exercises.
Next, you’ll add an instance of CartGrid to show the user the items in their shopping cart. But first you’ll need to have a shopping cart property that will contain the data for their cart.
orderInfo
property, create a bindable public property named shoppingCart
of type ShoppingCart
.
import cart.ShoppingCart;
import valueObjects.OrderInfo;
[Bindable]
public var orderInfo:OrderInfo;
[Bindable]
public var shoppingCart:ShoppingCart;
Don’t forget to ensure that cart.ShoppingCart is imported.
width
and height
of 100%. Inside the VGroup, add an instance of your CartGrid component. Assign the CartGrid a width
and height
of 100%, and bind the shoppingCart
property of the grid to the shoppingCart
property of the Review class.
<s:VGroup width="100%" height="100%">
<components:CartGrid width="100%" height="100%"
dataProvider="{shoppingCart.items}"/>
</s:VGroup>
removeProduct
event from the cartGrid. Add an event handler that calls a function named removeProductHandler
and pass it the event object as an argument. Use quick assist to generate the function for you.
<s:VGroup width="100%" height="100%">
<components:CartGrid width="100%" height="100%"
dataProvider="{shoppingCart.items}"
removeProduct="removeProductHandler(event)"/>
</s:VGroup>
event.product
to its constructor. Then call the removeItem
method of the shoppingCart and pass it your newly created ShoppingCartItem.
protected function removeProductHandler(event:ProductEvent):void
{
var sci:ShoppingCartItem = new ShoppingCartItem( event.product );
shoppingCart.removeItem( sci );
}
Be sure you add an import for the ShoppingCartItem class.
<s:VGroup width="100%" height="100%">
<components:CartGrid width="100%" height="100%"
dataProvider="{shoppingCart.items}"
removeProduct="removeProductHandler(event)"/>
<s:Label text="Total: {shoppingCart.total}"/>
</s:VGroup>
handleComplete
method. Create and dispatch a new Eve
nt with the type completeOrder
.
protected function handleComplete(event:MouseEvent):void
{
dispatchEvent( new Event( 'completeOrder' ) );
}
handleEdit
method. Create and dispatch a new Event
with the type editInformation
.
protected function handleEdit(event:MouseEvent):void
{
dispatchEvent( new Event( 'editInformation' ) );
}
completeOrder
and editInformation
events.
<fx:Metadata>
[Event(name="editInformation", type="flash.events.Event")]
[Event(name="completeOrder", type="flash.events.Event")]
</fx:Metadata>
shoppingCart
of type ShoppingCart
.
[Bindable]
public var shoppingCart:ShoppingCart;
width
and height
to be 100%. Bind the orderInfo
attribute to the orderInfo valueObject
. Bind the shoppingCart
attribute to the shoppingCart valueObject. Add event handlers for the editInformation
and completeOrder
events, and use quick assist to create those methods. Lastly, set the includeIn
attribute to be review.
<checkout:Review width="100%" height="100%"
orderInfo="{orderInfo}"
shoppingCart="{shoppingCart}"
editInformation="handleEdit( event )"
completeOrder="handleComplete( event )"
includeIn="review"/>
handleEdit
method. In the body of the method, set the currentView to 0, and call setViewByIndex
, passing in the currentView.
protected function handleEdit(event:Event):void
{
currentView=0;
setViewByIndex(currentView);
}
Earlier, you built the currentView and setViewByIndex to allow for easily switching between states. By telling the CheckoutView component to go to view 0, you are sending them back to the CustomerInfo screen.
shoppingCart
property to shoppingCart
.
<views:CheckoutView width="100%" height="100%"
includeIn="checkout"
shoppingCart="{shoppingCart}"/>
The checkout process is almost complete. You still need an OrderEvent object to pass the OrderInfo back to the main application. You’ll create that in this next exercise. For now, if you save the files and run the application, you’ll see that you can add items to the cart, enter your information, step through to the confirmation page, and go back to edit the information.
order
of type OrderInfo
.
public var order:OrderInfo;
Be sure to import the valueObjects.OrderInfo class.
this.order
equal to the order
parameter, just after the call to the super class.
public function OrderEvent(type:String, order:OrderInfo, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this.order = order;
}
package events
{
import flash.events.Event;
import valueObjects.OrderInfo;
public class OrderEvent extends Event
{
public var order:OrderInfo;
public function OrderEvent(type:String, order:OrderInfo, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this.order = order;
}
public override function clone():Event{
return new OrderEvent(type, order, bubbles, cancelable);
}
}
}
handleComplete
method. In this method, create and dispatch an OrderEvent, with the type set to placeOrder
, and using the orderInfo
object to populate its order
property.
protected function handleComplete(event:Event):void
{
dispatchEvent( new OrderEvent( 'placeOrder', orderInfo ) );
}
Remember that you’ll need to import the OrderEvent class.
handleComplete
method, reset the checkout process by setting the currentView
to 0 and passing currentView
to setViewByIndex
. The final step of the method is to clear out the orderInfo
property by setting it equal to a new OrderInfo()
instance:
protected function handleComplete(event:Event):void
{
dispatchEvent( new OrderEvent( 'placeOrder', orderInfo ) );
currentView=0;
setViewByIndex(currentView);
orderInfo = new OrderInfo();
}
<fx:Metadata>
[Event(name="placeOrder",type="events.OrderEvent")]
</fx:Metadata>
handlePlaceOrder
and pass along the event object. Use the quick assist feature to have Flash Builder build this method for you.handlePlaceOrder
method. Inside the method, reset the shopping cart (by instantiating a new ShoppingCart in its place) and set the currentState
to shopping:
protected function handlePlaceOrder(event:OrderEvent):void
{
shoppingCart = new ShoppingCart();
this.currentState="shopping";
}
returnToShopping
. Pass an event object to the method. Use code assist to create this method.returnToShopping
method, set the currentState
to shopping:
protected function returnToShopping( event:MouseEvent ):void
{
this.currentState="shopping";
}
The only remaining problem is that users can start the checkout process even if they have no items in their cart. Although this isn’t really a problem, it can lead to user confusion. In the final step, you’ll dynamically enable/disable the checkout button, based on the contents of the cart.
<s:Button id="btnCheckout" y="10" right="10" label="Checkout" click="startCheckout(event)" enabled="{shoppingCart.total != 0}"/>
In this lesson, you have:
• Created a navigation structure to the application using states (pages 337–345)
• Used two-way binding to ease the process of sharing and editing data (pages 334–336)
• Implemented a full checkout process (pages 346–358)