Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
This first exercise will improve the overall architecture of the application, but it will not add any functionality from the user’s point of view. In fact if everything is done properly, you’ll want the application to appear exactly as it did before you started. You will pull the application’s visual elements into a component, a view in terms of MVC architecture. FlexGrocer.mxml will begin to transform into the controller.
1. | Right-click the src folder in the FlexGrocer project that you used in Lesson 8, “Using Data Bindings and Collections,” and create a package named views. 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 Lesson09/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. It is a best practice to organize your components. In this case, the views folder will contain the views for your applications. |
2. | Right-click the views folder and then choose New > MXML Component. In the New MXML Component dialog box, set the name as ShoppingView, the base component as a Group, and the layout as Horizontal. Remove any width and height values, and then click Finish. |
3. | Insert an <fx:Script> block just after the closing </s:layout> tag. You will have an <fx:Script> block in this component. Some of the code you will copy from the FlexGrocer.mxml file, and other code you will write new. |
4. | From the FlexGrocer.mxml file, copy the bindable properties named groceryInventory and shoppingCart and paste them inside the Script block in the ShoppingView component. Change the scope of groceryInventory from a private variable to a public variable. [Bindable]
public var shoppingCart:ShoppingCart = new ShoppingCart();
[Bindable]
public var groceryInventory:ArrayCollection; |
5. | Add the imports for the ArrayCollection and ShoppingCart classes. The easiest way to do this is to put your cursor at the end of the class name (for instance, after the final t of cart), and press Ctrl+Spacebar. You won’t be prompted as to which class you want imported while importing the ArrayCollection class, but you will for the ShoppingCart class. This is because the only class that matches ArrayCollection is the ArrayCollection class, but Flash Builder will ask if you mean to use the ShoppingCart or ShoppingCartItem class, both of which start with ShoppingCart. Pressing Ctrl+Spacebar will force Flash Builder to use its code-completion feature, which in this case will automatically import the classes for you. <fx:Script>
<![CDATA[
import cart.ShoppingCart;
import mx.collections.ArrayCollection;
[Bindable]
public var shoppingCart:ShoppingCart = new ShoppingCart();
[Bindable]
public var groceryInventory:ArrayCollection;
]]>
</fx:Script>When you copied these variables into the component, they became properties of the component. Simply by using the var statement and defining the variables to be public, you are creating these as public properties of the components that can have data passed into them. This is no small matter. The basic building blocks of object-oriented programming are objects, properties, and methods. So knowing how to create properties is a very important piece of information. Later in this lesson, you will add functions to a component. Just as variables in a class are properties, so functions in the class are the methods of your components. |
6. | From the FlexGrocer application, cut the HGroup with the id of bodyGroup and all of its contents and paste it into your ShoppingView component, after the end of the <fx:Declarations> tag pair. In ShoppingView, remove the opening and closing HGroup tags, but leave the contents of this tag pair in place. You will no longer need the HGroup in place, as your ShoppingView component is set to have a HorizontalLayout. |
7. | From the FlexGrocer application, cut the VGroup set to be included in the expanded state (this VGroup holds a RichText and two Labels) and paste it into your ShoppingView component just above the VGroup with the id of cartGroup. Your resulting code should look like the following:
Code View:
Scroll
/
Show All <s:VGroup id="products" width="100%" height="150"
visible.cartView="false" width.cartView="0" height.cartView="0">
<s:Label id="prodName" text="Milk"/>
<s:Image scaleMode="letterbox"
source="@Embed('assets/dairy_milk.jpg')"
mouseOver="this.currentState='expanded'"
mouseOut="this.currentState='State1'"/>
<s:Label id="price" text="$1.99"/>
<s:Button id="add" label="Add To Cart" click=
"addToCart( groceryInventory.getItemAt( 0 ) as Product )"/>
<s:Button id="remove" label="Remove From Cart" click=
"removeFromCart( groceryInventory.getItemAt( 0 ) as Product )"/>
</s:VGroup>
<s:VGroup includeIn="expanded" x="200" width="100%">
<s:RichText text="{( groceryInventory.getItemAt( 0 ) as
Product ).description}" width="50%"/>
<s:Label text="Certified Organic" visible="{(groceryInventory.getItemAt
( 0 ) as Product ).isOrganic}"/>
<s:Label text="Low Fat" visible="{(groceryInventory.getItemAt( 0 ) as
Product ).isLowFat}"/>
</s:VGroup>
<s:VGroup id="cartGroup" height="100%"
width.cartView="100%">
<s:List id="cartList"
dataProvider="{shoppingCart.items}" includeIn="State1"/>
<s:Label text="Your Cart Total $0"/>
<s:Button includeIn="State1" height="17" label="View Cart"
click="handleViewCartClick( event )"/>
<s:DataGrid includeIn="cartView" requestedRowCount="4">
<s:columns>
<s:ArrayList>
<s:GridColumn dataField="dataField1"
headerText="Column 1"></s:GridColumn>
<s:GridColumn dataField="dataField2"
headerText="Column 2"></s:GridColumn>
<s:GridColumn dataField="dataField3"
headerText="Column 3"></s:GridColumn>
</s:ArrayList>
</s:columns>
<s:typicalItem>
<fx:Object dataField1="Sample Data"
dataField2="Sample Data"
dataField3="Sample Data"></fx:Object>
</s:typicalItem>
<s:ArrayList>
<fx:Object dataField1="data1"
dataField2="data1"
dataField3="data1"></fx:Object>
<fx:Object dataField1="data2"
dataField2="data2"
dataField3="data2"></fx:Object>
<fx:Object dataField1="data3"
dataField2="data3"
dataField3="data3"></fx:Object>
<fx:Object dataField1="data4"
dataField2="data4"
dataField3="data4"></fx:Object>
</s:ArrayList>
</s:DataGrid>
<s:Button includeIn="cartView" label="Continue Shopping"
click="this.currentState=''"/>
</s:VGroup>
|
8. | Cut the states block from the main application, and paste it in the component, between the closing <fx:Script> tag and the starting <fx:Declarations> tag. <s:states> <s:State name="State1"/> <s:State name="cartView"/> <s:State name="expanded"/> </s:states> The reality is that the order of where you place the states tag is unimportant, as long as you do not place it as the child of something other than the root node. |
9. | Cut the addToCart() and removeFromCart() methods from the main application, and paste them into your component in the <fx:Script> block. Use the code-completion feature to import the Product and ShoppingCartItem classes. Remove the trace ( shoppingCart ); statement from your addToCart() method. It is no longer needed. private function addToCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.addItem( sci );
}
private function removeFromCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.removeItem( sci );
}At this point, the full Script block should read as: <fx:Script>
<![CDATA[
import cart.ShoppingCart;
import cart.ShoppingCartItem;
import mx.collections.ArrayCollection;
import valueObjects.Product;
[Bindable]
public var shoppingCart:ShoppingCart = new ShoppingCart();
[Bindable]
public var groceryInventory:ArrayCollection;
private function addToCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.addItem( sci );
}
private function removeFromCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.removeItem( sci );
}
]]>
</fx:Script>These methods are called from some of the MXML tags you moved into the ShoppingView class, so they need to be moved into that component. |
10. | Copy the handleViewCartClick() method from the main application, and paste it into your component’s <fx:Script> block. private function handleViewCartClick( event:MouseEvent ):void {
this.currentState = "cartView";
}At this point, the full Script block should read as follows:
Code View:
Scroll
/
Show All <fx:Script>
<![CDATA[
import cart.ShoppingCart;
import cart.ShoppingCartItem;
import mx.collections.ArrayCollection;
import valueObjects.Product;
[Bindable]
public var shoppingCart:ShoppingCart = new ShoppingCart();
[Bindable]
public var groceryInventory:ArrayCollection;
private function handleViewCartClick( event:MouseEvent ):void {
this.currentState = "cartView";
}
private function addToCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.addItem( sci );
}
private function removeFromCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.removeItem( sci );
}
]]>
</fx:Script>
These methods are called from some of the MXML tags you moved into the ShoppingView class, so they need to be moved into that component. |
11. | Save the file. You have created your first MXML component. Now that the component is built, you will instantiate the new component from the main application. |
12. | Return to the FlexGrocer.mxml file and find the Label that shows the copyright mark. Just after that tag, type in <Shopp and choose views:ShoppingView from the code-hinting menu.
|
13. | Give the new tag an id of bodyGroup, and make it a self-closing tag. Add a width and a height of 100% to the tag. <views:ShoppingView id="bodyGroup" width="100%" height="100%" /> The code-hinting feature automatically added the namespace that references the views directory to your main application tag. The Application tag should now read like this: <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="handleCreationComplete( event )" xmlns:views="views.*"> |
14. | Change the handler on the btnCartView button from being defined as click.
Code View:
Scroll
/
Show All State1="handleViewCartClick( event )" to click="handleViewCartClick( event )"
The state definitions have all been moved to the ShoppingView class, so the main application no longer has any states other than the base state. For this reason, the click event handler should be defined without the explicit reference to State1. <s:Button id="btnCartView" y="10" right="90" label="View Cart"
click="handleViewCartClick( event )"/> |
15. | Still in FlexGrocer.mxml, change the handleViewCartClick() method so it changes the state of bodyGroup instead of the state of the main application. private function handleViewCartClick( event:MouseEvent ):void {
bodyGroup.currentState = "cartView";
}As mentioned in the previous step, the main application no longer has states. When the user clicks the View Cart button in the control bar, you need to change the state of the ShoppingView in order to show the cart. |
16. | Save the main application. It should now compile with no errors. However, you have not yet passed the data needed to render the products in the shopping view, so if you run the application, it will not show any products yet. To solve this problem, in the instantiation of the ShoppingView component, add an attribute that binds the groceryInventory property from the FlexGrocer application into the groceryInventory property of the ShoppingView component. <views:ShoppingView id="bodyGroup"
width="100%" height="100%"
groceryInventory="{groceryInventory}" />Now the ShoppingView should have the data it needs to render products as it did before. |
17. | Run the FlexGrocer.mxml file. You see that creating the component has not changed the functionality. It still renders a product that can be added and removed from the cart. |