Learning Flex Basics (Part 2): Creating a Simple Calculator with Macromedia Flex
Robert Crooks
In this tutorial, you will build a working calculator in Macromedia Flex. Although the calculator is simple—it just adds, subtracts, multiplies, and divides—building it will enable you to work with several essential building blocks for Flex applications.
If you like what you see and want to get more experience with Flex,
Macromedia Customer Training offers Developing Rich Internet Applications with Macromedia Flex, a two-day onsite training course to jump-start Flex development in your organization. Now let's get started!
What You Will Learn
- How to use the Application tag.
- How to use a Panel container.
- How to use Label controls.
- How to use Button controls.
- How to use a Grid container.
- How to create an ActionScript controller class for an MXML file.
- How to create an ActionScript class.
- How to add properties and methods to an ActionScript class.
- How to instantiate an ActionScript class within an MXML application and invoke methods.
Requirements
To complete this tutorial you will need to install the following software and files:
Macromedia Flex
A Text Editor
Flex Builder, Dreamweaver MX 2004, or a text editor that you can use to write XML and ActionScript code (a basic text editor such as Notepad is adequate). You can download Flex Builder with the Flex server try/buy links above. Use the following links to try/buy Dreamweaver MX 2004:
Solution files for the tutorial:
Prerequisite knowledge:
Building the Calculator
The calculator will have two parts: a view built in MXML; and a collection of event handlers built as an ActionScript class. The view will contain all the visual elements of the calculator such as the display and buttons. The handlers class contains data that holds variables you need in order to keep track of the user's actions; it also contains methods that you will use to handle events, such as the user clicking a button.
Figure 1. The calculator you will build in this tutorial.
In building the calculator you will learn to use:
- The Application class
- The Panel container
- The Label element
- The Grid container
- The Button element
- An ActionScript class that includes:
- A class definition
- Properties
- Methods
- The if-else and switch-case control structures
Create the Application Object
To begin any Flex application, start with an XML declaration and an Application
tag block. The Application tag include a namespace declaration for the MX class library: xmlns:mx="http://www.macromedia.com/2003/mxml"
. The mx prefix is used with all tags for this library.
- Create a new file and save it as calculator.mxml in the flex_tutorials directory.
- At the beginning of the file, insert the XML declaration:
-
After the XML declaration, add an Application
tag with the MXML namespace:
<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml">
</mx:Application>
Add a Panel for the Calculator
Generally, you place the visual components of your Flex application inside containers, which provide bounding boxes for text, controls, images, and other media elements. Here, you will use a Panel container that provides the overall visual wrapper for most applications. You will also use the title
property of Panel, which provides title text to display in a title bar, automatically included at the top of the panel.
-
Inside the Application block, add a Panel block with the title property equal to Calculator:
<mx:Panel title="Calculator">
</mx:Panel>
Add the Calculator Display
The Label element displays a single line of text. It has a number of properties; here, you will use the following properties:
- id: a unique identifier used to reference this instance in an event handler
- width: width of label in pixels
- text: text to display
- textAlign: horizontal alignment of text: left | right | center
-
Inside the Panel block, add a Label with the properties as follows:
<mx:Label id="calcDisplay" width="150" textAlign="right" />
Note: There is no text value in this tag because you will set the value with handler methods in the controller class; you build that functionality later in the tutorial.
Add a Grid to Lay Out the Buttons
To lay out the calculator buttons, you use a Grid layout container. A Grid container is similar to an HTML table: It arranges information in rows and columns using nested GridRow and GridItem containers. GridItem containers have
colSpan
and
rowSpan
properties that work the same as the HTML
<td>
attributes. You are going to lay out the buttons for the calculator in the grid. If you look at the screen shot of the finished calculator on
page 2, you will see that you need five rows, and that the top and bottom rows will contain three buttons, while the middle three rows contain four.
-
After the Label element, but still inside the Panel, add a Grid container block:
-
Inside the Grid container block, add five GridRow container blocks:
<mx:GridRow></mx:GridRow>
<mx:GridRow></mx:GridRow>
<mx:GridRow></mx:GridRow>
<mx:GridRow></mx:GridRow>
<mx:GridRow></mx:GridRow>
-
Inside the first GridRow container , add three GridItem containers, and set the colSpan
of the first item to 2:
<mx:GridItem colSpan="2"></mx:GridItem>
<mx:GridItem></mx:GridItem>
<mx:GridItem></mx:GridItem>
-
Inside each of the second, third, and fourth GridRow containers, add four GridItem containers:
<mx:GridItem></mx:GridItem>
<mx:GridItem></mx:GridItem>
<mx:GridItem></mx:GridItem>
<mx:GridItem></mx:GridItem>
-
Inside the fifth GridRow container , add three GridItem containers, and set the colSpan
of the third item to 2:
<mx:GridItem></mx:GridItem>
<mx:GridItem></mx:GridItem>
<mx:GridItem colSpan="2"></mx:GridItem>
Add the Calculator Buttons
The Flex Button control is similar to buttons in HTML and other languages. You will use three properties for the buttons:
- label: the text displayed on the button
- width
- click: the name of the handler executed when the user clicks on the button
Each button in your calculator will have the same general syntax:
<mx:Button label="[something]" width="[number]" click="[some handler method]"/>
Note: The following steps contain the names of event handlers that don't exist yet; you will create the handlers later as part of the CalculatorHandlers class.
-
Add a Button element inside each of the 18 GridItem containers; in the order of the GridItem containers, the Buttons will have the following properties:
Row 1
width="70"
label="Clear"
click="calcHandlers.clearAll()"
width="30"
label="C/E"
click="calcHandlers.clearEntry()"
width="30"
label="+"
click="calcHandlers.setOperation('add')"
Row 2
width="30"
label="1"
click="calcHandlers.addNumber('1')"
width="30"
label="2"
click="calcHandlers.addNumber('2')"
width="30"
label="3"
click="calcHandlers.addNumber('3')"
width="30"
label="-"
click="calcHandlers.setOperation('subtract')"
Row 3
width="30"
label="4"
click="calcHandlers.addNumber('4')"
width="30"
label="5"
click="calcHandlers.addNumber('5')"
width="30"
label="6"
click="calcHandlers.addNumber('6')"
width="30"
label="*"
click="calcHandlers.setOperation('multiply')"
Row 4
width="30"
label="7"
click="calcHandlers.addNumber('7')"
width="30"
label="8"
click="calcHandlers.addNumber('8')"
width="30"
label="9"
click="calcHandlers.addNumber('9')"
width="30"
label="/"
click="calcHandlers.setOperation('divide')"
Row 5
width="30"
label="0"
click="calcHandlers.addNumber('0')"
width="30"
label="."
click="calcHandlers.addNumber('.')"
width="70"
label="="
click="calcHandlers.doOperation()"
- Save the file.
Create the CalculatorHandlers Class
Create an ActionScript class that will be a controller for the calculator. Because the application is simple, you will include the data model in the controller rather than create it separately.
Creating a class in ActionScript is simple and is similar to the way you create one in other object-oriented languages. The basic syntax is:
class ClassName
{
[properties and methods]
}
Save classes as .as files; the file name must be the same as the class name.
Although the compiler will generate an empty constructor if the class definition does not contain one, it is good practice to include a constructor, even if it does nothing. Constructor methods in ActionScript may not take parameters or return any values, and constructor overloading is not supported. Constructors, like other methods, are created using the function
keyword:
[public|private] function ClassName(){}
Methods (and properties) are public by default, meaning that they can be accessed from outside the class. It's good practice to always make them public or private explicitly as a reminder to make them private if they don't need to be public.
- Open a new empty file and save it as CalculatorHandlers.as in the same directory as your calculator.mxml file.
-
Add a class definition for the CalculatorHandlers class:
class CalculatorHandlers
{}
-
Add an empty constructor function inside the class block:
public function CalculatorHandlers(){}
Add Properties to the CalculatorHandlers Class
Look at the code you wrote for calculator.mxml and see how none of it accesses the controller class properties directly; the properties are private, with one exception. When you instantiate the controller class in the MXML application later, you set a property that holds a reference to the entire calculator object, so that you can reference objects such as the display Label in the calculator without having to pass the reference when you call controller methods. So, you will set one public property to hold that reference, and several private properties to handle the calculator events.
The general syntax for setting properties is:
[public|private] var propertyName:dataType[=initialValue];
For example:
The properties to use, in addition to the public reference to the calculator object, are:
- Two registers to hold temporary values in the calculator pending operations
- A currentRegister value to track the register that the user modifies
- A currentOperation value to track the pending operation
These would be sufficient to make the calculator work, but you will add a few additional properties to hold numerical equivalents of the register values (these must be strings so that you can append additional numbers as the user clicks buttons) and a results value to hold the operation results. These extra properties will simplify some of the code and make the logic clearer.
-
Set the following properties at the beginning of the CalculatorHandlers class block:
Type |
Name |
Data Type |
Initial Value |
public |
calcView |
Object |
|
private |
reg1 |
String |
"" (empty string) |
private |
reg2 |
String |
"" |
private |
result |
Number |
|
private |
currentRegister |
String |
"reg1" |
private |
operation |
String |
"none" |
private |
r1 |
Number |
|
private |
r2 |
Number |
|
Add Methods to the CalculatorHandlers Class
If you review the values you set for the click events on the calculator buttons, you will see that you need the following public methods in the controller:
doOperation
addNumber
clearEntry
clearAll
setOperation
You will also create three private methods that the controller uses internally:
setDisplay
setCurrentRegister
resetAfterOp
The general syntax for methods is:
[public|private] function methodName(parameter1:dataType...):returnDataType
{
[ActionScript statements]
}
None of the controller methods will return values, so the return data type will be Void for each of them.
Add the doOperation() Method
The doOperation()
method performs addition, subtraction, multiplication, or division depending on which operation the user requests. Before carrying the operation, set the r1
and r2
properties equal to the reg1
and reg2
values, respectively, cast to the Number data type using the Number()
method, built into ActionScript.
To choose the appropriate operation, you will use a switch-case construct. The general syntax for switch-case in ActionScript is:
switch (expression)
{
case value:
[ActionScript statements]
break; // without this you fall through to next case
[more cases]
default:
[default case actions]
}
The other bit of ActionScript, the operators for addition, subtraction, multiplication, and division are (and you can probably guess):
Use the following steps to add the doOperation()
method.
-
After the constructor method, create a public doOperation
method that takes no parameters and returns nothing:
public function doOperation():Void
{
}
-
At the beginning of the method block, set this.r1
and this.r2
equal to the respective values of reg1
and reg2
cast to the Number type:
this.r1=Number(reg1);
this.r2=Number(reg2);
-
Add a switch-case structure that switches on the value of the operation
property:
-
Inside the switch block, add four cases for the operation types: add
, subtract
, multiply
, and divide
. In the each case, set the result property equal to the appropriate operation on r1
and r2,
then call the resetAfterOp()
method, and close each case with a break
statement:
case "add":
this.result=r1+r2;
resetAfterOp();
break;
case "subtract":
this.result=r1-r2;
resetAfterOp();
break;
case "multiply":
this.result=r1*r2;
resetAfterOp();
break;
case "divide":
this.result=r1/r2;
resetAfterOp();
break;
-
Add a default
case that does nothing (in case no operation is set):
Add the addNumber() Method
The addNumber()
method will take a number in string form passed from the calculator view and append it to the string value for the current register. The + operator in ActionScript is overloaded (the only operation that is) and performs string concatenation if both the operands are strings.
The concatenation is simple, but the method needs to take care of a couple of a special case: After the method performs the operation, it copies the result to register 1, and sets register 2 as the current register in case the user wants to perform further operations on the result. If the user starts entering numbers rather than setting a new operation, however, you clear register 1 and set it as the current register.
To check for this condition and perform the correct action, you must use an if
structure. You will also need to check two conditions, so you need to know that the logical AND
operator in ActionScript is &&
(the logical OR
operator is ||
). The general syntax for if-else structures is:
if (condition[s])
{
[ActionScript statements]
}[else if (condition) // 0 or more
{
[ActionScript statements]
}else // 0 or one
{
[ActionScript statements]
}]
The comparison operators for ActionScript are common ones:
Operator |
Meaning |
== |
equals |
< |
less than |
> |
greater than |
<= |
less than or equal |
>= |
greater than or equal |
! |
logical NOT |
An additional ActionScript concept you need for this method is dynamic evaluation. Modify the current register, keeping in mind which register is current is set at runtime by the setCurrentRegister()
method you will create later. The currentRegister property will always be equal to the name of one of the register properties, either reg1
or reg2
, so you can get the current register using the expression this[currentRegister]
. Square brackets following an object reference forces dynamic evaluation, and so you can use them to dynamically set or retrieve a property name.
-
After the doOperation()
method, create a public addNumber()
method that takes a String parameter called n and returns nothing:
public function addNumber(n:String):Void
{
}
-
Inside the method block, add an if
structure that checks for the conditions that the operation
property is equal to "none" and the currentRegister
property is equal to "reg2":
if (operation=="none" && currentRegister=="reg2")
{
}
Note: These conditions are true if the doOperation()
called the resetAfterOp()
method, and if the user has clicked a number button.
-
Inside the if block, set the reg1 property equal to an empty string and then call the setCurrentRegister()
method (which you create later):
reg1="";
setCurrentRegister();
-
After the if structure, append n to the value of the current register:
this[currentRegister]+=n;
Note that you are using a shortcut common to several programming languages here. The statement above is the equivalent of this[currentRegister] = this[currentRegister] + n;
-
Add a call to the setDisplay()
method, passing the currentRegister property as an argument (you create this method later):
setDisplay(currentRegister);
Add the clearEntry() and clearAll() Methods
These two methods are similar and straightforward, so you'll do them together. For the clearEntry()
method, clear the current register and the display. For the clearAll()
method, clear both registers and the display, set the pending operation to none, and set the currentRegister
property to reg1.
-
Add a public clearEntry()
method that takes no parameters and returns nothing:
public function clearEntry():Void
{
}
-
In the method block, set the value of the current register to an empty string and then call the setDisplay()
method, passing the currentRegister
property as an argument:
this[currentRegister]="";
setDisplay(currentRegister);
-
Add a public clearAll()
method that takes no parameters and returns nothing:
public function clearAll():Void
{
}
-
In the method block, set both register values to empty strings, and then call setCurrentRegister()
, passing nothing, call setOperation
, passing "none," and call setDisplay
, passing currentRegister
:
reg1="";
reg2="";
setCurrentRegister();
setOperation("none");
setDisplay(currentRegister);
Add setOperation() Method
This is a straightforward setter method. After setting the pending operation, reset the current register using a setCurrentRegister()
method that you will create later in the tutorial.
-
Add a public setOperation()
method that takes a parameter called operation
of type String
and returns nothing:
public function setOperation(operation:String):Void
{
}
-
In the method block, set the value of the operation
property to the passed operation parameter and then call the setCurrentRegister()
method:
this.operation=operation;
setCurrentRegister();
Add setDisplay(), setCurrentRegister(), and resetAfterOp() Methods
The three private methods are also straightforward. The setDisplay()
method sets the text property of the calculator display label to the value of the register whose name is passed as a parameter. The setCurrentRegister()
method sets register 1 as current if it is empty, and otherwise sets register 2 as current. The resetAfterOp()
method copies the result of an operation to register 1 and sets other values to prepare for further operations.
-
Add a private setDisplay()
method that takes a parameter called register
of type String
and returns nothing:
private function setDisplay(register:String):Void
{
}
-
In the method block, set the value of the calcView.calcDisplay.text
property to the passed register parameter:
calcView.calcDisplay.text = this[register];
-
Add a private setCurrentRegister()
method that takes no parameters and returns nothing:
private function setCurrentRegister():Void
{
}
-
In the method block, create an if-else
structure that sets the currentRegister
property to reg1 if reg1's current value is an empty string, and sets currentRegister
to reg2 otherwise:
if (reg1=="") {
currentRegister="reg1";
} else {
currentRegister="reg2";
}
-
Add a private resetAfterOp()
method that takes no parameters and returns nothing:
private function resetAfterOp():Void
{
}
-
In the method block, set the value of reg1
to the current value of result
, sets reg2
to an empty string, calls setDisplay()
, passing reg1 as the argument, and calls setOperation()
, passing none as the argument:
reg1=String(result);
reg2="";
setDisplay("reg1");
setOperation("none");
-
Save the file.
Note: This completes the CalculatorHandlers.as file.
Instantiate the CalculatorHandlers Class
The final step of the tutorial is to instantiate the CalculatorHandlers class you just created in the calculator.mxml file. You can do this through a tag, but since the tag will not be part of the Flex class library, you will have to add a new namespace for it. You do this by adding an additional xmlns
property to the Application
tag, giving it a value of "*". The "*" value simply indicates: "accept any class found in the application directory as an element."
Also remember that the CalculatorHandlers class needs a reference to the calculator Application object so that it can access the CalcDisplay object. You pass this reference by setting the calcView
property of the CalculatorHandlers object to {this}; in an MXML file, this is a reference to the overall Application object. The curly braces create data binding, so that the Flex compiler evaluates the contents as an expression rather that treating it as a literal string.
Finally, CalculatorHandlers object needs an instance name so that other elements in the calculator can refer to it; the instance name is the id
attribute of the tag that instantiates the class.
-
Return to calculator.mxml and add an additional xmlns
property to the Application
tag; give the value of the namespace the value of "*" and do not assign a prefix to it:
<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" xmlns="*">
-
At the beginning of the Application
tag block, add a CalculatorHandlers tag with the attributes id="calcHandlers"
and calcView="{this}"
:
<CalculatorHandlers id="calcHandlers" calcView="{this}"/>
- Save the file and test it in a web browser (for information on how to browse MXML files, see the first page in this tutorial).
Complete Code Listing for calculator.mxml
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" xmlns="*">
<!-- calculator controller -->
<CalculatorHandlers id="calcHandlers" calcView="{this}"/>
<!-- calculator view -->
<mx:Panel title="Calculator">
<!-- calculator display -->
<mx:Label id="calcDisplay" width="150" textAlign="right"/>
<!-- calculator controls -->
<mx:Grid>
<mx:GridRow>
<mx:GridItem colSpan="2">
<mx:Button width="70" label="Clear" click="calcHandlers.clearAll()"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="C/E" click="calcHandlers.clearEntry()"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="+" click="calcHandlers.setOperation('add')"/>
</mx:GridItem>
</mx:GridRow>
<mx:GridRow>
<mx:GridItem>
<mx:Button width="30" label="1" click="calcHandlers.addNumber('1')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="2" click="calcHandlers.addNumber('2')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="3" click="calcHandlers.addNumber('3')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="-" click="calcHandlers.setOperation('subtract')"/>
</mx:GridItem>
</mx:GridRow>
<mx:GridRow>
<mx:GridItem>
<mx:Button width="30" label="4" click="calcHandlers.addNumber('4')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="5" click="calcHandlers.addNumber('5')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="6" click="calcHandlers.addNumber('6')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="*" click="calcHandlers.setOperation('multiply')"/>
</mx:GridItem>
</mx:GridRow>
<mx:GridRow>
<mx:GridItem>
<mx:Button width="30" label="7" click="calcHandlers.addNumber('7')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="8" click="calcHandlers.addNumber('8')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="9" click="calcHandlers.addNumber('9')"/>
</mx:GridItem>
<mx:GridItem>
<mx:Button width="30" label="/" click="calcHandlers.setOperation('divide')"/>
</mx:GridItem>
</mx:GridRow>
<mx:GridRow>
<mx:GridItem>
<mx:Button width="30" label="0" click="calcHandlers.addNumber('0')"/>
</mx:GridItem>
<mx:GridItem >
<mx:Button width="30" label="." click="calcHandlers.addNumber('.')"/>
</mx:GridItem>
<mx:GridItem colSpan="2">
<mx:Button width="70" label="=" click="calcHandlers.doOperation()"/>
</mx:GridItem>
</mx:GridRow>
</mx:Grid>
</mx:Panel>
</mx:Application>
Complete Code Listing for CalculatorHandlers.as
/*
Calculator Controller
*/
class CalculatorHandlers
{
// properties
// object to hold a reference to the view object
public var calcView:Object;
// registers to hold temporary values pending operation
private var reg1:String="";
private var reg2:String="";
// result of an operation
private var result:Number;
// the name of the register currently used
private var currentRegister:String="reg1";
// the name of the next operation to be performed
private var operation:String="none";
// for convenience, holder for numerical equivalents
// of the register string values
private var r1:Number;
private var r2:Number;
// constructor
public function CalculatorHandlers()
{}
// methods
// perform the current operation on the 2 registers
public function doOperation():Void
{
// cast the register values to numbers
r1=Number(reg1);
r2=Number(reg2);
switch (operation)
{
case "add":
result=r1+r2;
resetAfterOp();
break;
case "subtract":
result=r1-r2;
resetAfterOp();
break;
case "multiply":
result=r1*r2;
resetAfterOp();
break;
case "divide":
result=r1/r2;
resetAfterOp();
break;
default:
// do nothing
}
}
// concatenate number to the value of the current register
public function addNumber(n:String):Void
{
if (operation=="none" && currentRegister=="reg2")
{
reg1="";
setCurrentRegister();
}
this[currentRegister]+=n;
setDisplay(currentRegister);
}
// clear the current register
public function clearEntry():Void
{
this[currentRegister]="";
setDisplay(currentRegister);
}
// clear both registers and the current operation
public function clearAll():Void
{
reg1="";
reg2="";
setCurrentRegister();
setOperation("none");
setDisplay(currentRegister);
}
// set the current operation
public function setOperation(operation:String):Void
{
this.operation=operation;
setCurrentRegister();
}
// set the value shown in the display
private function setDisplay(register:String):Void
{
calcView.calcDisplay.text = this[register];
}
// set which register is current
private function setCurrentRegister():Void
{
if (reg1=="")
{
currentRegister="reg1";
}
else
{
currentRegister="reg2";
}
}
// reset values after an operation
private function resetAfterOp():Void
{
reg1=String(result);
reg2="";
setDisplay("reg1");
setOperation("none");
}
}
Where to Go from Here
Although the calculator you have built here is simple, it works, and if you actually need to build a calculator of some kind, you could easily extend this one to include additional functionality. ActionScript includes a static Math class whose methods would do a lot of the work for you. More importantly, though, you should feel comfortable now with basic elements of MXML and ActionScript, and ready to start tackling more sophisticated applications.
Continue on to the following tutorials to learn more:
If you like what you see and want to get more experience with Flex,
Macromedia Customer Training offers Fast Track to Macromedia Flex, a two-day onsite training course to jump-start Flex development in your organization. You should also look at the informative set of sample applications in included with Flex; these illustrate the use of many Flex features.
About the author
Robert Crooks is the Director of Curriculum Development for Macromedia. He has written several courses on creating internet applications including the beta version of Fast Track to Macromedia Flex.