Sorting and Manipulating Shopping Cart Data


ArrayCollections have methods built in which support sorting, filtering, and manipulating their data. The ability to manipulate the data is done using the concept of a "cursor." A cursor is a position indicator, which allows direct access to any particular item in the collection. This allows for easily manipulating items within the collection. Among the controls you have with a cursor are methods for stepping from one item to the next; finding a particular item; adding, removing, or editing an item; etc. All of this is available natively to the ArrayCollection class, meaning you do not need to write verbose loops to achieve any of these goals.

Note

Cursors are not unique to ArrayCollections, they are available to any of the classes which implement the ICursorView interface. For more information on interfaces, please refer to the About Interfaces section of the Creating and Extending Flex 2 Components documentation.


Before you can use a cursor on an array collection, the data in the collection needs to be sorted. To do this, you will make use of the Sort and SortField classes, and the sort property of the ArrayCollection. Take a look at a simple sort example:

var prodSort:Sort = new Sort(); var sortField:SortField = new SortField("prodName"); prodSort.fields=new Array(sortField); myArrayCollection.sort = prodSort; myArrayCollection.refresh(); 


Here, you start by creating an instance of the Sort class, in this case, named prodSort. Next, an instance of the SortField class (named sortField) is created. The SortField constructor requires only a single argument, that being which property in the object should the collection be sorted by. Three other optional arguments can also be passed, indicating whether the sort should be case insensitive, should it be a descending sort, and should it be a numeric sort. All three optional arguments have a default value of false. The example sort specifies the collection should be sorted by prodName (the name of the product), and uses the default values for the optional arguments.

A Sort can have several sort fields (for example, you could sort by category then price), which is why the fields property of the Sort class requires that an array of SortFields be specified. If you only want to sort on a single field, create an array with only one SortField within it, as the example does.

Next, the Sort instance is set as the value for the sort property of the ArrayCollection. Lastly, the collections refresh() method is called, which instructs the collection to execute its sort.

Tip

When specifying multiple SortFields, the order in the array is the order in which the sort fields would be applied, so, to sort by category and then price, your code would look like this:


var prodSort:Sort = new Sort(); var sortField1:SortField = new SortField("catID"); var sortField2:SortField = new SortField("listPrice"); prodSort.fields=new Array(sortField1, sortField2); 


In this next exercise, you will write the logic to add items to the users shopping cart. Using a cursor, you will check to see if the item is already in the cart, and if it is, update the quantity, rather than re-adding the item.

1.

Open the ShoppingCart.as file in the valueObjects folder.

Alternatively, you can open ShoppingCart.as from your Lesson06/start/valueObjects directory, and save it into your flexGrocer/valueObjects directory. This is the shopping cart class that you built in the last lesson. In this lesson, you will add functionality to the class so the shopping cart e-commerce transactions can be managed.

2.

After the existing import statement, import the ArrayCollection class from the package mx.collections.

import mx.collections.ArrayCollection; 


This enables you to use the ArrayCollection class in the ShoppingCart class that you are defining here.

3.

Below the last import statement, add an import statement that will import the IViewCursor interface from the package mx.collections.

import mx.collections.IViewCursor; 


The cursor is available through the use of the IViewCursor interface. In order to work with a cursor, you will need to have access to this interface.

4.

Right after the class keyword, define a [Bindable] public property with the name of aItems and a data type of ArrayCollection. Use the new keyword to assign a new instance of the ArrayCollection class into your aItems property.

