In this lesson, you will:
• Create an ActionScript class to use as a value object
• Create ActionScript classes for a shopping cart
• Add functionality to the ShoppingCart and ShoppingCartItem classes
Objects are the core of any object-oriented language. So far you have used classes provided for you by Adobe; however, to accomplish anything of even marginal complexity in Flex, you need to be comfortable creating your own. Objects are the realization of classes. Another way to state this is that a class is a blueprint for an object that will be created. In this lesson, you will first create several classes and then use them throughout the application.
As mentioned at the end of Lesson 2, “Getting Started,” this book does not aspire to teach object-oriented programming (OOP), but every Flex developer needs at least a working knowledge of OOP terminology. So if you’re not familiar with terms like class, object, property, and method, now is a good time to take advantage of the hundreds, if not thousands, of OOP introductions around the web and in books.
You have already been building custom ActionScript classes in this book but may not have been aware of it because Flex initially hides this fact from you. When you build an application in MXML, you’re actually creating a new ActionScript class. Your MXML is combined with the ActionScript in the Script block, and a pure ActionScript class is created, which is then compiled into a SWF file for Flash Player. In the previous exercise, when you compiled FlexGrocer.mxml, a file named FlexGrocer-generated.as was created behind the scenes that contained the following code:
public class FlexGrocer extends spark.components.Application
You extended the Application class when you built FlexGrocer.mxml and Checkout.mxml. The same is true for every application you create using Flex.
If you wish to see the ActionScript created, you can add a compiler argument in Flash Builder. Navigate to Project > Properties > Flex Compiler > Additional compiler arguments, and add -keep-generated-actionscript to the end of the existing arguments. A folder named bin-debug/generated will be created automatically in your project, and many ActionScript files will be placed there. Your application files will be in the form Name-generated.as. Don’t forget to remove the compiler argument when you’ve finished exploring.
In the first exercise of this lesson, you will build a class directly in ActionScript, without relying on Flex to convert MXML into ActionScript. Ultimately, this will give you much more granular control over your final code and encourage code reuse.
Value objects, also called data transfer objects (DTOs), or just transfer objects, are objects intended to hold data. Unlike other objects you’ve used so far, such as Labels and DataGrids, value objects are free from any logic other than storing and retrieving their data. These objects are implemented as ActionScript classes.
The name data transfer object comes from the fact that DTOs are often used for data transfer to the back end (server) of an application, often for permanent storage in a database. In this lesson, you will build a value object for a grocery product, along with objects for both a ShoppingCart and a ShoppingCartItem.
Before you get started, you need to understand the basics of building an ActionScript class. A very simple class is shown here, and labeled for discussion:
On line A, the package represents the path where the class is stored. In this example, you know the file is stored in a valueObjects.grocery package. On your hard drive, this means that the ActionScript file is stored in the valueObjects/grocery directory under your project.
On line B, the class is named Fruit. This is the name used to represent the class throughout an application (much like DataGrid or Label), and it must correspond to the name of the file. The Fruit class will be stored in a Fruit.as file on your drive.
On line C, the properties of the class are declared. This particular class has only a single public property, named productName,
of type String. Multiple properties may be declared for any class.
Line D contains the constructor of the class. The constructor is called when a new object is instantiated from the class. The name of the constructor function must match the name of the class, which must match the name of the file. This function must be public, and it cannot have a return type listed.
In line E, the methods of the class are defined. This particular class has only a single method named toString()
, but multiple methods may be declared.
The terms method and function will often be used synonymously throughout the book. A function is a block of code that needs to be executed at some point in your application. A method is a function that belongs to a particular class, like the Fruit class here. In Flex it’s possible to create functions and methods; however, every function you create in this book can also appropriately be called a method.
Throughout the FlexGrocer application, you will need to display and manage typed data and send this data to different objects in the application. In this exercise, you’ll build a value object to hold information about a grocery product.
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 Lesson07/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.
First, this process created a package named valueObjects that you can now see in your Package Explorer. Next, it created a file named Product.as on your behalf. Finally, it populated that file with the required code for an ActionScript class.
Within the code, the words package
and class
are both keywords used in defining this class. Remember that this class will be a blueprint for many objects that you will use later to describe each grocery product.
[Bindable]
metadata tag on the line between the package
definition and the class statement.
package valueObjects {
[Bindable]
public class Product {
public function Product() {
}
}
}
The [Bindable]
metadata tag, when specified before the line with the class
keyword, means that every property in this class can be used in data binding (that is, it can be monitored for updates by various Flex controls). Instead of specifying the whole class as [Bindable]
, you can specify individual properties by locating the [Bindable]
metadata tag over each property. For this application, you want every property in this class to be bindable.
catID
and the type Number
.
package valueObjects {
[Bindable]
public class Product {
public var catID:Number;
public function Product() {
}
}
}
All properties of a class must be specified inside the class definition in ActionScript.
If you were to type only catID, without anything else, you would see a question mark appear in an orange circle, indicating that it’s not clear what you intended. Whenever you see that question mark in an orange circle , you can press Control-1 on that line of code, which will launch the quick fix tool, offering potential solutions. On this line of code, clicking Control-1 will offer you the option to create an instance variable from that line of code.
Choosing the option will add private var
before the catID
, and a datatype of Object
. In this case, we want catID
to be public, and datatyped as a Number
, so it is less work to type it as originally intended, rather than use the quick fix feature.
prodName
(String), unitID
(Number), cost
(Number), listPrice
(Number), description
(String), isOrganic
(Boolean), isLowFat
(Boolean), and imageName
(String). Your class should appear as follows:
package valueObjects {
[Bindable]
public class Product {
public var catID:Number;
public var prodName:String;
public var unitID:Number;
public var cost:Number;
public var listPrice:Number;
public var description:String;
public var isOrganic:Boolean;
public var isLowFat:Boolean;
public var imageName:String;
public function Product() {
}
}
}
You are creating a data structure to store inventory information for the grocery store. You have now created all the properties that will be used in the class.
When you created this class, Flash Builder created a default constructor on your behalf.
public function Product( catID:Number, prodName:String, unitID:Number, cost:Number, listPrice:Number, description:String, isOrganic:Boolean, isLowFat:Boolean, imageName:String ) {
}
The constructor function is called when an object is created from a class. You create an object from a class by using the new
keyword and passing the class arguments. In this case the parameter names match the property names of the class. This was done to keep things clear, but is not necessary.
Two words are often used in discussions of methods: parameter and argument. They are often used interchangeably, but technically, functions are defined with parameters, and the values you pass are called arguments. So a function is defined to accept two parameters, but when you call it, you pass two arguments.
this
keyword to avoid name collision (when the same name can refer to two separate variables).
public function Product( catID:Number, prodName:String, unitID:Number, cost:Number, listPrice:Number, description:String, isOrganic:Boolean, isLowFat:Boolean, imageName:String ) {
this.catID = catID;
this.prodName = prodName;
this.unitID = unitID;
this.cost = cost;
this.listPrice = listPrice;
this.description = description;
this.isOrganic = isOrganic;
this.isLowFat = isLowFat;
this.imageName = imageName;
}
This code will set each property of the object to the corresponding argument passed to the constructor. The first line of the constructor reads: “Set the catID
property of this object to the value that was passed to the catID
parameter of the constructor.”
You could name the constructor parameters differently from the properties (for example, categoryID
instead of catID
). In that case, each property listed to the left of the equals sign could have done without the this
. prefix (for example, catID = categoryID;
). The prefix is added when you wish to be specific when referencing a property. The this
prefix refers to the class itself and is implicit when there is no possibility of name collision.
toString()
and the return type String. It will return the string [Product]
and the name of the product. Your class should read as follows:
package valueObjects {
[Bindable]
public class Product {
public var catID:Number;
public var prodName:String;
public var unitID:Number;
public var cost:Number;
public var listPrice:Number;
public var description:String;
public var isOrganic:Boolean;
public var isLowFat:Boolean;
public var imageName:String;
public function Product( catID:Number, prodName:String,
unitID:Number, cost:Number, listPrice:Number,
description:String, isOrganic:Boolean, isLowFat:Boolean,
imageName:String ) {
this.catID = catID;
this.prodName = prodName;
this.unitID = unitID;
this.cost = cost;
this.listPrice = listPrice;
this.description = description;
this.isOrganic = isOrganic;
this.isLowFat = isLowFat;
this.imageName = imageName;
}
public function toString():String {
return "[Product]" + this.prodName;
}
}
}
toString()
is a special method of objects in ActionScript. Whenever you use an instance of your Product in a place where Flex needs to display a String, this method will be automatically invoked by Flash Player. A good example of this concept is the trace()
method, which can be used to output data to the console. Running the code trace ( someProduct )
would call the toString()
method of that Product instance and output the string it returns to the Console view. This can be very useful for debugging and displaying data structures.
theProduct
typed as a Product. Add a [Bindable]
metadata tag above this single property.
[Bindable]
private var theProduct:Product;
If you used code completion, Flash Builder imported the Product class for you. If you did not, then add import valueObjects.Product;
before continuing.
All MXML files ultimately compile to an ActionScript class. You must follow the same conventions when creating an MXML class as when creating an ActionScript class. For example, you must import any classes that are not native to the ActionScript language, such as the Product class you have built, and you must declare any properties that you will use in your MXML class.
handleCreationComplete()
method, but above the categoryService.send();
statement, create a new instance of the Product class and assign it to the theProduct
property. When creating the new Product, you will need to pass a value for each constructor argument. For these arguments you will use the data from the <fx:XML>
tag named groceryInventory
. Type the code as follows:
theProduct = new Product( groceryInventory.catID, groceryInventory.prodName, groceryInventory.unitID, groceryInventory.cost, groceryInventory.listPrice, groceryInventory.description, groceryInventory.isOrganic, groceryInventory.isLowFat, groceryInventory.imageName );
Here you are instantiating a new object of that Product class you built. You are passing the data from the <fx:XML>
tag as constructor arguments. If you were to review the XML in the groceryInventory
variable, you would note that it doesn’t presently have a node for catID
and unitID
. However, in this context, Flash Player will just interpret them as 0 (zero) for the Product value object. These values will be used extensively when you begin loading more complicated data from the server.
When you’re accessing properties from the groceryInventory XML, Flash Builder cannot help you with code completion or even compile-time checking. The nodes inside the XML aren’t checked; this is why Flash Builder does not complain when you type groceryInventory.catID
even though it is not present in the XML. This means it is extremely easy to make a typo that can be difficult to debug. For now check your code carefully, but as you continue to use strongly typed objects, you will see how Flash Builder can help and why typed objects can make debugging easier.
trace()
statement and trace the property theProduct
out to the console. This statement will automatically execute the toString()
method of your object and output the results. Your finished method should look like the following:
private function handleCreationComplete(event:FlexEvent):void {
theProduct = new Product( groceryInventory.catID, groceryInventory.prodName, groceryInventory.unitID, groceryInventory.cost, groceryInventory.listPrice, groceryInventory.description, groceryInventory.isOrganic, groceryInventory.isLowFat, groceryInventory.imageName );
trace( theProduct );
categoryService.send();
}
You should see [Product]Milk
in the Console view, which indicates that you have created a Product value object successfully.
As you just did in the previous exercise, you can instantiate an instance of the Product class by passing values as arguments to the constructor. In this exercise, you’ll build a method that will accept any type of object that contains all the properties and values needed for a product, and return an instance of the Product class populated with this data. This type of method is often referred to as a factory method, as its job is creating other objects.
Note that for this method to function correctly, the object passed to the method must contain property names that correspond exactly to the names you will hard-code into this method.
toString()
method. Immediately after this method, add the skeleton of a new public static
method called buildProduct()
. Be sure that the return type of the method is set to Product
, and that it accepts a parameter named o
typed as Object
, as shown:
public static function buildProduct( o:Object ):Product {
}
A static method is a method that belongs to a class, not to an instance. The methods you’ve worked with so far are called instance methods; they can be used only with instantiated objects.
Consider for a moment your toString()
method. That method uses productName
to display the name of the product represented by that object. If you have n product objects (where n is any number of product objects), each should display a different name when the toString()
method is called. Since this method uses data from the object, it is logical that the object must exist before the method can be called.
Conversely, you may have a method that doesn’t need (or care about) any of the data inside a specific instance. In fact, it could just be a utility method that does work independent of any particular instance. This is called a static method.
Static methods are often used for utilities such as the buildProduct()
method. You will be able to call this method without creating an instance of the Product first. Used appropriately, static methods can increase the legibility and usefulness of your objects. To reference a static method with the name buildSomething()
from the Product class, you would use the code Product.buildSomething()
, which uses the class name before the method, as opposed to the instance.
buildProduct()
method, create a new local variable named p
of type Product
.
var p:Product;
p
using the new
keyword. Pass the catID
, prodName
, unitID
, cost
, listPrice
, description
, isOrganic
, isLowFat
, and imageName
properties of the object o
as arguments to the constructor. The isOrganic
and isLowFat
variables will be compared against the String 'true'
before being passed as the following code demonstrates:
p = new Product( o.catID, o.prodName, o.unitID, o.cost, o.listPrice, o.description, ( o.isOrganic == 'true' ), ( o.isLowFat == 'true' ), o.imageName );
Remember that the data used here is retrieved from the <fx:XML>
tag. When data is retrieved this way, all the data is XML: The true
and false
values for these fields are just treated as a type of String. Comparing isOrganic
and isLowFat
to the String 'true'
will return a Boolean value, either a true
of a false
. In this way, you are converting the value contained in the XML to the Boolean value that the newly created object is expecting for these properties. This allows you to take a string value from the XML, and convert it to a Boolean.
return
keyword with the name of the object, p
. Your final buildProduct()
method should appear as follows:
public static function buildProduct( o:Object ):Product {
var p:Product;
p = new Product( o.catID, o.prodName, o.unitID, o.cost, o.listPrice, o.description, ( o.isOrganic == 'true' ), ( o.isLowFat == 'true' ), o.imageName );
return p;
}
This method will create and return a new Product value object and populate it with data from the object passed as an argument.
The class file is saved with the new method. No errors should appear in the Problems view.
handleCreationComplete()
method, remove the code that builds theProduct
and replace it with code that uses the static method to build theProduct
. Remember to remove the new
keyword.
theProduct = Product.buildProduct( groceryInventory );
This code calls the static method that builds an instance of the Product class, which returns a strongly typed Product value object from any type of object that has correspondingly named properties.
text
property of the <s:RichText>
tag to reference the description
property of the theProduct
object you created in the handleCreationComplete()
method. Also, add a visible
property to both labels, and bind each to the appropriate theProduct
object properties, as shown in the following code.
Remember, because Product is now an imported class, you can get code hinting for both the class name and its properties. When you are in the braces creating the binding, press Ctrl-Spacebar to get help for inserting the Product instance, theProduct
. Then, after you enter the period, you will get the properties listed.
<s:VGroup includeIn="expanded" width="100%" x="200">
<s:RichText text="{theProduct.description}" width="50%"/>
<s:Label text="Certified Organic"
visible="{theProduct.isOrganic}"/>
<s:Label text="Low Fat"
visible="{theProduct.isLowFat}"/>
</s:VGroup>
You are now referencing the value object you created. You also just gained one more benefit: Flash Builder is now helping you debug. If you were to make a typo—for example, if you were to type theProduct.isLowerFat
—Flash Builder would alert you to the error when you saved. Now that Flash Builder knows the types of the objects you are using, it can verify that you are accessing properties that exist. What might have taken you a few minutes to reread, check, and perhaps even debug, Flash Builder found in moments. Over the course of a project, that becomes days and weeks of time.
You should see that the trace()
method performs just as before, and the correct data should still appear when you roll over the image.
In this exercise, you’ll build a new class called ShoppingCartItem. This class will contain an item added to a shopping cart that you will also build shortly. The new class will keep track of a product added and its quantity. You will also build a method that calculates the subtotal for that item.
You will then build the skeleton for a ShoppingCart class that will handle all the logic for the shopping cart, including adding items to the cart.
In this class, you will calculate the quantity of each unique item as well as the subtotal.
class
definition, define a public property with the name product
and the type Product
, as shown:
package cart {
import valueObjects.Product;
public class ShoppingCartItem {
public var product:Product;
public function ShoppingCartItem() {
}
}
}
The product
is the most important piece of data in the ShoppingCartItem. If you used code completion, Flash Builder imported the Product class for you. If you did not, then add import valueObjects.Product;
before continuing.
quantity
of the type uint
, as shown:
package cart {
public class ShoppingCartItem {
public var product:Product;
public var quantity:uint;
public function ShoppingCartItem() {
}
}
}
The data type uint means unsigned integer, which is a nonfractional, non-negative number (0, 1, 2, 3, ...). The quantity of an item added to the shopping cart will be either zero or a positive number, so uint
is the perfect data type.
subtotal
and the data type Number
, as shown:
package cart {
public class ShoppingCartItem {
public var product:Product;
public var quantity:uint;
public var subtotal:Number;
public function ShoppingCartItem() {
}
}
}
Each time a user adds an item to the shopping cart, you’ll want the subtotal for that item to be updated. In this case, you are using Number as the data type. As the product’s price is not likely to be an integer, the Number class allows for fractional numbers. Eventually, you will display this data in a visual control.
product
typed as a Product
and quantity
typed as a uint
. You will provide a default value of 1
for the quantity. If the developer calling this method does not provide a quantity, you will assume 1.
public function ShoppingCartItem( product:Product, quantity:uint=1 ) {
}
Remember that a constructor function must be public and that it never specifies a return type.
this
.
public function ShoppingCartItem( product:Product, quantity:uint=1 ){
this.product = product;
this.quantity = quantity;
}
Remember that the constructor is called every time an object is created from a class. The constructor will set the properties that are passed in—in this case, an instance of the Product class, and the quantity, which is set to 1 as a default. This object will be used only when an item is added to the shopping cart, so a default quantity of 1 seems reasonable.
calculateSubtotal()
that will calculate the subtotal of each item by multiplying the listPrice
of the product by the quantity
, as follows:
public function calculateSubtotal():void{
this.subtotal = product.listPrice * quantity;
}
When the user adds items to the shopping cart, you need to perform calculations so that the subtotal can be updated. Eventually, you also need to check whether the item has already been added to the cart; if so, you will update the quantity. You’ll learn how to do this in the next lesson.
calculateSubtotal()
method on the last line of the constructor. This will ensure that the subtotal is correct as soon as the object is created.
public function ShoppingCartItem( product:Product, quantity:uint=1 ) {
this.product = product;
this.quantity = quantity;
calculateSubtotal();
}
toString()
that will return a nicely formatted string with the product’s name and quantity. The returned string will read [ShoppingCartItem]
, followed by a space, the product’s name, a colon, and finally the quantity of that product in this ShoppingCartItem.
public function toString():String {
return "[ShoppingCartItem] " + product.prodName + ":" + quantity;
}
As you learned previously, toString()
methods are automatically called when Flash Player needs to represent this object as a String, such as when you use it in a trace()
statement. This will provide you a lot of valuable debugging information.
Your new class will be the actual shopping cart, filled with ShoppingCartItem objects. This class will handle the manipulation of the data in the shopping cart. You have already created the visual look and feel of the shopping cart, and you will place all your business logic in this new class. This business logic includes work that must occur when adding an item to the cart, deleting an item from the cart, updating an item in the cart, and so on.
addItem()
method, which returns void
. The method will accept a parameter named item
, of type ShoppingCartItem
. In the method, add a trace
statement that will trace the item
added to the cart.
package cart {
public class ShoppingCart {
public function ShoppingCart() {
}
public function addItem( item:ShoppingCartItem ):void {
trace( item );
}
}
}
This is the method in which you will add a new item to the shopping cart. You’ll add more business logic to this method later. For now, you’ll just trace the item added to the cart. Remember that the toString()
method you wrote earlier is called automatically whenever an instance of the ShoppingCartItem class is traced.
import cart.ShoppingCartItem;
import cart.ShoppingCart;
To use a class in a different package, your application needs an import
statement that references the location or package in which the class is located.
import
statements, instantiate a public instance of the ShoppingCart class, name the instance shoppingCart
, and add a [Bindable]
metadata tag, as follows:
[Bindable]
public var shoppingCart:ShoppingCart = new ShoppingCart();
Pay attention to the differences in case here. Variables, such as shoppingCart, usually start with a lowercase letter. Classes, such as ShoppingCart, start with an uppercase letter.
When the user clicks the Add To Cart button, you want to call the addItem()
method of the ShoppingCart class you just created. You will pass the addItem()
method an instance of the ShoppingCartItem class. By instantiating the class here, you ensure that you have access to it throughout the application.
handleViewCartClick()
method in the <fx:Script>
block. Immediately after this method, add a new private function with the name addToCart()
that returns void
. Have the method accept a parameter named product
typed as Product
, as shown:
private function addToCart(product:Product):void {
}
This method will be called when the user clicks the Add To Cart button, and you will pass an instance of the Product value object. As you do not intend anyone to call this method from outside this MXML class, you can use the private identifier. Using the keyword private
here prevents others from calling this method unexpectedly.
addToCart()
method, create a new instance of the ShoppingCartItem class with the name sci
and pass the product
parameter as an argument to the constructor.
private function addToCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
}
Notice that you passed the product
but you did not pass the second parameter of the constructor (quantity
). This is okay, as you provided a default value for the quantity when creating this class.
addToCart()
method, call the addItem()
method of the shoppingCart
instance of the ShoppingCart class. Be sure to pass the sci
object you just created to the method, as follows:
private function addToCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.addItem( sci );
}
This code will call the addItem()
method of the ShoppingCart class you built earlier. In the next sections, you’ll learn how to loop through the data structure to see whether the item is added. For now, this method simply traces the name of the product added to the cart.
click
event that calls the addToCart()
method, passing an instance of theProduct
.
<s:Button label="Add To Cart" id="add"
click="addToCart( theProduct )"/>
Remember, the addToCart()
method creates an instance of the ShoppingCartItem class and then passes that object to the shopping cart.
Each time you click the Add To Cart button, you should see [ShoppingCartItem] Milk:1 appear in the Console view.
The next several exercises all deal with manipulating data in the shopping cart. You will work extensively with the Array class. In Lesson 8, “Using Data Binding and Collections,” you’ll add even more functionality to this class and allow it to work with Flex controls to update the display dynamically.
In this exercise you’ll write the code to add items to the shopping cart.
Alternatively, if you didn’t complete the previous exercise or your code is not functioning properly, you can import the FlexGrocer-PreCartData.fxp project from the Lesson07/intermediate 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.
class
definition, define a public property with the name items
, typed as an Array
and set equal to a new Array instance.
package cart {
public class ShoppingCart {
public var items:Array = new Array();
This instantiates an Array object and assigns it to the items
property. You will use this Array to track all the objects in the shopping cart.
total
, typed as a Number
. It will hold the total price of the items in this class. When the class is first instantiated, there won’t be any items, so set the value to 0
as shown:
public var total:Number=0;
Anytime a user adds an item to the cart, you will update this property with the price of the item. This will enable you to track the total cost of the end user’s order.
addItem()
method of the ShoppingCart class and remove the trace
statement. Use the push()
method of the Array class to add ShoppingCartItem to the items
array. The push()
method of the Array adds an element to the end of the array, after any existing items.
public function addItem( item:ShoppingCartItem ):void {
items.push( item );
}
In the previous exercise, you built a ShoppingCartItem class to hold 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 unsigned integer), and the subtotal (a number derived by multiplying the quantity by the price). When the user clicks the Add To Cart button, you pass a ShoppingCartItem to this method and place it in the Array using addItem()
.
toString()
that will return a nicely formatted string representing the items in the shopping cart. The returned string will be [ShoppingCart, followed by a space, a dollar sign, the closing right bracket, another space and the items
array as shown:
public function toString():String {
return "[ShoppingCart $" + total + "] " + items;
}
As you learned previously, toString()
methods are called automatically when Flash Player needs to represent this object as a String, such as when you use it in a trace()
statement. You added toString()
methods to several of your objects already. When this statement executes, it is going to display [ShoppingCart $0]
and then the array. The Array also has a toString()
method, so when you trace this array, it will actually call the toString()
method on each item in the array.
addToCart()
method. To the last line of this method add a trace()
statement that traces the shoppingCart
.
private function addToCart( product:Product ):void {
var sci:ShoppingCartItem = new ShoppingCartItem( product );
shoppingCart.addItem( sci );
trace( shoppingCart );
}
Each time you click the Add To Cart button, the entire shopping cart will be traced to the Console view.
Each time you click the Add To Cart button, you will see another line appear in the Console view. For example, clicking it three times will yield:
[ShoppingCart $0] [ShoppingCartItem] Milk:1
[ShoppingCart $0] [ShoppingCartItem] Milk:1,[ShoppingCartItem] Milk:1
[ShoppingCart $0] [ShoppingCartItem] Milk:1,
[ShoppingCartItem] Milk:1,[ShoppingCartItem] Milk:1
The number of items grows each time another Milk product is added to the cart. However, this uncovers an error in your ShoppingCart. If you add Milk a second time, you likely mean to increase the quantity by 1, not actually add a new item. You will address this over the next few exercises.
The code you are about to write conditionally places a new item, or updates an existing item, in the shopping cart. It is not difficult line by line, but the logic involved can seem complex at first. To be sure you understand the big picture before you try to implement the details, let’s walk through the logic required for the implementation.
addItem()
method is called.• If the item does not exist, it is added.
• If the item does exist, you find and update the existing item.
A key point in the logic of this exercise is determining whether a newly added ShoppingCartItem is already in the existing array of ShoppingCartItems. In this section, you will simply loop through the array looking for the correct item. In the next lesson, you’ll learn to use collections and cursors to make this faster and more elegant.
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 need to write a method that can check whether an existing product is in the cart already.
getItemInCart()
. This method will accept a single parameter named item
of type ShoppingCartItem
. The method will return a ShoppingCartItem.
private function getItemInCart( item:ShoppingCartItem ):ShoppingCartItem {
}
This method will accept a ShoppingCartItem and then look through all existing items to see if the represented product already exists in the cart.
getItemInCart()
function, declare a new local variable named existingItem
of type ShoppingCartItem
.
private function getItemInCart( item:ShoppingCartItem ):ShoppingCartItem {
var existingItem:ShoppingCartItem;
}
for
loop. Loops are blocks of code that are executed repeatedly using a series of values. In this loop, declare a variable i
of type uint
and loop from 0
to less than the length of the items
array. In ActionScript, this loop appears like the following code:
private function getItemInCart( item:ShoppingCartItem ):ShoppingCartItem {
var existingItem:ShoppingCartItem;
for ( var i:uint=0; i<items.length; i++ ) {
}
}
The code inside this for
loop will be executed for each item in the array, allowing you to inspect each value for a matching product. The loop will continue to execute as long as i
(the iterant) is less than the length of the items
array. Each time the loop executes, the iterant is increased by 1 (++
is shorthand for increment).
for
loop, assign the local existingItem
variable to the next item in the array. In ActionScript, the basic array contains data of an unknown type. This means that you will need to give the compiler a hint about the type of data in the array by casting it.
private function getItemInCart( item:ShoppingCartItem ):ShoppingCartItem {
var existingItem:ShoppingCartItem;
for ( var i:uint=0; i<items.length; i++ ) {
existingItem = items[ i ] as ShoppingCartItem;
}
}
existingItem.product
is equal to item.product
. If they are equal, return the existingItem
variable. Finally, return null
at the end of the method.
private function getItemInCart( item:ShoppingCartItem ):ShoppingCartItem {
var existingItem:ShoppingCartItem;
for ( var i:int=0; i<items.length; i++ ) {
existingItem = items[ i ] as ShoppingCartItem;
if ( existingItem.product == item.product ) {
return existingItem;
}
}
return null;
}
This code will now loop through the existing items and look for a matching product. If one is found, the code returns it. If a matching product is not found, null
is returned.
While the getItemInCart()
method allows you to find a given item in the cart, it would be nice to have a method that simply indicates whether or not the item is there; in other words, a method that returns a Boolean value indicating the item’s existence.
isItemInCart()
that will return a Boolean
. The method will accept a parameter named item
of type ShoppingCartItem
. Within the method, create a new variable local to the method with the name sci
, which will hold a matched ShoppingCartItem
, returned by the getItemInCart()
method.
private function isItemInCart( item:ShoppingCartItem ):Boolean {
var sci:ShoppingCartItem = getItemInCart( item );
}
The getItemInCart()
method returns an item
if it is found; else it will return null
.
return
statement that returns the Boolean expression ( sci != null )
. Your completed method should look as follows:
private function isItemInCart( item:ShoppingCartItem ):Boolean {
var sci:ShoppingCartItem = getItemInCart( item );
return ( sci != null );
}
The expression ( sci != null )
will evaluate to either true
or false
; therefore, the isItemInCart()
method will now return true
if the added item is in the cart and false
if the added item is not found in the cart.
The previous methods will help you to determine whether an item is in the cart. However, if that item is already in the cart, it shouldn’t be added to the cart again. Rather, the item’s quantity should be updated.
updateItem()
method, returning void
. Have it accept a parameter named item
, typed as ShoppingCartItem
. On the first line of the method, define a local variable with the name existingItem
, typed as a ShoppingCartItem
. Set that local variable equal to the result of the getItemInCart()
method, passing the item
.
private function updateItem( item:ShoppingCartItem ):void {
var existingItem:ShoppingCartItem = getItemInCart( item );
}
updateItem()
method, update the quantity
property of the existingItem
object to its current value plus the value located in the item
property.
existingItem.quantity += item.quantity;
Remember, whenever the Add To Cart button is clicked, a new item is added to the cart with the quantity
value set to 1
.
updateItem()
method and immediately after you set the quantity
, call the calculateSubtotal()
method of the existingItem ShoppingCartItem
instance. The final updateItem()
method should look like this:
private function updateItem( item:ShoppingCartItem ):void {
var existingItem:ShoppingCartItem = getItemInCart( item );
existingItem.quantity += item.quantity;
existingItem.calculateSubtotal();
}
When you first created the ShoppingCartItem class, you added a method with the name calculateSubtotal(),
which updated a subtotal
property with the listPrice
of each product multiplied by the quantity
of each product. Anytime you update the quantity
, you need to recalculate that subtotal
value.
updateItem()
method, create a skeleton for a private calculateTotal()
method, with a return type void
. In the method, create a local variable named newTotal
of type Number
and set it to an initial value of 0
. Create a second local variable named existingItem
of type ShoppingCartItem
.
private function calculateTotal():void{
var newTotal:Number = 0;
var existingItem:ShoppingCartItem;
}
In this method, you will loop over the entire shopping cart and eventually update the total
property of the ShoppingCart with the total of the user’s items.
calculateTotal()
method, create a skeleton of a for
loop that will loop through the items
array. Use the variable i
as the iterant for the loop, with a data type uint.
Use the length
property of items
as the terminating condition, and use the ++
operator to increment the iterant.
for ( var i:uint=0; i<items.length; i++ ) {
}
Like the loop you wrote before, this enables you to loop through the entire shopping cart. The loop will continue to execute as long as i
(the iterant) is less than the length of the array. Each time the loop executes, the iterant is increased by 1 (++
is shorthand for this increase).
existingItem
variable equal to items[ i ]
cast as a ShoppingCartItem
. Update the newTotal
variable with the subtotal
of each existing item stored in the items
array. Be sure to use the +=
operator so it will add the new value to the existing one. Your calculateTotal()
method should appear as follows:
private function calculateTotal():void{
var newTotal:Number = 0;
var existingItem:ShoppingCartItem;
for ( var i:uint=0; i<items.length; i++ ) {
existingItem = items[ i ] as ShoppingCartItem;
newTotal += existingItem.subtotal;
}
}
This loops through the entire shopping cart and updates the newTotal
variable by adding the subtotal (price * quantity)
of each item in the cart to the current newTotal
. Now anytime you need to calculate the total price of all the items, you can simply call this method.
calculateTotal()
method, assign the value in the newTotal
variable to the total
property of the ShoppingCart instance.
private function calculateTotal():void{
var newTotal:Number = 0;
var existingItem:ShoppingCartItem;
for ( var i:uint=0; i<items.length; i++ ) {
existingItem = items[ i ] as ShoppingCartItem;
newTotal += existingItem.subtotal;
}
this.total = newTotal;
}
In the next lesson, you will tell the display to update each time the total
property changes. By performing the calculation using the local newTotal
property and then assigning it to the total
at the end of the method, you will cause the display to update only once. Had you used the total
property throughout, Flex would have tried to update the display once for every item in the array.
You now have all the building blocks to finish your addItem()
method and ensure that the total
remains consistent as items are added or updated.
addItem()
method. On the first line add an if-else
statement. Use the isItemInCart()
method to check whether the item is currently in the cart.
public function addItem( item:ShoppingCartItem ):void {
if ( isItemInCart( item ) ) {
} else {
}
items.push( item );
}
if
block, call the updateItem()
method, passing the item
. Move the code that pushes the new item into the items
array into the else
block.
public function addItem( item:ShoppingCartItem ):void {
if ( isItemInCart( item ) ) {
updateItem( item );
} else {
items.push( item );
}
}
When the addItem()
is called, it will check to see whether the item is already in the cart. If it is, then the existing item will be updated. Else, the new item will be added.
calculateTotal()
method to recalculate the shopping cart’s total after any add or update occurs.
public function addItem( item:ShoppingCartItem ):void {
if ( isItemInCart( item ) ) {
updateItem( item );
} else {
items.push( item );
}
calculateTotal();
}
Each time you click the Add To Cart button, you will see another line appear in the Console view. For example, clicking it three times will yield:
[ShoppingCart $1.99] [ShoppingCartItem] Milk:1
[ShoppingCart $3.98] [ShoppingCartItem] Milk:2
[ShoppingCart $5.97] [ShoppingCartItem] Milk:3
Instead of adding a new item each time, the cart now updates the quantity of the item (as indicated by the last number). Also, note that the total in the shopping cart changes as you modify the cart. You will add more functionality, including the ability to remove items, in the next lesson.
In this lesson, you have:
• Created a Product class (pages 142–150)
• Created a static factory to build Product instances (pages 150–152)
• Populated a Product instance with data (pages 152–153)
• Created a ShoppingCartItem class (pages 154–156)
• Created a ShoppingCart class (pages 157–159)
• Manipulated shopping cart data (pages 159–161)
• Used an ActionScript loop to move through an array (pages 162–163)
• Added and updated items in the ShoppingCart (pages 164–167)