Displaying the Shopping Cart with a DataGrid

When you left off in Lesson 8, you had the contents of your cart displayed in a List control with the ability to remove the current item you were viewing from the cart via a Remove button. You will switch this over to use a DataGrid to display the contents of the cart. The DataGrid control supports the syntax to allow for you to specify the columns explicitly through the DataGridColumn. This is done with the following syntax:

<mx:DataGrid ... >   <mx:columns>     <mx:DataGridColumn dataField=""...>     <mx:DataGridColumn...>     <mx:DataGridColumn...>   </mx:columns> </mx:DataGrid> 

The dataField is used to map the column in the data set to a given column. The order in which the DataGridColumns are listed is the order you will see the columns from left to right in the DataGrid. This is useful when you need to specify a different order to the columns than specified in the data set. Each DataGridColumn supports a large number of attributes that affect the DataGrid's rendering and interaction with the given column.


Open Cart.mxml from the flexGrocer/views/ecomm directory.

If you prefer, you can copy this file from the Lesson11/start/views/ecomm directory to your flexGrocer/views/ecomm directory.


Replace the <mx:List> tag with an <mx:DataGrid> tag. Set the width and height to 100%, set draggableColumns to false, and set editable to true.

<mx:DataGrid      dataProvider="{cart.aItems}" width="100%" height="100%"   editable="true" draggableColumns="false">   <mx:columns>   </mx:columns> </mx:DataGrid> 

You are specifying the editable as true because you will allow one of the columns to be changed by the user. If it is set to false, the whole DataGrid becomes read-only. You no longer need the labelFunction attribute for data formatting on the base control because it is the DataGridColumns that will specify how each piece of data is shown. For now, leave the code in the script block that defined the label function; you will return to it in a few steps. The DataGrid will remain bound to the same data set as before (cart.aItems). You also had to set the draggableColumns attribute to false because the default value is TRue, and you don't want the columns to be able to be moved around.


Define an <mx:DataGridColumn> for the product name and place it at the top of the column list. Set dataField to product, editable to false, and headerText to Product.

<mx:DataGridColumn dataField="product" headerText="Product"   editable="false" /> 

The headerText attribute will specify the text of the DataGridColumn header. If you don't specify this, it will take the value of the dataField attribute.


You could have optionally set the dataField attribute to product.prodName because the product field is actually a complex object available as a property of ShoppingCartItem. The DataGridColumn can resolve property.property references.

Because the editable attribute is set to TRue on the <mx:DataGrid> tag, you need to set it to false for each column you don't want to use for editing.


Define an <mx:DataGridColumn> for displaying the quantity and place it after the last <mx:DataGridColumn>. Set dataField to quantity and headerText to Quantity.

<mx:DataGridColumn dataField="quantity" headerText="Quantity" /> 

This column will be used to allow users to change the quantity of a specific product they want to buy.


Define an <mx:DataGridColumn> for displaying subtotals for each item and place it after the last <mx:DataGridColumn>. Set dataField to subtotal, editable to false, and headerText to Amount.

<mx:DataGridColumn dataField="subtotal" headerText="Amount"    editable="false" /> 

The column will show the cost for this specific product. You don't want the customer to be able to change this amount, so you are setting the editable attribute to false.


Save Cart.mxml. Run the EComm Application, add the Buffalo product to the shopping cart and click on 'View Cart'.

You can see the cart shown in a DataGrid. A small note is that the Product column is showing up as text in the DataGrid, even though it is a complex attribute in the data set because there is a toString() function declared on the Product value object. If this wasn't defined, you would see [Object Product]. You will look at how to better display a complex object in a second. For now, this is what you should see:

Add Inline Editing Control for DataGridColumn