public class ShoppingCart {    [Bindable]    public var aItems:ArrayCollection = new ArrayCollection(); 


This instantiates an ArrayCollection object with the name of aItems. You will use this ArrayCollection to track all the objects in the shopping cart.

5.

Define a [Bindable] public property with the name of total and a data type of Number. It will be used as the default value for an empty shopping cart, so set the value to 0, as shown:

[Bindable] public var total:Number=0; 


You will update this variable any time a user adds an item to the cart with the price of the item. This will enable you to track the total cost of the end user's order.

6.

Define a private property with the name of cursor and a data type of IViewCursor. The ShoppingCart class should look like this so far:

public class ShoppingCart {    [Bindable]    public var aItems:ArrayCollection = new ArrayCollection();    [Bindable]    public var total:Number=0;    private var cursor:IViewCursor;    public function addItem(item:ShoppingCartItem):void{       trace(item.product);    } } 


IViewcursor is an interface available from the collections package, which includes the ArrayCollection class. By defining a private variable here, you can use the methods of this interface.

7.

Locate the addItem() method of the ShoppingCart class and remove the trace statement. Use the addItem() method of the ArrayCollection class to add the ShoppingCartItem to the aItems ArrayCollection:

public function addItem(item:ShoppingCartItem):void{    aItems.addItem(item); } 


In the last lesson, you built a ShoppingCartItem class to hold any data associated with items in a shopping cart. This class has properties to hold the Product (an instance of the Product class), the quantity (an integer) and the subtotal (a Number derived by multiplying the quantity by the price). When the user clicked the Add To Cart button, you passed the ShoppingCartItem to this method. The addItem() method is similar to the push() method you used on the Array class in the last task: it adds the object to the end of the ArrayCollection.

Tip

In the next step you will be asked to locate a specific VBox instance. Use the Outline view to find named object instances.

8.

Switch back to EComm.mxml and locate the cartBox VBox. Directly after the LinkButton control, add a List control. Assign the List control an id of cartView, specify cart.aItems as the dataProvider, and set the width to 100%.

<mx:List    dataProvider="{cart.aItems}"   width="100%"/> 


The List control is specifically designed to handle complex data structures. In this case, you are passing the ArrayCollection to the List control. You will see only that a ShoppingCartItem is added to the cart, not which one specifically. This is the default way to display an item. If more than one Product is added, all will be displayed in the List control (the cart).

9.

Save and run the application. Click the Add To Cart button for the Buffalo.

You should see the items that you click appear in the cart, as shown in the following example:

Each time you click an item, data binding is fired and the List control is automatically updated. Anytime the underlying data structure changes, the ArrayCollection is "smart" enough to automatically update the display.

The desired behavior is to have only new items added to the cart. If an item is clicked more than once, it should update the quantity of the item. You will use the IViewCursor functionality to make this work.

10.

Return to the ShoppingCart.as file and locate the addItem() method. Delete the existing code inside this method and call the manageAddItem() method. Pass the item parameter to the manageAddItem() method.

public function addItem(item:ShoppingCartItem):void{    manageAddItem(item); } 


Each time the user clicks the Add To Cart button, the cart needs to determine if the Product is already in the cart, or not. If the Product isn't already there, the item should be added, but if it is there, the quantity of the ShoppingCartItem the user is trying to add should be added to the existing quantity in the cart for that item.

Rather than having an un-wieldy block of code for all of this logic in the addItem () method, you will instead create a number of smaller methods to implement this logic. The manageAddItem () method will be the gateway into the logical process.

11.

Add a new private method with the name of manageAddItem(). The method should accept an argument of type ShoppingCartItem, and return void. Within the method, add conditional logic that tests whether the item is already in the cart. The logic to search through the cart will be implemented in a method you will soon write called isItemInCart(). If the item is not in the cart, call the addItem () method of the ArrayCollection as you had previously done. If it is, call another soon to be written method, called updateItem().

private function manageAddItem(item:ShoppingCartItem):void{    if (isItemInCart(item)){      updateItem(item);    }else{      aItems.addItem(item);    } } 


This demonstrates good architecture, in which you set up different functionality that you will use over and over again in different methods.

12.

Create the isItemInCart() method, and be sure it accepts an argument named item as a instance of the ShoppingCartItem class. The method should return a Boolean. Within the method, create a new variable local to the method with the name of sci, which will hold a matched ShoppingCartItem, if there is one.

private function isItemInCart(item:ShoppingCartItem):Boolean{    var sci:ShoppingCartItem = getItemInCart(item);    if(sci == null){       return false;    } else {       return true;    } } 


When you write the getItemInCart() method, you will build it so that if it finds the item, it returns it; otherwise, it will return null. If the item is not found, sci will be null, so isItemInCart will return a value of false. If something is found, sci will not be null, and therefore the method will return true.

Next, you need to create the getItemInCart() method, which will use a cursor to find an item already in the collection.

13.

Create the getItemInCart() method, which takes an argument of type ShoppingCartItem named item, and returns a ShoppingCartItem. Within this method, you will instantiate the cursor property you defined earlier, using the createCursor() method of the ArrayCollection.

private function getItemInCart(item:ShoppingCartItem):ShoppingCartItem{    cursor = aItems.createCursor(); } 


You need to search through the entire cart to check whether the item the user is adding is already in the cart. The ShoppingCartItem that the user wants to add is being passed to this method; if it is already in the cart, the method will return the item there. As you learned earlier, before a cursor can be used to search through a collection, the collection first needs to be sorted. In the next few steps, you will sort the aItems collection.

14.

Within the getItemInCart() method, call the soon to be written sortItems() method. Build a skeleton for this method directly below the getItemInCart() method.

private function getItemInCart(item:ShoppingCartItem):ShoppingCartItem{    cursor = aItems.createCursor();    sortItems(); } private function sortItems():void{ } 


The aItems sort will indicate which field everything should be ordered by. In effect, it tells the cursor how to search through the cart. As you want to verify that the cart only contains one ShoppingCartItem for each product, you will sort on the product property. To facilitate effective reuse, the sorting functionality is encapsulated in its own method.

15.

In the sortItems() method, instantiate a new Sort instance with the name of prodSort. Next, instantiate a new SortField instance with the name of sortField, set it equal to a new SortField, and pass the product data field name as a parameter. Finally set the fields property of the prodSort object equal to a new Array passing the SortField as the parameter.

private function sortItems():void{    var prodSort:Sort = new Sort();    var sortField:SortField = new SortField("product");    prodSort.fields=new Array(sortField); } 


Tip

Flex Builder should have automatically imported both the Sort and SortField classes when you created this function. If it did not, be sure to import mx.collections.Sort and mx.collections.SortField.

This sort will be performed on the properties of the shopping cart items in the aItems collection, and enables the cursor to move through this data. The fields property specifies an array of fields on which to sort. In this case, you are sorting on a single field, the product inside of the ShoppingCartItem class. As you learned earlier, there are a number of optional arguments you could pass to the SortField constructor, but in this case, the default values are being used, so these can be omitted.

16.

Still in the sortItems() method, add the Sort to the sort property of the aItems ArrayCollection, then call the refresh() method of the aItems ArrayCollection. The final sortItems() method should look as shown:

private function sortItems():void{    var prodSort:Sort=new Sort();    var sortField:SortField=new SortField("product");    prodSort.fields=new Array(sortField);    aItems.sort=prodSort;    aItems.refresh(); } 


When this method is called, it defines how the collection will be sorted, and then executes the sort. As you learned earlier, this is required before a cursor can be used on a collection.

17.

Return to the getItemInCart() method. Immediately after the call to the sortItems() method, pass the item parameter to the cursor's findFirst() method, and store the results in a Boolean variable named found.

var found:Boolean = cursor.findFirst(item); 


In this step, you use the findFirst() method of the cursor to search through the collection of ShoppingCartItems looking for a match. The findFirst () method requires an object be passed to it. The property within the object is used to determine the name of the property in the item, on which you are looking for a match. The value of the objects property specifies the value to match. In this case, you are instructing the cursor to search through the product properties of each ShoppingCartItem, and to find the first Product object whose value matches the Product object in the passed in ShoppingCartItem. If a match is found, the method will return a value of TRue. If no match is found, a value of false will be returned. Importantly, the cursor will stop on the matching record.

Tip

In addition to findFirst(), the cursor also has the findAny() and findLast() methods. Any of these three could be used in the code, but because your logic will ultimately prevent more than ShoppingCartItem for each Product from being added, findFirst() seems a logical choice.

18.

In the getItemInCart() method add a conditional statement to test if found is true. If TRue, create a new ShoppingCartItem with the name of sci, which references the current property of the cursor. Add an else statement that will return a value of null. After the conditional, return sci.

if(found){    var sci:ShoppingCartItem = cursor.current as ShoppingCartItem; }else{    return null; } return sci; 


The current property of the cursor will return the entire object at the present position of the cursor, which will be the ShoppingCartItem you found using the cursor's findFirst() method. If findFirst() was successful, the cursor will stop on that record, and the current property will remain at that position. Therefore, you can access the ShoppingCartItem at that position and eventually update the quantity for that duplicate item, using cursor.current. The final getItemInCart() method should look like this:

private function getItemInCart(item:ShoppingCartItem):ShoppingCartItem{    cursor = aItems.createCursor();    sortItems();    var found:Boolean = cursor.findFirst(item);    if(found){       var sci:ShoppingCartItem = cursor.current as ShoppingCartItem;    }else{       return null;    }    return sci; } 


19.

Create a skeleton for the updateItem() method, which accepts an argument named item of type ShoppingCartItem. The method will return void. On the first line of the method, define a local variable with the name of sci, data typed to a ShoppingCartItem, which is equal to cursor.current cast as a ShoppingCartItem.

private function updateItem(item:ShoppingCartItem):void{    var sci:ShoppingCartItem = cursor.current as ShoppingCartItem; } 


Because the cursor has not been moved since it was used to check if the item was in the cart, cursor.current still refers to the matched item. The sci variable will always be populated because this method is called only if there has already been a match in the cart. If the sci variable is null, this method will not be called, and a new item will be added to the cart using the addItem() method.

The cursor.current object must be cast as a ShoppingCartItem instance because in the ActionScript definition the IViewCursor's current property is data typed as Object.

20.

Still in the updateItem() method, update the quantity property of the sci object to its current value plus the value that is located in the existing aItems ArrayCollection.

sci.quantity += item.quantity; 


Remember, whenever a new item is added to the cart, you hard coded the quantity value in the ShoppingCartItem to 1.

21.

Still in the updateItem() method and immediately after you set the quantity, call the reCalc() method of the sci ShoppingCartItem class. The final updateItem() method should look like this:

private function updateItem(item:ShoppingCartItem):void{    var sci:ShoppingCartItem = cursor.current as ShoppingCartItem;    sci.quantity += item.quantity;    sci.recalc(); } 


When you first created the ShoppingCartItem class, you added a method with the name of recalc() that created a subtotal property with the price of each product multiplied by each product. When you built the method, you hard coded the quantity to 1. It now makes sense to recalculate that value because you have just updated the quantity property to however many items the user has in their cart.

22.

Directly after the updateItem() method, create a skeleton for the calcTotal() method. Set the initial value of the total variable you declared previously to 0.

private function calcTotal():void{    this.total = 0; } 


In this method, you will loop over the entire shopping cart and update a total text field with the entire total of the end user's purchases. Initially, you need to set the value of the total variable to 0.

23.

Still in the calcTotal() method, create a skeleton of a for loop that will loop through the aItems ArrayCollection. Use the variable i as the iterator for the loop and type it to an int. Use the length property of aItems to return the length of the ArrayCollection, and use the ++ operator to increment the iterator.

for(var i:int=0;i<aItems.length;i++){ } 


This builds a simple loop that enables you to loop through the entire shopping cart. The loop will continue to execute as long as i (the iterator) is less then the length of the array. Each time the loop executes, the iterator is increased by 1 (++ is shorthand for this increase).

24.

Inside the loop, update the total variable with the subtotal of each item stored in the aItems array. Be sure to use the += operator so it will add the new value to the existing one. Use the getItemAt() method and pass it the value i to get a reference to each ShoppingCartItem. Your final calcTotal() method should look as follows:

private function calcTotal():void{    this.total = 0;    for(var i:int=0;i<aItems.length;i++){       this.total += aItems.getItemAt(i).subtotal;    } } 


This loops through the entire shopping cart, and updates the total variable by adding the subtotal (price* quantity) of each item in the cart to the current total. Now any time you need to calculate the total price of all the items, you can simply call this method.

25.

Locate the addItem() method and call the calcTotal() method you just wrote. The final addItem() method should look as follows:

public function addItem(item:ShoppingCartItem):void{    manageAddItem(item);    calcTotal(); } 


After a new item is added to the cart, it makes sense to update the total field, which is bound to the total text field that displays to the end user.

26.

Return to EComm.mxml and locate the cartBox VBox. Immediately after the Label control that says "Your Cart Total," add another Label control whose text property is bound to the total property in the cart object. Add an <mx:HBox> tag around the two Label controls.

<mx:HBox>    <mx:Label text="Your Cart Total:"/>    <mx:Label text="{cart.total}"/> </mx:HBox> 


This will create another Label control directly above the cart that will display the total cost of the cart, which you set in your calcTotal() method. Remember that you instantiated the ShoppingCart class as the cart in an earlier lesson.

27.

Save and run the application. Add Buffalo to the cart and you will see the cart total increases, as well as the item is only added once. Later, you will use a DataGrid to actually display more information (such as price, quantity and subtotal) for each item.




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