Table of Contents
In this section we draw on the concepts presented thus far to create a simple — calculator as shown below.
When you compile this application for SWF, you can uncomment the lines that embed fonts. This will import fonts that give a somewhat more polished look to the calculator.
As throughout this Guide, the examples are "live" and you can edit them. However, many of the examples are a bit long and not ideally suited to the example editor. So, if you wish to do some experimenting with this example you may wish to copy the source files and resources to whichever directory you use for development.
Keep in mind that this is an exercise in basic LZX, not in scripting, so there won't be too much discussion on how the script works.
Our first step will be to build the calculator visually. That is, to start with we will not worry about the functionality, we'll just make it look right. Let's start by bringing in all the art assets. There are a body, a display and a number of buttons.
Example 14.1. Using resource for body image
<canvas height="500" width="800"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png" /> <resource name="calc_display" src="../resources/display.png" /> <resource name="button_grn" src="../resources/button_green.gif" /> <resource name="button_red" src="../resources/button_red.gif" /> <resource name="button_blu" src="../resources/button_blue.gif" /> <view name="calculator" resource="calc_body" x="20" y="20"> </view> </canvas>
In this code we have brought in the fonts and art assets we plan to use, and created the body of the calculator. Remember, if you're compiling for DHTML you cannot use imported fonts.
We don't need to set the size of the view that is the calculator body, because it will stretch to the size of its resource automatically.
Next we add the display:
Example 14.2. Adding the display
<canvas width="800" height="500"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png" /> <resource name="calc_display" src="../resources/display.png" /> <resource name="button_grn" src="../resources/button_green.gif" /> <resource name="button_red" src="../resources/button_red.gif" /> <resource name="button_blu" src="../resources/button_blue.gif" /> <view name="calculator" resource="calc_body" x="20" y="20"> <!-- display --> <view id="display" resource="calc_display" x="20" y="31"> <!-- the text in the display --> <text name="screen" height="30" width="165" font="displayText" fontsize="30" y="5" x="5"> 0 </text> </view> </view> </canvas>
There are two parts to this. The first is the light gray art asset that is the display itself, and the second is the text in the display. We're naming everything with name attributes now, so that later we will be able to refer to these elements from scripts (for example, to change the text in the display).
Note that the text tag is within the display view tag. In fact it's convenient because it allows us to position the text tag relative to the display, which is how we think of it.
The buttons are all the same size, and they're arranged in a grid. The buttons are actually arranged in rows (or you might describe them as columns, but for the purposes of this tutorial, we're going to treat them as rows):
Ignore the fact that the first row of two buttons is odd (we'll come to that later). The above diagram shows how we're going to arrange them. Each button will be a view with a resource attached to it. We could have used images for the labels, but it is far easier to use text.
Here's the code for a single button:
Example 14.3. code for a single button
<view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 7 </text> </view>
The resource attribute tells the button what image to use. Remember the button will automatically size to fit the view. Next the clickable attribute means that this button will be clickable - it will have an onclick event, which will come to later and the mouse will change to a hand when it hovers over it. The text tag is the button's label.
The onclick event handler is calling a method that we haven't written yet.
The code for a row of buttons is as follows:
Example 14.4. code for a row of buttons
<!-- row 2 --> <view name="row2"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 7 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 8 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 9 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> * </text> </view> </view>
Notice that we're using the simplelayout tag to control the spacing of the buttons in the x-axis.
For four rows of buttons, we will just repeat the above code, and we shall space all the rows with a simplelayout in the y-axis. Here's the code so far:
Example 14.5. Spacing rows with <simplelayout>
<canvas height="500" width="800"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png" /> <resource name="calc_display" src="../resources/display.png" /> <resource name="button_grn" src="../resources/button_green.gif" /> <resource name="button_red" src="../resources/button_red.gif" /> <resource name="button_blu" src="../resources/button_blue.gif" /> <view name="calculator" resource="calc_body" x="20" y="20"> <!-- display --> <view id="display" resource="calc_display" x="20" y="31"> <!-- the text in the display --> <text name="screen" height="30" width="165" font="displayText" fontsize="30" y="5" x="5"> 0 </text> </view> <!-- 5 rows of buttons --> <view name="buttons" x="19" y="88"> <simplelayout axis="y" spacing="7" /> <!-- row 2 --> <view name="row2"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 7 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 8 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 9 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> * </text> </view> </view> <!-- row 3 --> <view name="row3"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 4 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 5 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 6 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="15" valign="middle"> - </text> </view> </view> <!-- row 4 --> <view name="row4"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 1 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 2 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 3 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> + </text> </view> </view> <!-- row 5 --> <view name="row5"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 0 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="20" fgcolor="#ffffff" x="5" valign="middle"> +/- </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> . </text> </view> <view resource="button_grn" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> = </text> </view> </view> </view> </view> </canvas>
The x and fontsize properties of the text elements have been tweaked to get the button labels to line up correctly.
Finally, let's build the top row of buttons:
Example 14.6. top row of buttons
<!-- row 1 --> <view name="row1" width="${parent.width}"> <view resource="button_red" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="7" valign="middle"> C </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );" align="right"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> / </text> </view> </view>
The "C" button will be automatically placed at x="0" y="0". The "/" button needs to be right aligned, hence the align="right" attribute.
The parent view of the buttons (i.e. one named buttons in the code above) will automatically size to fit rows 2, 3, 4 and 5, so it's OK to align the "/" button to the right, as long as we explicitly set the width of row1 to the same as buttons.
There is a lot of code here to make the calculator behave predictably. We can break this down into methods that perform tasks, and properties that retain values. Alternatively, if we didn't want to use methods and properties, we'd have to write a lot of functions and have a large number of global variables declared. All the methods that we write are going to be assigned to the display view, which is also the parent of the screen text field.
start() This is the method that sets up all the view properties.
clear() For clearing the screen and restarting. All this does is call the start method again, so that the properties get re initialized. We could have called the start method directly when we wanted to clear the screen.
inputDigit() The default method for hitting a key. This figures out what the user is typing in (whether it's a number, an operator, the equals sign and so forth) and takes the appropriate action.
inputOperator() When the user hits an operator ("+", "-", "*", "/") a few things could based on what the user has entered previously. For example, if the user has hits "1" then "*", nothing happens. But if the user has already entered two operands and an operator (e.g. "1" then "*" then "4" then "+") the first expression gets evaluated. This method handles all that logic.
equals() Called when the user hits the equals sign.
execute() The actual method that does any computation. Called from various circumstances.
isThereDecimal() Utility method for checking if a decimal point is shown on the display (to prevent one from being entered twice.
addDecimalPoint() Concatenates a decimal point to the end of the display
negative()valueX The first operand (value) to use.
oldValue This can be different things depending on the combination of buttons that were entered.
lastInput What was last entered (e.g. digit, equals, operator).
allOperators An array of values that are operators.
operator The active operator (e.g. "+").
isInArray This is a simple function that loops through an array to determine whether a value is contained within it. The reason it is not a method is because it's the kind of function that might be reused in another script, and it's handy to have it set aside like this.
So here's the full code:
Example 14.7. Completed calculator
<canvas height="500" width="800"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png" /> <resource name="calc_display" src="../resources/display.png" /> <resource name="button_grn" src="../resources/button_green.gif" /> <resource name="button_red" src="../resources/button_red.gif" /> <resource name="button_blu" src="../resources/button_blue.gif" /> <!-- global variables and functions --> <script> <![CDATA[ // utility function that finds an item in an array // function isInArray( x, theArray ) { var isThere = false; for ( var i=0; i<theArray.length; i++ ) { if ( theArray[i] == x ) { isThere = true; } } return isThere; } ]]> </script> <view name="calculator" resource="calc_body" x="20" y="20"> <!-- display --> <view id="display" resource="calc_display" x="20" y="31" oninit="this.start()"> <!-- the text in the display --> <text name="screen" height="30" width="165" font="displayText" fontsize="30" y="5" x="5"> 0 </text> <method name="start"> this.valueX = 0; this.lastInput = 'none'; this.oldValue = false; this.allOperators = new Array( '+', '-', '/', '*' ); this.operator = '+'; this.screen.setText( this.valueX.toString() ); </method> <method name="clear"> this.start(); </method> <!-- handles the push of a button --> <method name="inputDigit" args="buttonObj"> <![CDATA[ var val = buttonObj.buttonText.getText(); // weed out any non-numbers (e.g. operators, clear button) if ( isInArray( val, this.allOperators ) ) { var theOperator = val; this.inputOperator( theOperator ); return; } else if ( val == 'C' ) { this.start(); return; } else if ( val == '.' ) { this.addDecimalPoint(); return; } else if ( val == '+/-' ) { this.negative(); return; } else if ( val == '=' ) { this.equals(); return; } displ = this.screen.getText(); if ( displ == '0' ) if ( val == '0' ) { return; } if ( ( this.lastInput == 'none' ) || ( this.lastInput == 'operator' ) ) { // clear display and rewrite this.screen.setText( val ); } else if ( this.lastInput == 'digit' ) { this.screen.setText( displ + val ); } else if ( this.lastInput == 'equals' ) { this.clear(); this.screen.setText( val ); } this.lastInput = 'digit'; ]]> </method> <method name="inputOperator" args="operator"> if ( this.lastInput == 'digit' ) { this.execute( this.screen.getText() ); } this.valueX = this.screen.getText(); this.operator = operator; this.lastInput = 'operator'; </method> <method name="equals"> if (this.lastInput != 'equals' ) { this.oldValue = this.screen.getText(); this.lastInput = 'equals'; this.execute( this.oldValue ); } else { this.lastInput = 'equals'; this.execute( this.oldValue ); } </method> <method name="execute" args="val"> this.valueX -= 0; var valFromDisp = val - 0; if ( this.valueX == 0 ) return; if ( this.operator == '+' ) { val = this.valueX + valFromDisp; } else if ( this.operator == '-' ) { val = this.valueX - valFromDisp; } else if ( this.operator == '*' ) { val = this.valueX * valFromDisp; } else if ( this.operator == '/' ) { val = this.valueX / valFromDisp; } valFromDisp = val; this.screen.setText( valFromDisp.toString() ); this.valueX = this.screen.getText(); </method> <method name="isThereDecimal"> <![CDATA[ var disp = this.screen.getText(); var isDecimal = false; for ( var i=0; i < disp.length; i++ ) { if ( disp.charAt( i ) == '.' ) { return true; } } return false; ]]> </method> <method name="addDecimalPoint"> if ( ( this.lastInput == 'none' ) || ( this.lastInput == 'operator' ) ) { if ( !this.isThereDecimal() ) { this.screen.setText( "0." ); } } else if ( this.lastInput == 'digit' ) { if ( !this.isThereDecimal() ) { var newText = this.screen.getText(); newText += "."; this.screen.setText( newText ); } } else if ( this.lastInput == 'equals' ) { this.clear(); this.screen.setText( '0.' ); } this.lastInput = 'digit'; </method> <method name="negative"> if ( ( this.lastInput == 'digit' ) || ( this.lastInput == 'equals' ) ) { var newDisp = ( this.screen.getText() - 0 ) * -1; this.screen.setText( newDisp.toString() ); } else { clear(); } </method> </view> <!-- 5 rows of buttons --> <view name="buttons" x="19" y="88"> <simplelayout axis="y" spacing="7" /> <!-- row 1 --> <view name="row1" width="${parent.width}"> <view resource="button_red" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="7" valign="middle"> C </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );" align="right"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> / </text> </view> </view> <!-- row 2 --> <view name="row2"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 7 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 8 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 9 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> * </text> </view> </view> <!-- row 3 --> <view name="row3"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 4 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 5 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 6 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="15" valign="middle"> - </text> </view> </view> <!-- row 4 --> <view name="row4"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 1 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 2 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 3 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> + </text> </view> </view> <!-- row 5 --> <view name="row5"> <simplelayout axis="x" spacing="7" /> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> 0 </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="20" fgcolor="#ffffff" x="5" valign="middle"> +/- </text> </view> <view resource="button_blu" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> . </text> </view> <view resource="button_grn" clickable="true" onclick="display.inputDigit( this );"> <text name="buttonText" font="obliqueText" fontsize="30" fgcolor="#ffffff" x="9" valign="middle"> = </text> </view> </view> </view> </view> </canvas>
Notice that there is now an oninit event handler on the display that calls the start method.
Also notice how we're always calling the inputDigit method from the buttons with the same argument: We're passing the button element, and the inputDigit method works out what the button it is by calling the getText() method on its text element. This saves us the hassle of writing a different argument each time.
There is still a lot of code here, a lot of which is repetitive. We can condense it by using classes. For an introduction to classes in LZX, see Chapter 12, Introduction to Classes and Object Oriented Programming
Let's take a look at how we can use classes to simplyfy our code. We'll start by making a class for calculator buttons.
Consider a calculator button. Most of them are blue and they have a white label on them. The label varies from button to button. They all call a method when clicked. When we say blue, we really mean that they take a resource which happens to be blue. Some buttons take other images. In at least one case, we had to position the label explicitly so it would look right.
In short, what we need is a class that will:
Produce a blue button by default.
Allow us to specify a different resource.
Take a label attribute.
Allow the label to be explicitly positioned in the x plane to tweak it.
Here is the old code for a single button:
Example 14.8. Single button
<canvas width="800" height="80">
<!-- Uncomment to include fonts for SWF -->
<!-- fonts -->
<!--
<font src="helmetbi.ttf" name="obliqueText" />
-->
<!-- art assets -->
<resource name="button_blu" src="../resources/button_blue.gif"/>
<view resource="button_blu" clickable="true"
onclick="display.inputDigit(this);">
<text name="buttonText" font="obliqueText" fontsize="30"
fgcolor="#ffffff" x="9" valign="middle">
0
</text>
</view>
</canvas>
And here's how that might be translated into a class:
Example 14.9. Converting button to class
<canvas width="800" height="100"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> --> <resource name="button_blu" src="../resources/button_blue.gif"/> <resource name="button_red" src="../resources/button_red.gif"/> <resource name="button_green" src="../resources/button_green.gif"/> <class name="calcButton" resource="button_blu" clickable="true" onclick="display.inputDigit(this);" oninit="this.initButton()"> <attribute name="buttLabel" type="string"/> <attribute name="labelX" type="number" value="11"/> <method name="initButton"> this.buttonText.setAttribute('x', this.getAttribute('labelX')); this.buttonText.setText(this.getAttribute('buttLabel')); </method> <text name="buttonText" font="obliqueText" fontsize="25" fgcolor="#ffffff" x="13" valign="middle"> <handler name="ontext"> this.setAttribute('width', this.getTextWidth() + 5); </handler> </text> </class> <simplelayout axis="x" spacing="5"/> <calcButton buttLabel="0"/> <calcButton buttLabel="1" resource="button_red"/> <calcButton buttLabel="+/-" resource="button_green" labelX="0"/> </canvas>
We can give the class a default resource (button_blu
), as well as a default onclick attribute. Since these are
default attributes of the class we're extending (i.e. view), they get overwritten when we instantiate the class and set that
attribute.
However we need a method to handle the custom attributes buttLabel
and labelX
. That's why
we call the initButton()
method at init time, and set the label of the text and optionally the x-offset of the label
that way.
In this case we've made the fontsize of the view a little smaller, so that we don't have to worry about making the labels fit the buttons.
The method within the <text>
tag adjusts the size of the text field when the text gets set to the correct size
(plus a few extra pixels for good measure).
Applying this to the original calculator code, we get:
Example 14.10. Classes in calculator
<canvas width="800" height="400"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png"/> <resource name="calc_display" src="../resources/display.png"/> <resource name="button_grn" src="../resources/button_green.gif"/> <resource name="button_red" src="../resources/button_red.gif"/> <resource name="button_blu" src="../resources/button_blue.gif"/> <!-- classes --> <class name="calcButton" resource="button_blu" clickable="true" onclick="display.inputDigit(this);" oninit="this.initButton()"> <attribute name="buttLabel" type="string"/> <attribute name="labelX" type="number" value="11"/> <method name="initButton"> this.buttonText.setAttribute('x', this.getAttribute('labelX')); this.buttonText.setText(this.getAttribute('buttLabel')); </method> <text name="buttonText" font="obliqueText" fontsize="25" fgcolor="#ffffff" x="13" valign="middle" width="0"> <method event="ontext"> this.setAttribute('width', this.getTextWidth() + 5); </method> </text> </class> <!-- global variables and functions --> <script><![CDATA[ // utility function that finds an item in an array // function isInArray(x, theArray) { var isThere = false; for (var i=0; i<theArray.length; i++) { if (theArray[i] == x) { isThere = true; } } return isThere; } ]]></script> <view name="calculator" resource="calc_body" x="20" y="20"> <!-- display --> <view id="display" resource="calc_display" x="20" y="31" oninit="this.start()"> <!-- the text in the display --> <text name="screen" height="30" width="165" font="displayText" fontsize="30" y="5" x="5"> 0 </text> <method name="start"> this.valueX = 0; this.lastInput = 'none'; this.oldValue = false; this.allOperators = new Array('+', '-', '/', '*'); this.operator = '+'; this.screen.setText(this.valueX.toString()); </method> <method name="clear"> this.start(); </method> <!-- handles the push of a button --> <method name="inputDigit" args="buttonObj"> <![CDATA[ var val = buttonObj.buttonText.getText(); // weed out any non-numbers (e.g. operators, clear button) if (isInArray(val, this.allOperators)) { var theOperator = val; this.inputOperator(theOperator); return; } else if (val == 'C') { this.start(); return; } else if (val == '.') { this.addDecimalPoint(); return; } else if (val == '+/-') { this.negative(); return; } else if (val == '=') { this.equals(); return; } displ = this.screen.getText(); if (displ == 0) { if (val == 0) { return; } } if ((this.lastInput == 'none') || (this.lastInput == 'operator')) { // clear display and rewrite this.screen.setText(val); } else if (this.lastInput == 'digit') { this.screen.setText(displ + val); } else if (this.lastInput == 'equals') { this.clear(); this.screen.setText(val); } this.lastInput = 'digit'; ]]> </method> <method name="inputOperator" args="operator"> if (this.lastInput == 'digit') { this.execute(this.screen.getText()); } this.valueX = this.screen.getText(); this.operator = operator; this.lastInput = 'operator'; </method> <method name="equals"> if (this.lastInput != 'equals') { this.oldValue = this.screen.getText(); this.lastInput = 'equals'; this.execute(this.oldValue); } else { this.lastInput = 'equals'; this.execute(this.oldValue); } </method> <method name="execute" args="val"> this.valueX -= 0; var valFromDisp = val - 0; if (this.valueX == 0) return; if (this.operator == '+') { val = this.valueX + valFromDisp; } else if (this.operator == '-') { val = this.valueX - valFromDisp; } else if (this.operator == '*') { val = this.valueX * valFromDisp; } else if (this.operator == '/') { val = this.valueX / valFromDisp; } valFromDisp = val; this.screen.setText(valFromDisp.toString()); this.valueX = this.screen.getText(); </method> <method name="isThereDecimal"> <![CDATA[ var disp = this.screen.getText(); var isDecimal = false; for (var i=0; i != disp.length; i++) { if (disp.charAt(i) == '.') { return true; } } return false; ]]> </method> <method name="addDecimalPoint"> if ((this.lastInput == 'none') || (this.lastInput == 'operator')) { if (!this.isThereDecimal()) { this.screen.setText("0."); } } else if (this.lastInput == 'digit') { if (!this.isThereDecimal()) { var newText = this.screen.getText(); newText += "."; this.screen.setText(newText); } } else if (this.lastInput == 'equals') { this.clear(); this.screen.setText('0.'); } this.lastInput = 'digit'; </method> <method name="negative"> if ((this.lastInput == 'digit') || (this.lastInput == 'equals')) { var newDisp = (this.screen.getText() - 0) * -1; this.screen.setText(newDisp.toString()); } else { clear(); } </method> </view> <!-- 5 rows of buttons --> <view name="buttons" x="19" y="88"> <simplelayout axis="y" spacing="7"/> <!-- row 1 --> <view name="row1" width="100%"> <calcButton buttLabel="C" resource="button_red"/> <calcButton buttLabel="/" align="right" labelX="13"/> </view> <!-- row 2 --> <view name="row2"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="7"/> <calcButton buttLabel="8"/> <calcButton buttLabel="9"/> <calcButton buttLabel="*" labelX="13"/> </view> <!-- row 3 --> <view name="row3"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="4"/> <calcButton buttLabel="5"/> <calcButton buttLabel="6"/> <calcButton buttLabel="-" labelX="15"/> </view> <!-- row 4 --> <view name="row4"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="1"/> <calcButton buttLabel="2"/> <calcButton buttLabel="3"/> <calcButton buttLabel="+"/> </view> <!-- row 5 --> <view name="row5"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="0"/> <calcButton buttLabel="+/-" labelX="1"/> <calcButton buttLabel="." labelX="16"/> <calcButton buttLabel="=" resource="button_grn" labelX="12"/> </view> </view> </view> </canvas>
Not only does this make the code more navigable, but lightens the calculator application by over 100 lines of code.
Now, say that we wanted to upgrade the buttons we have and make them react to mouse events (e.g. highlight when rolled over and appear depressed when clicked). Since we've created a class, we only have to make the change in one place, and it will immediately be propagated to all of the instances of that class:
Example 14.11. Updating class definition
<canvas width="800" height="400"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png"/> <resource name="calc_display" src="../resources/display.png"/> <resource name="button_grn"> <frame src="../resources/new_button_green_off.png"/> <frame src="../resources/new_button_green_over.png"/> <frame src="../resources/new_button_green_down.png"/> </resource> <resource name="button_blu"> <frame src="../resources/new_button_blue_off.png"/> <frame src="../resources/new_button_blue_over.png"/> <frame src="../resources/new_button_blue_down.png"/> </resource> <resource name="button_red"> <frame src="../resources/new_button_red_off.png"/> <frame src="../resources/new_button_red_over.png"/> <frame src="../resources/new_button_red_down.png"/> </resource> <!-- classes --> <class name="calcButton" resource="button_blu" clickable="true" onclick="display.inputDigit(this);" oninit="this.initButton()" extends="basebutton"> <attribute name="buttLabel" type="string"/> <attribute name="labelX" type="number" value="11"/> <method name="initButton"> this.buttonText.setAttribute('x', this.getAttribute('labelX')); this.buttonText.setText(this.getAttribute('buttLabel')); </method> <text name="buttonText" font="obliqueText" fontsize="25" fgcolor="#ffffff" x="13" valign="middle" width="0"> <method event="ontext"> this.setAttribute('width', this.getTextWidth() + 5); </method> </text> </class> <!-- global variables and functions --> <script> <![CDATA[ // utility function that finds an item in an array // function isInArray(x, theArray) { var isThere = false; for (var i=0; i != theArray.length; i++) { if (theArray[i] == x) { isThere = true; } } return isThere; } ]]> </script> <view name="calculator" resource="calc_body" x="20" y="20"> <!-- display --> <view id="display" resource="calc_display" x="20" y="31" oninit="this.start()"> <!-- the text in the display --> <text name="screen" height="30" width="165" font="displayText" fontsize="30" y="5" x="5"> 0 </text> <method name="start"> this.valueX = 0; this.lastInput = 'none'; this.oldValue = false; this.allOperators = new Array('+', '-', '/', '*'); this.operator = '+'; this.screen.setText(this.valueX.toString()); </method> <method name="clear"> this.start(); </method> <!-- handles the push of a button --> <method name="inputDigit" args="buttonObj"> <![CDATA[ var val = buttonObj.buttonText.getText(); // weed out any non-numbers (e.g. operators, clear button) if (isInArray(val, this.allOperators)) { var theOperator = val; this.inputOperator(theOperator); return; } else if (val == 'C') { this.start(); return; } else if (val == '.') { this.addDecimalPoint(); return; } else if (val == '+/-') { this.negative(); return; } else if (val == '=') { this.equals(); return; } displ = this.screen.getText(); if (displ == '0') { if (val == '0') { return; } } if ((this.lastInput == 'none') || (this.lastInput == 'operator')) { // clear display and rewrite this.screen.setText(val); } else if (this.lastInput == 'digit') { this.screen.setText(displ + val); } else if (this.lastInput == 'equals') { this.clear(); this.screen.setText(val); } this.lastInput = 'digit'; ]]> </method> <method name="inputOperator" args="operator"> if (this.lastInput == 'digit') { this.execute(this.screen.getText()); } this.valueX = this.screen.getText(); this.operator = operator; this.lastInput = 'operator'; </method> <method name="equals"> if (this.lastInput != 'equals') { this.oldValue = this.screen.getText(); this.lastInput = 'equals'; this.execute(this.oldValue); } else { this.lastInput = 'equals'; this.execute(this.oldValue); } </method> <method name="execute" args="val"> this.valueX -= 0; var valFromDisp = val - 0; if (this.valueX == 0) return; if (this.operator == '+') { val = this.valueX + valFromDisp; } else if (this.operator == '-') { val = this.valueX - valFromDisp; } else if (this.operator == '*') { val = this.valueX * valFromDisp; } else if (this.operator == '/') { val = this.valueX / valFromDisp; } valFromDisp = val; this.screen.setText(valFromDisp.toString()); this.valueX = this.screen.getText(); </method> <method name="isThereDecimal"> <![CDATA[ var disp = this.screen.getText(); var isDecimal = false; for (var i=0; i !=disp.length; i++) { if (disp.charAt(i) == '.') { return true; } } return false; ]]> </method> <method name="addDecimalPoint"> if ((this.lastInput == 'none') || (this.lastInput == 'operator')) { if (!this.isThereDecimal()) { this.screen.setText("0."); } } else if (this.lastInput == 'digit') { if (!this.isThereDecimal()) { var newText = this.screen.getText(); newText += "."; this.screen.setText(newText); } } else if (this.lastInput == 'equals') { this.clear(); this.screen.setText('0.'); } this.lastInput = 'digit'; </method> <method name="negative"> if ((this.lastInput == 'digit') || (this.lastInput == 'equals')) { var newDisp = (this.screen.getText() - 0) * -1; this.screen.setText(newDisp.toString()); } else { clear(); } </method> </view> <!-- 5 rows of buttons --> <view name="buttons" x="19" y="88"> <simplelayout axis="y" spacing="7"/> <!-- row 1 --> <view name="row1" width="100%"> <calcButton buttLabel="C" resource="button_red"/> <calcButton buttLabel="/" align="right" labelX="13"/> </view> <!-- row 2 --> <view name="row2"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="7"/> <calcButton buttLabel="8"/> <calcButton buttLabel="9"/> <calcButton buttLabel="*" labelX="13"/> </view> <!-- row 3 --> <view name="row3"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="4"/> <calcButton buttLabel="5"/> <calcButton buttLabel="6"/> <calcButton buttLabel="-" labelX="15"/> </view> <!-- row 4 --> <view name="row4"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="1"/> <calcButton buttLabel="2"/> <calcButton buttLabel="3"/> <calcButton buttLabel="+"/> </view> <!-- row 5 --> <view name="row5"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="0"/> <calcButton buttLabel="+/-" labelX="1"/> <calcButton buttLabel="." labelX="16"/> <calcButton buttLabel="=" resource="button_grn" labelX="12"/> </view> </view> </view> </canvas>
Aside from defining and naming the new multi-frame resources at the
top of the code, the only other change was to extend the
basebutton
class instead of the
LzView
class. The
basebutton
class is a predefined class that
gives simple mouse rollover and click behavior to a view with a
multiframe resource. Since the basebutton
class
extends LzView
, it inherits all
LzView
's attributes. This means that we can
still use the same attributes (e.g. onclick
and
resource
) in our new calcButton
class, as we could in the original
<view>
tag, which used to be the button.
Although we've saved a lot of typing, the lzx file is still quite long. This may not be much of an issue if you're writing this application on your own, but one of the great advantages of using classes is that a project can be divided up among developers with ease. To do this, we're going to break the calculator application across a number of files, using classes as a divider.
Since we already have the button separated out as a class, let's start with that one:
Example 14.12. Grouping classes in libraries
calculator_button.lzx: <library> <!-- A standard calculator button --> <resource name="button_grn"> <frame src="./resources/new_button_green_off.png"/> <frame src="./resources/new_button_green_over.png"/> <frame src="./resources/new_button_green_down.png"/> </resource> <resource name="button_blu"> <frame src="./resources/new_button_blue_off.png"/> <frame src="./resources/new_button_blue_over.png"/> <frame src="./resources/new_button_blue_down.png"/> </resource> <resource name="button_red"> <frame src="./resources/new_button_red_off.png"/> <frame src="./resources/new_button_red_over.png"/> <frame src="./resources/new_button_red_down.png"/> </resource> <class name="calcButton" resource="button_blu" clickable="true" onclick="display.inputDigit(this);" oninit="this.initButton()" extends="basebutton"> <attribute name="buttLabel" type="string"> <attribute name="labelX" type="number" value="11"> <method name="initButton"> this.buttonText.setAttribute('x', this.getAttribute('labelX')); this.buttonText.setText(this.getAttribute('buttLabel')); </method> <text name="buttonText" font="obliqueText" fontsize="25" fgcolor="#ffffff" x="13" valign="middle"> <method event="ontext"> this.setAttribute('width', this.getTextWidth() + 5); </method> </text> </class> </library>
Notice that the <library>
tag replaces the <canvas>
when we're breaking apart code like this.
It's also worth noting that the resources included by calculator_button.lzx are referenced relative to it.
Next we're going to separate out the display. Even though there is only one instance of the display, it's OK to turn it into a class:
Example 14.13. Placing display in a library
calculator_display.lzx: <library> <resource name="calc_display" src="../resources/display.png"/> <!-- The calculator display --> <class name="calcDisplay" resource="calc_display" x="20" y="31" oninit="this.start()"> <!-- the text in the display --> <text name="screen" height="30" width="165" font="displayText" fontsize="30" y="5" x="5"> 0 </text> <method name="start"> this.valueX = 0; this.lastInput = 'none'; this.oldValue = false; this.allOperators = new Array('+', '-', '/', '*'); this.operator = '+'; this.screen.setText(this.valueX.toString()); </method> <method name="clear"> this.start(); </method> <!-- handles the push of a button --> <method name="inputDigit" args="buttonObj"> <![CDATA[ var val = buttonObj.buttonText.getText(); // weed out any non-numbers (e.g. operators, clear button) if (isInArray(val, this.allOperators)) { var theOperator = val; this.inputOperator(theOperator); return; } else if (val == 'C') { this.start(); return; } else if (val == '.') { this.addDecimalPoint(); return; } else if (val == '+/-') { this.negative(); return; } else if (val == '=') { this.equals(); return; } displ = this.screen.getText(); if ((displ == '0') && (val == '0')) { return; } if ((this.lastInput == 'none') || (this.lastInput == 'operator')) { // clear display and rewrite this.screen.setText(val); } else if (this.lastInput == 'digit') { this.screen.setText(displ + val); } else if (this.lastInput == 'equals') { this.clear(); this.screen.setText(val); } this.lastInput = 'digit'; ]]> </method> <method name="inputOperator" args="operator"> if (this.lastInput == 'digit') { this.execute(this.screen.getText()); } this.valueX = this.screen.getText(); this.operator = operator; this.lastInput = 'operator'; </method> <method name="equals"> if (this.lastInput != 'equals') { this.oldValue = this.screen.getText(); this.lastInput = 'equals'; this.execute(this.oldValue); } else { this.lastInput = 'equals'; this.execute(this.oldValue); } </method> <method name="execute" args="val"> this.valueX -= 0; var valFromDisp = val - 0; if (this.valueX == 0) return; if (this.operator == '+') { val = this.valueX + valFromDisp; } else if (this.operator == '-') { val = this.valueX - valFromDisp; } else if (this.operator == '*') { val = this.valueX * valFromDisp; } else if (this.operator == '/') { val = this.valueX / valFromDisp; } valFromDisp = val; this.screen.setText(valFromDisp.toString()); this.valueX = this.screen.getText(); </method> <method name="isThereDecimal"> <![CDATA[ var disp = this.screen.getText(); var isDecimal = false; for (var i=0; i != disp.length; i++) { if (disp.charAt(i) == '.') { return true; } } return false; ]]> </method> <method name="addDecimalPoint"> if ((this.lastInput == 'none') || (this.lastInput == 'operator')) { if (!this.isThereDecimal()) { this.screen.setText("0."); } } else if (this.lastInput == 'digit') { if (!this.isThereDecimal()) { var newText = this.screen.getText(); newText += "."; this.screen.setText(newText); } } else if (this.lastInput == 'equals') { this.clear(); this.screen.setText('0.'); } this.lastInput = 'digit'; </method> <method name="negative"> if ((this.lastInput == 'digit') || (this.lastInput == 'equals')) { var newDisp = (this.screen.getText() - 0) * -1; this.screen.setText(newDisp.toString()); } else { clear(); } </method> </class> </library>
The <view>
tag changes to a <class>
tag, and the id attribute is removed. This is because if
we used an id attribute here (i.e. in the class definition, then every instance of that class would have the same
id (which would cause problems, since IDs are supposed to be unique!). We also added a name
attribute, which will get used to create
instances of the class. The resource for the display is also included in the display library file.
Finally, the code for the calculator itself:
Example 14.14. Calculator implemented with libraries
<canvas width="800" height="400"> <!-- Uncomment to include fonts for SWF --> <!-- fonts --> <!-- <font src="helmetbi.ttf" name="obliqueText" /> <font src="helmetr.ttf" name="displayText" /> --> <!-- art assets --> <resource name="calc_body" src="../resources/body.png"/> <!-- includes --> <include href="../calculator_button.lzx"/> <include href="../calculator_display.lzx"/> <!-- global variables and functions --> <script> <![CDATA[ // utility function that finds an item in an array // function isInArray(x, theArray) { var isThere = false; for (var i=0; i<theArray.length; i++) { if (theArray[i] == x) { isThere = true; } } return isThere; } ]]> </script> <view name="calculator" resource="calc_body" x="20" y="20"> <!-- display --> <calcDisplay id="display"/> <!-- 5 rows of buttons --> <view name="buttons" x="19" y="88"> <simplelayout axis="y" spacing="7"/> <!-- row 1 --> <view name="row1" width="100%"> <calcButton buttLabel="C" resource="button_red"/> <calcButton buttLabel="/" align="right" labelX="13"/> </view> <!-- row 2 --> <view name="row2"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="7"/> <calcButton buttLabel="8"/> <calcButton buttLabel="9"/> <calcButton buttLabel="*" labelX="13"/> </view> <!-- row 3 --> <view name="row3"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="4"/> <calcButton buttLabel="5"/> <calcButton buttLabel="6"/> <calcButton buttLabel="-" labelX="15"/> </view> <!-- row 4 --> <view name="row4"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="1"/> <calcButton buttLabel="2"/> <calcButton buttLabel="3"/> <calcButton buttLabel="+"/> </view> <!-- row 5 --> <view name="row5"> <simplelayout axis="x" spacing="7"/> <calcButton buttLabel="0"/> <calcButton buttLabel="+/-" labelX="1"/> <calcButton buttLabel="." labelX="16"/> <calcButton buttLabel="=" resource="button_grn" labelX="12"/> </view> </view> </view> </canvas>
We used the same id attribute that we had in the <view>
tag of the display in the instance of the calcDisplay class.
The calculator app is now broken up across three files:
calculator_main.lzx
calculator_display.lzx
calculator_display.lzx
At the very least, classes help minimize the amount of typing you have to do, as well as giving you an intuitive system for dividing up and organizing your code. LZX was designed to be an Object Oriented language, and the use of classes in LZX goes much further than saving you typing.
For a deeper discussion of using classes in LZX, see the Chapter 28, Classes and Chapter 33, Extending Classes chapters of the Guide
Copyright © 2002-2007 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.
Copyright © 2002-2007 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.