In a DataGrid, you have the ability to specify that a column of the data shown can be changed by the user when focus is brought to the cell. This is done by setting the editable attribute to true. The default editing control for the column is a text field. It is possible to specify the editor to use when managing the data via the itemEditor attribute and the editorDataField, which specifies the attribute of the editor control used to manage changing of the value for the cell and which attribute on that control the data set should look at to get the changed value. The following are the built-in controls you can specify (full package names are needed unless imported into the containing page):

  • Button

  • CheckBox

  • ComboBox

  • DateField

  • Image

  • Label

  • NumericStepper

  • Text (Default)

  • TextArea

  • TextInput


You can also specify your own control if you desire, as long as it implements the IDropInListItemRenderer interface in its class definition.


Open the Cart.mxml you created in the previous exercise.

If you didn't finish the previous exercise, you can open Cart_1.mxml from Lesson11/intermediate and save it as Cart.mxml in your flexGrocer/views/ecomm directory.


In the <mx:DataGridColumn> tag that maps to the quantity, set the itemEditor to mx.controls.NumericStepper, editorDataField to value, and editable to true.

<mx:DataGridColumn dataField="quantity"   itemEditor="mx.controls.NumericStepper"   editorDataField="value" editable="true" headerText="Quantity" /> 

This now has the Quantity column being edited as a Numeric Stepper. The underlying value of the column is bound to the value attribute of the Numeric Stepper.


Save Cart.mxml. Run the EComm Application, add the Buffalo product to the shopping cart, and click 'View Cart'.

When you click in the Quantity column, you will notice that it doesn't open up as a free-form text field, but rather as a NumericStepper control.

Create an MXML Item Renderer for Displaying the Product

The default behavior of the DataGrid is to convert every value of the data set that is being shown into a String and then display it. However, when you are dealing with a complex object that is stored in the data set, another alternative is to create a custom item renderer that shows more information about the column. In this case, you are going to create a simple item renderer that shows the product's name and image.

When working with item renderers, you will find that there is an implicit public variable available to you in the item renderer called data, which represents the data of the row itself. You can use the data to bind your controls without having to worry about what column you are working with. When the DataGrid creates a column that has a custom item renderer associated with it, it creates a single instance of the cell renderer per row, so you don't have to worry about scoping between rows.


Create a new folder under the FlexGrocer project named renderer and one under that called ecomm.


Right-click on the ecomm folder you just created and choose New > MXML Component. In the New MXML Component dialog box, set the filename to ProductName.mxml, the base component to an HBox, and remove any width and height values and then click Finish.

This MXML file will define the layout of a given cell in the DataGrid. You are creating it in a separate file so that, if needed, it can be used on multiple DataGrid columns and/or multiple DataGrids.


Open ProductName.mxml.

The file should already be open in your Flex Builder workspace; if not, the file is located in your flexGrocer/renderer/ecomm directory.


In the <mx:HBox>, set the verticalScrollPolicy and horizontalScrollPolicy attributes to off.

<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"   verticalScrollPolicy="off" horizontalScrollPolicy="off"> 

This will keep the cell renderer from having scroll bars if the DataGrid is resized too small.


Place an <mx:Image> tag inside of the <mx:HBox> to display the product's thumbnail image. You will need to set the source attribute to a hard coded directory location, but the filename should be bound to the imageName of the product. That will make it look like assets/{data.product.imageName}.

<mx:Image source="{'assets/' +data.product.imageName}"/> 

You do not need to specify the height or width of the image because it will resize the column to fit the image.


The image location used is relative to the location where the main application is loaded from, not the location of the file that contains the <mx:Image> tag.


Place an <mx:Text> tag for product name in the <mx:HBox> below the <mx:Image> tag. Bind the text attribute to data.product.prodName. Set the height and width to 100%.

<mx:Text text="{data.product.prodName}" width="100%" height="100%"/> 


Save the ProductName.mxml file.

There is no need to test this component at this point because it is not assigned to the DataGrid yet.


Open the Cart.mxml you created in the previous exercise.

Alternately, you can open Cart_2.mxml from Lesson11/intermediate and save it as Cart.mxml in your flexGrocer/views/ecomm directory.


Update the <mx:DataGridColumn> with a dataField of product with a new attribute, itemRenderer, set to renderer.ecomm.ProductName.

<mx:DataGridColumn dataField="product" headerText="Product"   itemRenderer="renderer.ecomm.ProductName" editable="false"/> 

With the use of the itemRenderer attribute, you are overriding the default TextInput editor. You need to use the fully qualified class name to set your item renderer unless you have imported the class package that it exists in.


Update the <mx:DataGrid> with a new attribute variableRowHeight set to TRue.

<mx:DataGrid      dataProvider="{cart.aItems}" width="100%" height="100%"   editable="true" draggableColumns="false"   variableRowHeight="true"> 

It is necessary for you to set the variableRowHeight to true, so that Flex will resize the row's height to accommodate the thumbnail image.


This attribute can be used to allow for exploding details inside a DataGrid row. In this case, you can have summary data in one cell that if you click on an icon or button, the cell expands to show the new details.


Save Cart.mxml. Run the EComm Application, add the Buffalo product to the shopping cart and click on 'View Cart'.

Create an Inline MXML Item Renderer for Displaying the Remove Button

Another option for creating an item renderer is through the <mx:itemRenderer> tag, which allows you to declare and create the item renderer inline with the DataGridColumns. From a compiler perspective, doing an inline item renderer is the equivalent of building it in an external file (it actually compiles the code of the inline item renderer as a separate file internally). Inside the <mx:cellRenderer> tag, you will place an <mx:Component> tag, which defines the boundaries of the inline item renderer file from the rest of the page. Thus, the inside of the <mx:Component> tag will have its own scope that you will need to do imports, function declarations, and the like.


Although this will be very efficient from a coding perspective to build inline item renderers, it does not allow you to reuse the item renderers for other DataGrids. Good candidates are item renderers that are specific to one DataGrid only, such as action item controls.

Just like the item renderer you just created, this one will have access to the data variable, which will hold the reference to the row. In addition, you will also be able to talk out of the inline cell editor back into the page through the outerDocument scope. Note, however, that all functions and variables in the containing page that you want to reference must be declared as public, because it is really a component talking to another component.

For this example, you will look to replace the Remove button that is outside of the DataGrid with a Remove button inside of each row.


Open the Cart.mxml you created in the previous exercise.

Altermately, you can open Cart_3.mxml from Lesson11/intermediate and save it as Cart.mxml in your flexGrocer/views/ecomm directory.


Create a new <mx:DataGridColumn> to hold a Remove button at the bottom of the DataGrid column list. Set editable to false; otherwise, the cell would be able to receive focus. You also do not need to specify dataField, because there is no data you are mapping directly to.

<mx:DataGridColumn editable="false"> </mx:DataGridColumn> 

This will create the placeholder column in the DataGrid. We used a start and end <mx:DataGridColumn> tag because the item renderer definition will be placed inside it.


Place the <mx:itemRenderer> and <mx:Component> tags inside the <mx:DataGridColumn> tag.

<mx:itemRenderer>   <mx:Component>   </mx:Component> </mx:itemRenderer> 


Place an <mx:VBox> tag inside the <mx:Component> tag to provide a container for the Remove button.

<mx:itemRenderer>   <mx:Component>     <mx:VBox>     </mx:VBox>   </mx:Component> </mx:itemRenderer> 

When creating this inline item renderer we want to use the VBox to help us be able to center the button in the DataGrid no matter the size of the cell.


Place an <mx:Button> tag inside the VBox. Set the label to Remove and set the click event to call the removeItem() function on the containing page. You will need to use the outerDocument reference to call the function.

<mx:VBox>   <mx:Button     label="Remove"     click="outerDocument.removeItem(valueObjects.ShoppingCartItem(data));"/> </mx:VBox> 

In prior lessons, you used the Remove Button that was outside of the List to remove an item from the shopping cart. The remove function could simply look at the selectedItem in the List to determine which product to remove. Because you are building an inline item renderer you will need to change the method signature of the removeItem() function to accept a ShoppingCartItem instance that the row is pointing to. It is necessary for you to do this because the DataGrid will not always have a concept of a selected row that the code can remove.

You need to fully qualify the casting of the data property into the ShoppingCartItem because the import statements made at the top of the file are in a different scope than the inline item renderer.


As an alternative to the 'as' operator, you can convert an object instance from one type to another (as long as they are compatible) by simply wrapping the desired object instance in the ClassNameToConvertTo(object) syntax.


Change the method signature of removeItem to accept a ShoppingCartItem as an argument. Also, change the method to public.

public function removeItem(cartItem:ShoppingCartItem):void{ 

When the button is clicked, the item renderer will pass the cart item of the row it is on, so we need to add an argument to accept this cart item. We need to make this method public so that the code running inside the inline cell renderer can access it.


Change the first two lines of the removeItem() function, so that the Product to be removed from the cart is set equal to the argument passed in.

var prod:Product = cartItem.product; 

The whole function should now look like this:

public function removeItem(cartItem:ShoppingCartItem):void{    var prod:Product = cartItem.product;    var e:ProductEvent = new ProductEvent(prod,"productRemoved");    this.dispatchEvent(e); } 

You no longer need to build the product item from the selected row because the row is now calling this method and passes in the specific cart item.


Inside the script block, add an import for the mx.controls.dataGridClasses.DataGridColumn class.

import mx.controls.dataGridClasses.DataGridColumn; 

You need to update the labelFunction that you had for the List control so that it will work with a DataGrid. The method signature for labelFunctions on DataGrids are labelFunctionName(item:Object,.dataField:DataGridColumn).


In the Flex Builder integrated development environment (IDE), if you choose DataGridColumn from the list of classes that are presented after the : of your argument, the IDE will automatically import the class for you if it is not already present.


Add an argument to the end of the renderLabel() function to call dataField of type DataGridColumn.

private function renderLabel(item:ShoppingCartItem, dataField:DataGridColumn):String{ 

Because the DataGrid has multiple columns that can each have its own labelFunction, as well as share the same labelFunction, the additional argument is used to distinguish between which labelFunction is being used. If you know that your function will only be used on just one column, you can ignore this argument in your code.


Update the renderLabel() function to just return the subtotal of the item with a $ on the front. Change the name to renderPriceLabel().

For now, you want to put a simple mask on the price to represent the number as a dollar figure. The signature and functionality of the labelFunction is the same on the DataGrid as it is on the List.

private function renderPriceLabel(item:ShoppingCartItem, dataField:String):String{    return "$"+String(item.subtotal); } 


Update the <mx:DataGridColumn> with a dataField of subtotal with a new attribute of labelFunction set to renderPriceLabel.

<mx:DataGridColumn dataField="subtotal" headerText="Amount"   labelFunction="renderPriceLabel" editable="false"/> 

This will have the subtotal column use renderPriceLabel on each of the rows in the DataGrid.


Remove the old <mx:Button> that called the remove logic outside the DataGrid.

The final code for the Cart.mxml should look like the following:

[View full width]

<?xml version="1.0" encoding="utf-8"?> <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Metadata> [Event(name="productRemoved",type="events.ProductEvent")] </mx:Metadata> <mx:Script> <![CDATA[ import mx.controls.dataGridClasses.DataGridColumn; import events.ProductEvent; import valueObjects.ShoppingCart; import valueObjects.ShoppingCartItem; import valueObjects.Product; [Bindable] public var cart:ShoppingCart; private function renderPriceLabel(item:ShoppingCartItem, dataField: DataGridColumn) :String{ return "$"+String(item.subtotal); } public function removeItem(cartItem:ShoppingCartItem):void{ var prod:Product = cartItem.product; var e:ProductEvent = new ProductEvent(prod,"productRemoved"); this.dispatchEvent(e); } ]]> </mx:Script> <mx:DataGrid dataProvider="{cart.aItems}" width="100%" height="100%" editable="true" draggableColumns="false" variableRowHeight="true"> <mx:columns> <mx:DataGridColumn dataField="product" headerText="Product" itemRenderer="renderer.ecomm.ProductName" editable="false"/> <mx:DataGridColumn dataField="quantity" itemEditor="mx.controls.NumericStepper" editorDataField="value" editable="true" headerText="Quantity"/> <mx:DataGridColumn dataField="subtotal" headerText="Amount" editable="false" labelFunction="renderPriceLabel"/> <mx:DataGridColumn editable="false"> <mx:itemRenderer> <mx:Component> <mx:VBox> <mx:Button label="Remove" click="outerDocument.removeItem(valueObjects. ShoppingCartItem(data));"/> </mx:VBox> </mx:Component> </mx:itemRenderer> </mx:DataGridColumn> </mx:columns> </mx:DataGrid> </mx:VBox>


Save Cart.mxml. Run the EComm.mxml Application, add the Buffalo product to the shopping cart and click on 'View Cart'. Notice both the formatting on the Amount column and the Remove button in the shopping cart.

Update ShoppingCartItem with Set and Get Functions

One thing you might have noticed when you took the shopping cart out for a test run was that when you changed the quantity of the cart, the pricing didn't update. This is because changes to either price or quantity are not triggering the recalc() function you created on the ShoppingCartItem class.

ActionScript enables you to declare some behind-the-scenes functions that will fire whenever you attempt to access a property of a class. These are called custom set and get functions. In the function, you will place the keyword of either get or set in your function declaration that has the same name as the property you are trying to mask. Also, you will change the property to be a private variable that is named differently than before. It is recommended that you prefix it with an underscore. The three parts will follow this brief structure:

private var _insVar:uint; public function set insVar(qty:uint):Void public function get insVar():uint 


Open ShoppingCartItem.as from your flexGrocer/valueObjects directory.

If you prefer, you can copy this file from the Lesson11/start/valueObjects to your flexGrocer/valueObjects directory.


Change the quantity property declaration to have a prefix of _ and to be private.

private var _quantity:uint; 

We changed this property to be private so that no one can directly access this property outside the class itself.


Add a set quantity() function declaration and have it call the recalc() function.

public function set quantity(qty:uint):void{   _quantity = qty;   recalc(); } 

This will be called every time something changes quantity. This is where you want to call the recalc() function.


Add the get quantity() function declaration.

public function get quantity():uint{    return _quantity; } 


Add a public function called toString(), which will return type String. Have the function return a concatenation of the ShoppingCartItem quantity and its product's name.

public function toString():String{ return this.quantity.toString() + ": " + this.product.prodName; } 

This function will be used if Flex is asked to display the contents of the ShoppingCartItem. Currently this is done on the small cart view off the main product page.


Save ShoppingCartItem.as. Run EComm.mxml application, add the Buffalo product to the shopping cart and click on 'View Cart'. Change the quantity of the Buffalo item in your cart and see the subtotal change.

When you change the quantity of a cart item, you will see the updating of the price after you leave the column because the DataGrid is watching for changes to the data set to which it is assigned. The reason that you need to leave the column is that the updating of the data that DataGrid is managing does not happen until after you are finished working with the column. This is to avoid constant broadcasting of every change made to the column until the user is finished editing the value. Be aware that the cart total above the cart does not change just the price of the individual items in the cart. You will be updating the cart in a later lesson.

Adobe Flex 2.Training from the Source
Adobe Flex 2: Training from the Source
ISBN: 032142316X
EAN: 2147483647
Year: 2006
Pages: 225

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net