For this mega-installment of our introduction to Object-Oriented Programming in ActionScript 3, we'll take a look at a few outstanding topics, as well as putting everything together and organizing a simple project around several independent, yet cooperative, objects.
If you've been following along with the AS3 101 series so far, you'll have no problem with the general techniques involved in this tutorial. You'll get to focus on how the various elements of the entire Flash movie interact with each other and learn the fundamentals of building Object-Oriented applications. If you find this is a bit over your head, I would advise you to backtrack and brush up on the other topics covered by this series, namely those involving working with XML, loading external images, and the first two parts of this OOP series.
Step 1: Welcome Back
I'm going to start this tutorial by talking about about a few concepts that didn't make it into the previous OOP tutorials. The idea of static members, packages, imports, source paths, and composition are all going to be necessary for both the image viewer project as well as your general on-going Object-Oriented Programmer lifestyle. As with most of my AS3 101 tutorials, we'll focus largely on the concepts and the theory first, and then utilize them in a more practical manner later.
Step 2: Static Cling
All of the properties and methods that we have been writing so far are known as instance properties (or instance methods, as appropriate). What does that mean? That the properties and methods belong to the instance. Remember how we said that all houses built from the same blueprint have a front door, but that the characteristics of the door might be unique on a per-house basis? Yeah, that was a long time ago, way back up at the beginning of the first OOP tutorial, but that's what an instance property is. Each instance can have its own, unique value stored in that variable. It's possible to give one Button101
instance a label of "Abracadabra" and another instance a label of "Hocus Pocus". Because each object has its own property, each instance has its own value stored in it.
However, what if you just need a place to store a value, a value that doesn't need to change independently with each object that is instantiated? What if Button101
had a corner radius property that you wanted to keep consistent across all button instances, so that all buttons had a consistent appearance?
If that's the case, then why does every instance need its own copy of the same value?
It doesn't. If we're confident that all instances will be able to share the same value, then we can have all instances actually share the same property. We do this by declaring the property as being static. That means that the property (or method, if you were to do the same thing to a method) doesn't belong to the instance, but to the class. This is like stating on the blueprint that all houses must have a red-painted wooden door.
What this looks like in practice is this:
private static var cornerRadius:Number = 5;
All we did is add the static
keyword. That turns it into a class property (or, again, a class method). These are also called static properties (or... yeah, you get the idea).
Note that, like the override
keyword, the order between static
and public/private/internal/protected
is inconsequential. Again, though, it's wise to pick a standard and stick with it.
If we do this (declare the property as static, not pick a standard), then all instances will have the same value for cornerRadius
. This isn't a copy of the value; this is actually a reference to the same value across all instances.
Why do this? Well, for one, there are places where it just makes sense. In the scenario outlined in this step, it makes sense, because we never have a variation in that particular value. For another, declaring properties or methods as static can reduce memory usage. Since the property points to a single value in memory, and not unique instances of similarly-named values, there is a smaller impact on memory. Obviously, in our simple example, creating a single Number
variable instead of two is hardly worth considering, but in cases where objects are created several times over and the value contained within a property is a significant data structure, then static properties might produce a meaningful savings.
Keep an eye out for a tangential discussion of the when to use static methods and properties in the near future.
For a more in-depth look at when to utilize static methods and properties, look out for a soon-to-be-published Quick Tip on Activetuts+.
Step 3: Packages
Another leftover is the notion of packages. We've been blithely unaware of packages until now, but I think you're ready to handle it. Packages are just a way of organizing your code intro group and sub-groups. And it has a lot to do with that package
keyword that starts off every class file.
Technically, we've been using packages all along. It's impossible not to; that package
keyword defines the package to which your class belongs. However, we've been using what's called the top-level package. When you write this:
package { // Class code here... }
You're placing your class in the top-level package.
For various reasons, though, you might want to start putting your class files in folders. Organization is probably the most common reason; you'd like to keep various aspects of a project grouped together and segregated from one another, as necessary. You may also wish to reuse a class name. If you could keep the Main
class of one SWF in a separate folder from the Main
class of another SWF, then you could use the name twice.
Packages are just the way your classes are organized into folders. It's a two-step process: first you need to create your file in a folder structure reflecting the organization you desire. Then you mirror the folder structure after the package
keyword in your class file.
Step 4: Create an Example Package
We'll create a sample package right now, to illustrate how this is done. We'll actually retrofit the previous tutorial's project to use packages. If you don't have those files handy, a copy of it can be found in the download package as "button_tutorial."
In your file system, create a folder to house the entire project.
Copy the Flash file into this folder
Create a series of folders within that folder. They should follow this structure:
- [project folder]/
- as3101/
- sample/
- ui/
- as3101/
Now, in the as3101/sample/
folder, copy the DocumentClass file . Copy the Button101 class to the as3101/ui/
folder.
That's the first part; we've got an organizational scheme on our filesystem. Now we need to edit those two files so that the package matches this folder structure.
Open up DocumentClass, and edit the first line to read:
package as3101.sample {
Similarly, open up Button101 and make the first line read:
package as3101.ui {
In other words, we edited the files so that the bit between package
and the opening curly brace reflects the folder structure leading to the class file itself, relative to the Flash file. We use dots to separate the folder elements, though, not slashes or backslashes. Note that the class name itself is not included; the package is just the enclosing folders. The class name is still listed after the class
keyword.
As an organizational technique, this opens up many more possibilities for structuring your projects. As you write more and more classes, you'll want to group related classes together and create hierarchies of groupings. Packages will let you do just that.
However, we're not done here; if you try to run the project right now you'll get nothing; because we've moved the DocumentClass, the Flash file can't find it anymore.
Step 5: Fully-Qualified Class Names
One subtle point that's worthwhile to keep in mind is that as soon as you use a package, your class name got more complex. Now, for all intents and purposes, our two classes are still called "DocumentClass
" and "Button101
." When you discuss them with other team members, you can refer to them by those names, and even in code you can still use those names. However, technically, the use of a package turns the class name into a longer name. The name becomes package + "." + class name
. That is, our DocumentClass
is really as3101.sample.DocumentClass
, and Button101
is really as3101.ui.Button101
.
This is known as the fully-qualified class name, and it will play an important part in organizing our class files.
You may also see it in this form: as3101.ui::Button101
. This doesn't really mean anything different from the all-dot version. Just know how to read it (you'll see this most commonly when runtime error occur, the error describes the classes in which the error occurred).
So, why does our Flash document now do nothing? Because, in the document properties, the Document Class is set to DocumentClass
. But our class is actually called as3101.sample.DocumentClass
. Normally, this kind of mis-naming causes a compiler error, but in this situation Flash is trying to be helpful. It knows to look for a class called DocumentClass
to be the Flash file's document class. But it can't find it (that exact class name no longer exists since we've moved the class to a package). So, Flash silently creates a document class, called DocumentClass
for us. As it happens, this class does nothing except extend MovieClip
and be called DocumentClass
. Therefore, if we hit "Test Movie," we get nothing; an empty SWF, reflective of the empty document class created for us.
Before we correct this, first prove this by opening up the publish settings (File > Publish Settings…) for the document. Click on the "Settings…" button next to the "Script: ActionScript 3.0" control. In the resulting window, click on the checkmark button next to the "Document class:" input:

This will result in the following error message:

This is Flash's way of saying the same thing I just told you. Only Flash won't tell you that unless you take some kind of action like we just did.
To set things right, change the value of that Document class field to as3101.sample.DocumentClass
. When you press Return, nothing should happen (the other option is to get a similar warning dialog). When you click the pencil icon, you should see the class open up. These both mean we have successfully re-linked our Flash file to its newly re-named document class.
However, if you test now, things still don't work. On the bright side, you will at least get some errors saying why things don't work:

In effect, we're experiencing the same problem with the Button101
class as we were with the DocumentClass
.
Step 6: Imports
Once you start putting classes into packages, you suddenly need to deal with telling Flash where to find them. Be wary of the issue uncovered in the last step, but fortunately everywhere else the matter of telling Flash where to find classes is a simple line of code. This is the import
statement.
You've already used them, and can probably already guess why they're there. We've written quite a few import flash.display.Sprite;
sorts of lines in the last two tutorials, but now we get to write them for our own classes.
The error produced at the end of the last step said something about a type not being found: Button101
. That might be a little confusing (and admittedly, Adobe's compiler errors tend to be worded in a less-than-helpful way). This just means that we've used something called Button101
but never defined what that is. We're using a class, but we're not using that fully-qualified name to point Flash in the right direction.
You might assume that all you need to do is something like this:
var button:as3101.ui.Button101 = new as3101.ui.Button101();
It's not that there's something wrong with using the fully-qualified name, but it does not solve the problem at hand. In order to let Flash know where this newly-relocated Button101
class is, we need to import it.
import
lines need to go before the class
declaration. That it, after the package opens, but before the class opens. Like this:
package as3101.sample { // Imports go here. public class DocumentClass extends MovieClip { // ...
They can be in any order you like, but they need to be grouped in that spot. Most developers take the time to alphabetize their import statements (in fact, Flash Builder does this for you).
Now, to solve our current problem, we just import the Button101
class:
package as3101.sample { import as3101.ui.Button101; // Other imports... public class DocumentClass extends MovieClip { // ...
Note that once a class has been imported using its fully-qualified name, it is available to the class by it's short name. So, the lines that read var button:Button101 = new Button101();
don't need changed. There's nothing wrong, per se, with using the fully-qualified name at this point. But the import allows us to use the shorthand name, which means we have less characters cluttering up the code, which is a good thing.
Test the movie now, and, with Flash aware of where the various classes are located, you should be back in business.
One little extra note on imports: you technically don't need to write import statements for classes that are in the same package as the class doing the importing. It won't hurt anything to include them, though, and in my mind, the self documenting nature import statements makes that a worthwhile practice.
The finished files for this example (including the package change, the inclusion of imports, and the updated FLA to reflect the new Document class) are in the download package as "button_tutorial_packages".
To summarize these points on imports:
- If the class you want to use is in a different package, you must use the
import
statement. - This allows you to use the "short name" of the class, but it's OK to use the fully-qualified name if you need to.
- If the class you want to use is in the same package, the
import
statement is not required, but it's OK to use it if you want.
Step 7: Wildcard Imports
Now, you should know there are two ways to import a class. One is the way we just did: list the class using its fully-qualified name.
However, you may opt to use the wildcard import. It looks like this:
import as3101.ui.*;
And it does what you probably expect it to. Any class inside of the ui
package is now available to the class doing the import by its short name.
There are a few things to note about this technique.
- You can't do this:
import as3101.*;
. That won't get you anything. Wildcards only import classes that are direct children of
the package specified. Since Button101 and DocumentClass are both members of sub-packages of as3101, we would achieve nothing by
that attempt. - Using the wildcard does not automatically compile every class in the package into your SWF. I repeat: it does not.
The Flash compiler is smart enough to compile only the classes that it sees are being used by your SWF. You do not need
to worry about needless filesize increases by using the wildcard.
Knowing this, using wildcards or not should become a personal preference. Personally, I only use them on the Flash Player classes (the ones that start with flash.…
). In those cases, I use so many flash.net
or flash.display
classes that it's just easier to make sure they're all available without having to trot back to the import section and add a new import. However, with the classes that I write, I find it useful to specifically list the imports individually, as a form of documentation. You can look at the import section and see on which other classes this particular class relies. The choice is up to you, however, and I'd advise you determine your convention and stick to it.
I will be writing a Quick Tip that delves deeper into imports. That will be released before long, so keep your ear to the ground if you'd like to know more about the subtler details of import statements.
For a deeper discussion on wildcard imports keep an eye out for a coming Activetuts+ Quick Tip on the subject..
Step 8: Source Paths
If we're going to talk about using packages to organize our class files, we should then extend the discussion to source paths. A source path is a directory in which Flash will look for classes to compile. By default, every Flash document knows to look in the Flash document's own directory for class files. This is why our examples have worked so far; our classes have always been in the same folder as the Flash document (keep in mind that our recent venture into packages technically puts the class file into another folder, but that the fully-qualified class name resolves the class to a series of folders plus the file. And the root folder of our packages is in the same folder as the Flash document).
Now, there are any number of reasons why you'd want to store classes in some other directory. Two of the most common are:
- You have reusable utility classes (possibly TweenMax, Away3D, or something useful you've written). You want to use these classes
across all projects, and don't want to have to copy the files from one project to the next. - You would like to organize your project so that, for whatever reason, your Flash files are in a different directory from your class
files. This actually makes sense when you have a large project involving many Flash files. Just to keep the project structure
clearer, you might decide to keep a "classes" folder and a "flas" folder. This way Flash files are grouped together, but can still
share classes common across the project.
Naturally, you can accomplish these tasks. I wouldn't have brought them up if you couldn't. I'm mean, but not that mean.
And, of course, you have two options for doing this. Both involve defining a source path. You can define them at the application level (in which case the source path is available to every Flash document you open on that machine), or at the document level (in which case the source path is only available to that document). In the case of reason #1 above, the application level source path makes sense. As for reason #2, the document level source path would be better.
To Define an Application-Level Source Path
Open your Flash preferences (on the Mac: Flash > Preferences; on the PC: Edit > Preferences). Click on the ActionScript category on the left. At the bottom of the resulting window, click on the "ActionScript 3.0 Settings…" button.

In the resulting window, there are three areas: source paths is the top one.

Click on the "+" button and type in your path. Or click on the folder button to browse to it. However you enter it, once it's there, click OK until you're back to Flash. At this point, you can freely import and use any class from that directory.
To Define a Document-Level Source Path
With the document open, open the Publish Settings (go to File > Publish Settings…). Click on the "Flash" tab. Click on the "Settings…" button next to the ActionScript version control. A new window opens up, and this will be very similar to the window you get with the application preferences. It's layout is a bit different, but the idea is the same. Make sure you're on "Source Path" and type in your path. Click "OK" and remember that this is for this document only.

Between packages and source paths, you have many options for organizing your classes.
Step 9: Composition
In the previous tutorial, we focused quite a bit on inheritance. There is a technique that might be considered oppositely-wound from inheritance. It's called composition.
Now, there is nothing wrong with inheritance. It's not like I sat of a throne of lies and fed you malicious taradiddle in that last tutorial. But you might come across the phrase "prefer composition over inheritance" in your Object-Oriented journey. If (when) you get to learning design patterns you will most certainly hear it, and you will even see it manifest if you hang around advanced coders long enough. Big Spaceship recently (as of this writing) posted a discussion of the methodology they used when designing the display package of their github-ed ActionScript library. In it, they explain their reasons for, essentially, composition instead of inheritance.
So what is it? The good news is that you already know how to do it, and have been doing it all along. Composition is just when one object stores another object in a property. If your class has a property called myLoader
, and stores in it a Loader
object, that your class is said to compose the Loader object. This happens all the time; the Loader
itself, in fact, has a contentLoaderInfo
property that, itself, composes a LoaderInfo
object, which gives you information about the actual loading.
Step 10: The Subtle Difference
Composition seems like a far cry from inheritance, doesn't it? The two probably don't seem related at all. But consider this: In the last tutorial, we spent most of the time making a Button101
class that extended Sprite
. This made it everything that Sprite
is (and everything a DisplayObject
is, and everything a DisplayObjectContainer
is, and…, well, you remember that discussion). This was certainly useful, and again, I'm not here to tell you that it was the wrong way to do it.
But an alternate way to do it would be to not extend anything in particular, and instead let the Button101
class instantiate its own Sprite
object, or receive a target Sprite
object as a constructor parameter, or in some way compose the Sprite
and not inherit it.
How this would work may not be immediately clear. Allow me to throw a bunch of code at you in a shotgun-like effort to make this more clear.
First, let's consider the original implementation. Here is, in full, the Button101
class from the last tutorial (you may find the project in the "button_tutorial" folder in the download package):
package { import flash.display.Shape; import flash.display.Sprite; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFormat; public class Button101 extends Sprite { private var bgd:Shape; private var labelField:TextField; private var _url:String; public function Button101() { bgd = new Shape(); bgd.graphics.beginFill(0x999999, 1); bgd.graphics.drawRect(0, 0, 200, 50); addChild(bgd); labelField = new TextField(); labelField.width = 200; labelField.height = 30; labelField.y = 15; var format:TextFormat = new TextFormat(); format.align = "center"; format.size = 14; format.font = "Verdana"; labelField.defaultTextFormat = format; addChild(labelField); addEventListener(MouseEvent.ROLL_OVER, onOver); addEventListener(MouseEvent.ROLL_OUT, onOut); mouseChildren = false; buttonMode = true; } public function set label(text:String):void { labelField.text = text; var autoWidth:Number = Math.max(200, labelField.textWidth + 40); this.width = autoWidth; } public function get label():String { return labelField.text; } private function onOver(e:MouseEvent):void { bgd.alpha = 0.8; } private function onOut(e:MouseEvent):void { bgd.alpha = 1; } public function set url(theUrl:String):void { if (theUrl.indexOf("http://") == -1) { theUrl = "http://" + theUrl; } _url = theUrl; } public function get url():String { return _url; } override public function set width(w:Number):void { labelField.width = w; bgd.width = w; } override public function set height(h:Number):void { labelField.height = h; labelField.y = (h - labelField.textHeight) / 2 - 3; bgd.height = h; } } }
And here is the snippet of code from the DocumentClass class that set up a Button101
:
button = new Button101(); button.x = 10; button.y = 200; button.label = "Active Tuts"; button.url = "http://active.tutsplus.com"; addChild(button); button.addEventListener(MouseEvent.CLICK, onButtonClick);
Now, here is equivalent code, only utilizing composition instead of inheritance. This is the updated Button101
class (changed and added lines are highlighted):
package { import flash.display.Shape; import flash.display.Sprite; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFormat; public class Button101 { private var _target:Sprite; private var bgd:Shape; private var labelField:TextField; private var _url:String; public function Button101() { _target = new Sprite(); bgd = new Shape(); bgd.graphics.beginFill(0x999999, 1); bgd.graphics.drawRect(0, 0, 200, 50); _target.addChild(bgd); labelField = new TextField(); labelField.width = 200; labelField.height = 30; labelField.y = 15; var format:TextFormat = new TextFormat(); format.align = "center"; format.size = 14; format.font = "Verdana"; labelField.defaultTextFormat = format; _target.addChild(labelField); _target.addEventListener(MouseEvent.ROLL_OVER, onOver); _target.addEventListener(MouseEvent.ROLL_OUT, onOut); _target.mouseChildren = false; _target.buttonMode = true; } public function set label(text:String):void { labelField.text = text; var autoWidth:Number = Math.max(200, labelField.textWidth + 40); this.width = autoWidth; } public function get label():String { return labelField.text; } private function onOver(e:MouseEvent):void { bgd.alpha = 0.8; } private function onOut(e:MouseEvent):void { bgd.alpha = 1; } public function set url(theUrl:String):void { if (theUrl.indexOf("http://") == -1) { theUrl = "http://" + theUrl; } _url = theUrl; } public function get url():String { return _url; } public function set width(w:Number):void { labelField.width = w; bgd.width = w; } public function set height(h:Number):void { labelField.height = h; labelField.y = (h - labelField.textHeight) / 2 - 3; bgd.height = h; } public function get target():Sprite { return _target; } } }
And here is the updated snippet of code required to set up the updated Button101
(again, changes are highlighted):
button = new Button101(); button.target.x = 10; button.target.y = 200; button.label = "Active Tuts"; button.url = "http://active.tutsplus.com"; addChild(button.target); button.target.addEventListener(MouseEvent.CLICK, onButtonClick);
It's not much more code. It's more about the addition of the _target
property and the usage of it instead of the implicit this
where Sprite
-like things were concerned.
Here's a more thorough breakdown:
- First, on line 8, we remove the
extends Sprite
bit so that we're not utilizing inheritance. - On line 10, we added the
target
property. This will store our Sprite (that is, it composes the Sprite) that was previously
being inherited. This sets up with composition, and not inheritance. - On line 17, the first line of the constructor, we create a new
Sprite
and stash it in our_target
property. At this point,
we're successfully using composition. - On lines 22, 33, and 35-39, we've simply added a reference to
_target
in front of method calls that were previously supplied by
theSprite
superclass. Because we're not extendingSprite
anymore, we no longer have anaddChild
oraddEventListener
method,
ormouseChildren
orbuttonMode
properties. We do, however, have aSprite
on which we can call those things, so we transfer the
responsibility there. - In lines 67 and 71, we need to remove the
override
keyword, as we're not extending anything, so there's nothing to override. - At the end of the class (lines 77-79), we add a whole new method. It's a getter function that returns a reference to the
composedSprite
. This step is crucial for the changes in DocumentClass. - In DocumentClass, the changes all involve directing
Sprite
-like behavior to thetarget
of the button, rather than to the button
itself. For example, on lines 2 and 3, rather than positioning the button object, we position the target of the button. Similarly,
we can't addbutton
as a child to the display list, but we can addbutton.target
.
Then end result should be identical. The difference is under the hood, in how that Button101
is set up.
Actually, the end result doesn't work without some extra modifications, as the click handler needs a little extra attention. Explaining the required changes is a bit beyond the scope of this tutorial, but you can find a fully-working example in the download package under "button_tutorial_composition"
Step 11: "Has-a" and "Is-a"
So, if both inheritance and composition are acceptable techniques, how do you know when to use which? Well, first, there's the archetypal answer, mentioned earlier, of "prefer composition over inheritance." If you feel you have the option of going with either, you might want to err on the side of composition, if only because you know some curmudgeony old tutorial writer who told you do so. In all seriousness, though, you can trust that people smarter than you (and smarter than me) have come up with that preference idiom, and there's no reason not to take their word for it.
But how about some more practical advice? A popular technique for evaluating the appropriateness of inheritance versus composition is the "Has-a"/"Is-a" test.
It goes like this. You take the names of your two classes (for the moment we'll pretend they are "ClassA" and "ClassB") and try them out in the following sentences:
ClassB is a ClassA.
ClassB has a ClassA.
And take note of the one that is a more appropriate relations.
If is a is more appropriate, then the relationship is one of inheritance. If has a is more appropriate, then it's a compositional relationship.
For example, let us consider the built-in TextField
class. Say the following out loud (loudly, and with conviction, especially if you're in a crowded room).
A
TextField
is aDisplayObject
.
ATextField
has aDisplayObject
.
To me, at least, the is a relationship makes more sense. The TextField
is displayable, so it is a DisplayObject
. Now TextField
's can also style their text, by way of the TextFormat
class. What about the following:
A
TextField
is aTextFormat
.
ATextField
has aTextFormat
.
At this point, we hopefully agree that it makes more sense for the TextField
to utilize (have) a TextFormat
. It's a useful component of a TextField
, but a TextField
isn't, itself, defined by being a TextFormat
.
Let's think about another example – the built-in Loader
class. Keep in mind that Loader
is a type of display object that can load external content. Let's try out the following statements:
A
Loader
is aDisplayObject
ALoader
has aDisplayObject
If you answered that the is a relationship makes more sense, you're right. The Loader
is, itself, displayable. If you answered that the has a relationship makes more sense, you're also right! There are no losers here at AS3 101 Camp! The Loader
has a property called content
which is the displayable content that was loaded.
One more example, then we'll drop this theoretical discussion. Let's use our previous Button101
in the test:
A
Button101
is aSprite
.
AButton101
has aSprite
.
Well, this is our debate from the previous step, isn't it? Which makes more sense? To me, either makes sense. The button could very well be a DisplayObject
of some type, or it could be something else and merely control the DisplayObject
in question. In this particular example, I think the answer depends a little on personal preference. For the record, my preference is to favor composition. You may draw your own conclusions, but remember that every time you choose inheritance, God kills a kitten.
(Yes, that was a joke. Personally, I don't believe in kittens.)
You can learn more by searching for "inheritance versus composition" or "prefer inheritance over composition." Many of the results I spotted were on the intermediate-to-advanced technical side, but if you crave a deeper discussion on this topic, Google can help you out.
Step 12: The Ultimate Mnemonic Device
If you need a more visual explanation of Inheritance and Composition, then consider the following.
Inheritance is like Robocop. He is a typical human extended with extra functionality. Or put another way, he is a robot that inherits core functionality from a human.

http://www.internationalhero.co.uk/r/robocop.jpg
Composition is like Michael Knight and KITT. Michael Knight is a typical human, and remains a typical human, only he owns (composes) a self-aware car.

http://manggoblog.files.wordpress.com/2008/10/hoff-knight-rider-mustang.jpg
Both are totally rad, but they've arrived at their respective awesomeness through different techniques.
But of the two, only the Hoff went of to star in Baywatch. Therefore, prefer composition over inheritance.

http://stupidcelebrities.net/wp-content/hasselhoff2.jpg
(I should point out that I did not come up with this analogy, but I am at a loss for locating the original source. If anyone knows, please let us know in the comments)
Step 13: Communicate
My point in bringing up composition has more to do with assembling a larger program out of objects, rather than getting into a discourse on the finer points of whether it's appropriate over inheritance in various situations. Those finer points are important, and you'll be running into them many times over as you grow as an Object-Oriented developer, but right now we need to see how we put our Object-Oriented Programming techniques to use in building a larger application.
The most common frustration and confusion I see in budding OOP developers is over the communication between various objects. Most people at this point in their education understand classes and objects, and how to write a given class. The next step is to start using multiple classes and objects to create something useful (like a website), but it seems that the natural confusion comes from learning that objects are meant to be encapsulated (remember that discussion? It was covered in my first OOP tutorial). Then how does one object talk to another object, so that a system can be created, out of which that useful program can be built?
Most first attempts I've witnessed involve either terrible, terrible violations of the encapsulation principle, of the scope of responsibility for a given class, or both. Or else the attempts simply do not work. I would like to take the time (and article space) to show you an example of a poorly-written system of objects.
Step 14: The Code In This Step is Questionable, Please Do Not Bother to Type It Out For Yourself. We Are Illustrating Less-Than-Ideal Technique.
I hope the heading made the point.
In this example, I present two classes, Button101BadIdea
, and DocumentClassBadIdea
. They are based on the previous Button101
examples, but you will see modifications specifically meant to illustrate the bad technique. I've stripped down both classes, though, in an attempt to bring a little focus to the relevant code.
Here is DocumentClassBadIdea
package { import flash.display. MovieClip; import flash.text.TextField; import flash.events.MouseEvent; import flash.net.*; public class DocumentClassBadIdea extends MovieClip { private var tf:TextField; private var button1:Button101BadIdea; private var button2:Button101BadIdea; public function DocumentClass() { tf = new TextField(); addChild(tf); tf.text = "Hello World"; button1 = new Button101BadIdea(tf, "You clicked button 1"); button1.x = 10; button1.y = 200; button1.label = "Button 1"; addChild(button1); button2 = new Button101BadIdea(tf, "You clicked button 2"); button2.x = 220; button2.y = 200; button2.label = "Button 2"; addChild(button2); } } }
And here is Button101BadIdea:
package { import flash.display.Shape; import flash.display.Sprite; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFormat; public class Button101BadIdea extends Sprite { private var bgd:Shape; private var labelField:TextField; private var _clickField:TextField; private var _text:String; public function Button101BadIdea(tf:TextField, text:String) { _clickField = tf; _text = text; bgd = new Shape(); bgd.graphics.beginFill(0x999999, 1); bgd.graphics.drawRect(0, 0, 200, 50); addChild(bgd); labelField = new TextField(); labelField.width = 200; labelField.height = 30; labelField.y = 15; var format:TextFormat = new TextFormat(); format.align = "center"; format.size = 14; format.font = "Verdana"; labelField.defaultTextFormat = format; addChild(labelField); addEventListener(MouseEvent.ROLL_OVER, onOver); addEventListener(MouseEvent.ROLL_OUT, onOut); addEventListener(MouseEvent.CLICK, onClick); mouseChildren = false; buttonMode = true; } public function set label(text:String):void { labelField.text = text; } public function get label():String { return labelField.text; } private function onOver(e:MouseEvent):void { bgd.alpha = 0.8; } private function onOut(e:MouseEvent):void { bgd.alpha = 1; } private function onClick(e:MouseEvent):void { _clickField.text = _text; } } }
The change is subtle. And if you try this out (look in the download package for the "bad_idea" project), you'll see that it works just fine. But here's what changed:
- In DocumentClassBadIdea, on lines 18 and 24, there are two arguments passed to the
Button101BadIdea
constructor, a reference to theTextField
on the stage, and a bit of text. - We also removed the lines that added a
CLICK
listener to the buttons, and the click-listening function. - In Button101BadIdea, on line 16, we're receiving two parameters (the
TextField
and theString
), and then storing them in instance properties on the next two lines. - At line 38, we add our own
CLICK
listener, to be handled internally. - On lines 55-57, we have the
CLICK
listener. This uses those new properties to apply theString
to the text value of theTextField
.
Now, if it works, why is it a bad idea? It's usually a pretty subjective thing to deem the correctness of code. On the one hand, if it works, then it can't be that bad. On the other hand, if the code is to be maintained, then the maintainability of the code is just as important being functional.
The code presented here is not maintainable. The biggest problem is right here (in Button101BadIdea
):
_clickField.text = _text;
We have just locked our button class into doing one thing and one thing only: putting text into a TextField
. Now, granted, it's been set up to use any text and any TextField
, but really this button has a very specific purpose. If you ever needed a button to do something else – anything else – you'd be stuck with having to make more button classes. Even if the appearance of the button was to be the same.
Step 15: I Can Has Better Solushun?
Of course, the better solution was the one already presented in the previous tutorials (see the "button_tutorial" project in the download package if you need a refresher). But what makes it better?
For starters, the Button101
class is far more reusable if it doesn't assume that a click should result on putting text into a TextField
. If needed, that end result can still be accomplished, but so can any other result. But we achieve the result not within the Button101
object; instead, that logic is in the DocumentClass
object. Or, more generally, that logic is in the object that "owns" the Button101
object.
Now, the object that owns the Button101
object can be pretty much any object that requires a button. The need for the button is then followed by the specific end result needed when the button is clicked. And that's the responsibility of the object that needs the button, not the button itself.
So what we have, in the original and better solution, is one object that composes another object. The object being composed is rather autonomous, but provides certain public properties and methods with which it can be customized to a degree. The object doing the composing is in control, and manipulates the composed object through its public properties and methods.
This relationship is the basic building block of larger applications, and the focus of the remainder of this tutorial.
Step 16: The Hierarchy of Composition
To enable two-way communication between classes, we need to start thinking in terms of hierarchies of composition. As a rule, if object A composes object B, then the reverse is decidedly not true. I must iterate that, naturally, there are exceptions to this rule. But this particular methodology works well in a vast majority of cases, and fits in nicely with the built-in event system in ActionScript 3.
Think of it this way. Your SWF starts with a document object. This object will, among other things, be composed of other objects, such as MovieClip, or objects representing MovieClips. There may even be an object that represents a collection of thumbnails or a grid of buttons. The object may be "owned" by the document object (by "owned," I mean the document object composes the thumb collection object). Now, the thumb collection object might compose more than one thumbnail object, which themselves are composed of a Loader
object and a TextField
object.
Now, does it make sense for the thumb collection object to compose the document object? Or for individual thumbnails to compose the thumb collection object? Is it even possible for a built-in class like Loader
to compose a custom class like Thumbnail
?
A hierarchy of composition starts to become apparent. You'll notice that not only is there a structure to how things are composed, but the objects themselves start at the most grand (the document object) and work their way to more and more specific and focused objects (such as a thumbnail object, or the basic building block of a Loader
object).
We can illustrate this hierarchy like so:

In the above diagram, objects are represented by the boxes with the class names in them. One object composes another if the line connecting ends with a diamond shape at the composing end. That is, in the above illustration, DocumentClass
composes Thumbnail
, and Thumbnail
in turn composes Loader
and TextField
.
Step 17: Embracing Events
ActionScript 3 is largely based upon the idea of dispatching and listening for events. I won't get into the basic of events here; you can read up on this ActiveTuts tutorial for more information if you need it. Events are a great way to enable communication (that's pretty much what they're about). Dispatching an event from one object allows pretty much any other object to act in response to it.
However, note the difference between executing public methods and handling an event. For instance, imagine object A and object B. Object A can execute any of object B's public methods, and that's one form of communication. If, on the other hand, Object A dispatches and Event for which Object B is listening, then that's another form of communication. But note the difference. The first is active: Object A directly manipulates Object B. The second is passive: Object A merely dispatched an event. Object B may not even be listening, and if it is, Object A has no control over what Object B may do as a result.
Step 18: Open Communication
So we have a hierarchy of composition. Typically the document object composes the thumb collection object. And because of that relationship, the document object gets to execute public methods on / directly manipulate / actively communicates with the thumb collection object. But the thumb collection doesn't know about the object that "owns" it. Ideally, it would be written in a reusable way so as to allow it to be used by any other object that wants to use a thumb collection. So we can't tie it to any particular document object.
But we want the document object to remove its preloader when the thumb collection is all loaded; how does the thumb collection communicate with the document object if it doesn't compose it? As you may have guessed, it does so through events. The thumb collection communicates indirectly / passively with the document object. In fact, it's a bit misleading to say it "communicates with the document object". It's really just saying, into the void, "My images have loaded, in case anyone cares." And thankfully there is someone listening in the void, and they do care, because they will now remove their preloader.
In other words, two-way communication is often achieved through a public method in one direction, and through event dispatches in the other.
Now, perhaps the most common form of two-way communication that's not the method just described is the ability for a public method to return a value. I would argue that this isn't so much two-way communication as it is a way to get information out of another object. That is, if Object A calls a method on Object B and gets a return value, then Object B may be told to do something, but Object A isn't really expected to do anything in response to getting the value back. It's splitting hairs, and while this maneuver is valuable, it's not our focus here.
We'll be picking this apart in a more hands-on approach momentarily, but keep this concept in mind as we build our image viewer.
Step 19: Building an Image Viewer
Let's roll up our sleeves. First things first, let's get an overview of our project. The final example was shown at the top of the article, so if you forget, trot back there for a moment. This step will be about defining our needs and breaking apart our objects.
First, let's write down the objectives
- Display a group of thumbnail images
- Clicking on a thumbnail image will display a larger version of the image
- This will also cause a "selected" state to appear on the thumbnail
- Clicking on a "next" or "previous" button will cause the appropriate image to show in the larger area, as well as selecting the appropriate thumbnail
- If the user is at the first image in the sequence, the "previous" button will be disabled and display itself as such. Similar action with the "next button" at the end of the sequence.
- Clicking on the larger image results in a link to the original image on Flickr
- Control which images are shown, and the associated data, through an XML document that is loaded at runtime
You may be able to tell, but I was drawing from this particular project in the last few steps with my examples of composition structures. Call it foreshadowing. So this following list of needed classes shouldn't be a total surprise:
Document Class
"Runs" the SWF. Pulls everything together into a working application
ThumbnailCollection
Manages a group of Thumbnail
objects. Responsible for creation and layout of individual thumbnails, based on incoming data. Also responsible for selecting thumbnails.
Thumbnail
An individual thumbnail. Acts as a button and loads and displays a image. Can also display a selected state, which can be enabled or disabled through public methods.
DetailImage
Loads a larger image. Can be clicked, and dispatches that event.
Pagination
Controls the two PaginationButtons
, re-dispatching their CLICK
events and managing when they are enabled and disabled.
PaginationButton
An individual button of the "next" or "previous" variety. Basically just a button, although they can be enabled and disabled, and update their appearance accordingly.
DataLoader
All of the other classes mentioned so far have been visual; this one is not. It's responsibility is to load the XML file and parse it, and retrieve relevant data when asked. This kind of class tends to feel more foreign to beginning Object-Oriented programmers, but it's no more or less useful than the visual side of things. It's inclusion here is partly to illustrate this point.
ImageData
A Value Object whose sole purpose is to carry bits of data pertaining to a single image (A "Value Object" is just an object with, typically, a bunch of properties and few if any methods. The point is to collect some bits of information – values – into a single object).
ImageChangeEvent
We'll need a custom event class in order to package up some useful information along with the event. The event will specifically be when an image should change due to some kind of interaction.
With that in mind, let's get started.
Step 20: Create the Project
We have a good idea of the classes involved, so we can successfully create the project. I'll detail the process using the computer's filesystem and OS, but if you're comfortable with a good project-oriented code editor, such as Flash Builder, FlashDevelop, or TextMate, feel free to create the project using those tools.
First, create a project folder. I'll call mine Image Viewer.
Inside of this folder, create two more folders, "src" and "bin". "src" will be where we put source files, like FLA files, AS files, and anything else that doesn't need to be deployed to run the final product. "bin" will be where the various files are that get deployed, such as SWF files, XML files, and image files that get loaded by the SWF.
Inside of the src folder, make two more folders, "flas" and "classes". We'll only have one FLA file, but we'll aim to organize our project by keeping FLA file separated from AS files.
Next, we'll create some folders in classes to be our package structure. Create this structure inside of classes:
[source="text"] classes/ data/ events/ ui/ detail/ pagination/ thumbs/
And lastly, create some more folder in bin to help keep that organized. We need folders for xml and images, specifically.
[source="text"] bin/ images/ thumbs/ detail/ data/
The final folder structure should look like this:

Step 21: Set Up the FLA
Now, create a Flash file (ActionScript 3.0, of course) and save it in the src\flas folder. I'm calling mine ImageViewer.fla. Alternately, you can start with the FLA file provided in the download pack, in image_viewier_start. It has graphics in it for the various elements so you don't need to burn time drawing buttons and the like.
Even with the starter FLA, we need to set up a few things.
Choose the menu item File > Publish Settings…. Make sure the "Formats" tab is selected, and choose a name for your SWF. More importantly, though, is to set the path. We want to go to the bin, so type in ../../bin/ImageViewer.swf
.
Next, click on the Flash tab, and then on the Settings… button next to ActionScript Version. In here, make sure the Source Path tab is selected, and click on the + button.
In the resulting entry, type ../classes/
.
Setting a relative path for publish paths and document source paths make the project more portable. Resist the temptation to click on the "target" button, as that results in an absolute path to the classes folder. This makes it much harder to move the project to a new location on your hard drive, or to hand to someone else for them to work on on a different machine.
Step 22: Create and Set the Document Class
Create a new text file and save it as ImageViewer.as in [project]/src/classes/ui
. Add the following to the file:
package ui { import flash.display.*; import flash.events.*; public class ImageViewer extends Sprite { public function ImageViewer() { trace("Ready.") } } }
Note that we need to fill in the package in the class, as we're technically in the ui
package. In case you missed it, remember that we don't put classes
in the package, as the classes
folder is our source path. We start there, but we don't include it as part of the package.
In your Flash file, make sure nothing is selected (press Command-Shift-A) and open the Property window. Where it says Class, enter:
[source="text"] ui.ImageViewer
Remember how the use of packages makes our full class name longer? We need the full name in the document class field, so we need to prepend the short name (ImageViewer
) with the package.
Now, if all went well, we'd be able to test the movie at this point and see a trace of "Ready." in the Output panel:

At this point, we have a project waiting to be worked on, and we have a document class hooked up to the FLA. We'll soon add more classes, but first, let's get our loadable assets ready.
Step 23: Add the Images
You can find a number of images ready to use in the download pack. You will find them in image_viewer/bin/images/. You can poke through the files if you want, and you'll probably find them predictably organized. There are thumbnail-sized images in one folder, and there are also larger versions of the same images in another folder. Put the small images in your project's bin/images/thumbs/
and the large images in bin/images/detail/
.
The images are all culled from Flickr, and are licensed under the Creative Commons. Attributions can be found in the next step, as well as in the final project.
If you wish to use your own images, size your thumbnails to 100x100, and your details to 400x300, and just put them in the appropriate folders.
Step 24: Create the XML File
We'll now catalog those images in an XML document. Flash will consume this file and parse the data it needs out of it. The structure of this file is important for following along with the rest of the tutorial. However, if you've used your own images, be sure to set the content of the XML appropriately. The format is not complex, hopefully you can make the substitutions on your own.
Create a new text file, and save it as images.xml in bin/data/
.
Our XML structure is basically just a single list of image items, an individual node of which looks like this:
<image name="flickr.jpg" attribution="Photo by Flickr, available under an attribution license" link="http://www.flickr.com" />
A single image has an image name, an attribution, and a source web page. Seems reasonable, unless you're wondering how we load an image from just an image name. To reduce typing, we'll stick a few attributes in the root node to define the paths to the thumbs and details folders, like so:
<images thumbPath="images/thumbs/" detailPath="images/detail/">
What this does is three-fold:
- We won't have to type out the full image path for each image, because we'll assume that all images are in the same directory.
- We don't have to specify individual paths for the thumbnail and the detail, and instead we rely on the two versions having
the same filename, just residing in different directories. - We can easily customize the location of the image folders, if you so desire.
Here is the full XML document, populated with data relevant to the images in the download package:
<images thumbPath="images/thumbs/" detailPath="images/detail/"> <image name="light-reading.jpg" attribution="Photo by MikeSchinkel, available under a Creative Commons Attribution license" link="http://www.flickr.com/photos/mikeschinkel/2680776058/" /> <image name="glocal-similarity-map.jpg" attribution="Photo by blprnt_van, available under a Creative Commons Attribution license" link="http://www.flickr.com/photos/blprnt/3189182327/" /> <image name="brimelows-bathroom.jpg" attribution="Photo by LeeBrimelow, available under a Creative Commons Attribution license" link="http://www.flickr.com/photos/brimelow/71209993/" /> <image name="blue-cubes.jpg" attribution="Photo by frankdouwes, available under a Creative Commons Attribution license" link="http://www.flickr.com/photos/frankdouwes/4015001782/" /> <image name="right-to-left.jpg" attribution="Photo by modern_carpentry, available under a Creative Commons Attribution license" link="http://www.flickr.com/photos/modern_carpentry/3705876429/" /> <image name="recursive3.jpg" attribution="Photo by flavouz, available under a Creative Commons Attribution license" link="http://www.flickr.com/photos/flavouz/503682800/" /> </images>
Step 25: Create the DataLoader Class
We get to start right in with our first non-document class, and it's a non-visual one, as well. Begin by creating a new text document, and saving it as DataLoader.as in src/classes/data/
.
Now, as this is not a visual class, there's definitely no need to extend Sprite
or MovieClip
. However, we do need to dispatch events, so it will be beneficial to extend EventDispatcher
. Here is our first pass at the code, which is moderately complex. The individual techniques used herein should not be new to you, so I won't belabor the loading and handling of XML. There is a brief walkthrough after the code, though.
package data { import flash.events.*; import flash.net.*; public class DataLoader extends EventDispatcher { private var _xml:XML; public function DataLoader(src:String) { var loader:URLLoader = new URLLoader(new URLRequest(src)); loader.addEventListener(Event.COMPLETE, onXmlLoad); loader.addEventListener(IOErrorEvent.IO_ERROR, onXmlError); } private function onXmlLoad(e:Event):void { _xml = new XML(e.target.data); dispatchEvent(new Event(Event.COMPLETE)); } private function onXmlError(e:IOErrorEvent):void { trace("There was a problem loading the XML file: " + e.text); } } }
In short, the DataLoader
object loads an XML file and stores the file as XML once it loads. To get it to load, we need to pass in a URL to the XML document to the constructor. The load starts, and the conversion of raw text into an XML object is handled by the COMPLETE
listener. There is also a simple error handler, which will spit out some useful information to the Output panel, but more than anything prevents an IOError from disrupting the program if something bad does happen.
Step 26: Integrate the DataLoader into the Application
With our DataLoader
class created (even if it's not finished), we can go ahead and use it. This is a nice way to work: start with a minimal program (such as a FLA and an almost-empty document class), add one object that's barely functional, make sure it works, add functionality, test, repeat. By not typing too much, we can minimize errors. Our frequent tests make it more obvious where errors were introduced, and by starting with zero functionality we progress in complexity in an organic way.
At any rate, go back to the ImageViewer class and have it create and use the DataLoader
:
package ui { import flash.display.*; import flash.events.*; import data.DataLoader; public class ImageViewer extends Sprite { private var _imageData:DataLoader; public function ImageViewer() { _imageData = new DataLoader("data/images.xml"); _imageData.addEventListener(Event.COMPLETE, onImageDataLoad); } private function onImageDataLoad(e:Event):void { trace("Data loaded."); } } }
Test it out, and you should see a single trace, indicating that the data has finished loading.

Step 27: Analyzing the DataLoader
Now, let's look at the choices that we made when designing things so far.
First, we chose to have the DataLoader contain its raw XML in a private property, and we have not written a public getter for it. The reasons for this is to encapsulate the raw data. Our next steps will involve writing some specialized methods to access the data, so we'll be continuing this discussion, but the main point is that we want an object to be responsible for maintaining the data. By restricting public access to the raw XML, we can control how the data is accessed. Not only that, but by writing methods to retrieve data, we can – at a later point – change the underlying mechanism of how the data is structured without the need to update multiple class files in response to that change. We would only need to update the DataLoader
class.
Second, we have the DataLoader
handle it's own error events. Now, realistically, if errors were to be an actual issue (for instance, if you were loading XML from another domain which may be inaccessible beyond your control), you'd want to handle things a little more elaborately. But for our purposes, we're pretty confident that the XML is going to load. In the case that it doesn't (say, we misspelled the file name), we would get the error event. If the error event isn't handled (that is, there is no listener for the URLLoader
's IOErrorEvent.IO_ERROR
event), then a runtime error is triggered. By hooking up something, we can at least prevent against disruption to the execution of the program. But the point is that we handle this event internally, so that, when we create the DataLoader
in the document class, we don't have to worry about that error so much. The DataLoader
is a bit more of black box, encapsulating the internal mechanism of the load.
Finally, we chose to dispatch a COMPLETE
event once the XML file loads and is converted into an XML object. This does two things for us. First, by dispatching our own event, as opposed to letting the document class listen directly to the URLLoader
's COMPLETE
event, we can ensure that the event is our COMPLETE
event. That is, it's not enough that the external file has loaded. We also want to make sure we have an XML object instantiated. So we dispatch the event when we're ready, after the XML is stored.
The more important thing that the COMPLETE
event accomplishes is the opportunity for two-way communication. It'll work like this: The document class creates a new DataLoader
, effectively communicating with it to load a certain file. When the file is loaded, the DataLoader
dispatches the COMPLETE
event, in a sense communicating with the document class so that it proceed with the rest of its logic.
But this is a subtle point, and I'll continue to point it out: while the document class communicates directly the DataLoader
, giving it a specific file to load, the DataLoader
merely dispatches an event. The document class may or may not be listening, as far as the DataLoader
is concerned. At this point, DataLoader
could be used in a completely different project, provided we wanted to utilize an XML file with the same structure.
We have here our first composition hierarchy. The document composes the DataLoader
and works with it directly, while the DataLoader
is more vague in its communication and simply dispatches events.

Don't forget, though, that even at this point there is another level of composition, as the DataLoader
composes URLLoader
and XML
objects.
Step 28: Add a Data Accessor Method
Let's give DataLoader
a way to expose the underlying data. However, as discussed in the previous step, we'll avoid exposing the XML object and instead create data accessor methods as we need them.
In DataLoader, add the following method:
public function getThumbnailPaths():Array { var paths:Array = []; var srcList:XMLList = _xml.image.@name; var thumbPath:String = _xml.@thumbPath; for each (var src:String in srcList) { paths.push(thumbPath + src); } return paths; }
If you're unfamiliar with standard parsing of XML with E4X, then you might want to brush up on that, as we'll be doing similar things in the coming steps.
The point I want to focus on is that we have created a method that will parse the XML for us, and simply return a list of paths to the thumbnail images. This separates the responsibilities of the objects: The document class is merely going to want that list of paths and not be mired down in the details of parsing the XML for that information. Conversely, the DataLoader
class is all about the details of the XML structure and qualified to parse it; so let's put the logic where it belongs.
Step 29: Use the Data Accessor Method
Back in ImageViewer, let's use that getThumbnailPaths
method we just created. Modify the onImageDataLoad
method to look like this:
private function onImageDataLoad(e:Event):void { var thumbPaths:Array = _imageData.getThumbnailPaths(); trace(thumbPaths.join("\n")); }
This doesn't add much functionality for now, but it does print out something more interesting than before. You should see a list of full paths to the thumbnail images:

This illustrates that our getThumbnailPaths
method is working, and also that we're using the composed object by directly calling methods on it.
Step 30: Create a Thumbnail Class
Our next step would be to pass this thumbPaths
data to the ThumbnailCollection
class so that it can then do its job of managing multiple, individual Thumbnail
objects. Obviously, to get it to work, we kind of need to develop the Thumbnail
class at the same time. We'll start with the Thumbnail
class and then "connect" the document class to the Thumbnail
objects through the ThumbnailCollection
object.
Keep in mind that it doesn't really work that way; the document class will have no knowledge of the individual Thumbnails
because they'll be encapsulated by the ThumbnailCollection
. And vice versa, because we'll be dispatching events from Thumbnail
to ThumbnailCollection
, which will then re-dispatch events as necessary back to the document class.
With that in mind, let's build a Thumbnail
class. Create a new text file and save it as Thumbnail.as in classes/ui/thumbs/
. Add the following code to the class:
package ui.thumbs { import flash.display.*; import flash.events.*; import flash.geom.*; import flash.net.*; public class Thumbnail extends EventDispatcher { private var _container:ThumbnailGraphic; private var _loader:Loader; private var _highlight:Sprite; public function Thumbnail() { _container = new ThumbnailGraphic(); _container.addEventListener(MouseEvent.ROLL_OVER, onOver); _container.addEventListener(MouseEvent.ROLL_OUT, onOut); _container.mouseChildren = false; _container.buttonMode = true; _loader = new Loader(); _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError); _highlight = _container.getChildByName("highlight_mc") as Sprite; _highlight.alpha = 0; var highlightDepth = _container.getChildIndex(_highlight); _container.addChildAt(_loader, highlightDepth); } public function load(url:String):void { _loader.load(new URLRequest(url)); } public function select():void { _container.removeEventListener(MouseEvent.ROLL_OVER, onOver); _container.removeEventListener(MouseEvent.ROLL_OUT, onOut); _container.mouseEnabled = false; _highlight.alpha = .8; } public function deselect():void { _container.addEventListener(MouseEvent.ROLL_OVER, onOver); _container.addEventListener(MouseEvent.ROLL_OUT, onOut); _container.mouseEnabled = true; _highlight.alpha = 0; } private function onComplete(e:Event):void { var x:Number = (_loader.width - 100) / 2; var y:Number = (_loader.height - 100) / 2; _loader.scrollRect = new Rectangle(x, y, 100, 100); dispatchEvent(new Event(Event.COMPLETE)); } private function onError(e:IOErrorEvent):void { trace("Problem loading this thumbnail image: " + _loader.contentLoaderInfo.url); } private function onOver(e:MouseEvent):void { _highlight.alpha = .4; } private function onOut(e:MouseEvent):void { _highlight.alpha = 0; } public function get container():ThumbnailGraphic { return _container; } } }
All in all, a moderately complex class, but nothing terribly advanced. A few points of clarification:
The ThumbnailGraphic
class is, if you haven't spotted it yet, a class provided by the FLA as a symbol that has been exported for ActionScript. So, there is no class file for it (although, conceivably, we could have actually made the exported class the Thumbnail
class, but our focus is on composition, so rather than extending a DisplayObject
we're composing one).
We use a Loader
object to handle the loading of and display of an image. We're handling the complete and error events. The error event is a basic trace. The complete handler is a little more involved. However, it's all about merely displaying the loaded image within an 100 x 100 square. If the image is larger than that, then the resulting calculations on the scrollRect
will crop the image to the center. If it's smaller, then the image will get centered. This isn't a substitute for not cropping your thumbnail images properly, but it does help prevent unsightly bleeds in the event of user error when providing images.
The mouse hover is straightforward enough, using the highlight frame found within the Flash symbol. Feel free to church it up here, and implement a tween using TweenMax or Tweener or the animation package of your choosing. Animations always add a nice feel to most pieces, but I'll remove the tangent for my purposes.
A CLICK
event has been hooked up, but we'll leave the logic of the listener for the time being.
The select
and deselect
methods will get used a bit further down the line. But we're setting them up here. The idea is that a selected Thumbnail
will have the highlight on it (at bit more "solid" of an alpha setting), but also be non-interactive at that point. This will prevent clicks on the same Thumbnail
in a row, and keep the highlight from acting unexpectedly. deselect
just reverses everything that makes a Thumbnail
objected selected.
There is a getter for the container
property, which is an instance of the ThumbnailGraphic
symbol. This provides access to the display object that will need to be positioned and added to the stage. Being a public function, we'll be seeing this again in the next step as we write the ThumbnailCollection
class.
We'll be coming back to this class later, but for now let's turn our attention to the ThumbnailCollection
class.
Step 31: Write the ThumbnailCollection Class
The ThumbnailCollection
class is, again, responsible for managing multiple Thumbnail
objects. Make another text file, and save it as ThumbnailCollection.as in /classes/ui/thumbs
. Here's our initial code to put in:
package ui.thumbs { import flash.display.*; import flash.events.*; public class ThumbnailCollection extends EventDispatcher { private var _container:Sprite; private var _thumbnails:Array; public function ThumbnailCollection():void { _container = new Sprite(); _thumbnails = new Array(); } public function addThumbAt(url:String, index:uint):void { var thumb:Thumbnail = new Thumbnail(); thumb.load(url); _container.addChild(thumb.container); thumb.container.x = 100 * index; _thumbnails[index] = thumb; } public function get container():Sprite { return _container; } } }
You might think of this class as a glorified Array. Its main function is to house a collection of Thumbnail
objects (hence the name of the class), and it does so in an Array. But it also protects that Array. If we use a ThumbnailCollection
object (and we will, in the next step), then we need to trust that it will do its job at managing the collection, and thus we don't have direct access to that Array. We can add items to the Array indirectly, through addThumbAt
, but that's about it. Not to mention that that method also does a bunch of other things to create a Thumbnail
.
Step 32: Use the ThumbnailCollection Object
Let's put that all together and see what we can do. Back in ImageViewer, we'll create a ThumbnailCollection
instance and populate it using the list of thumb paths from DataLoader
.
package ui { import flash.display.*; import flash.events.*; import data.DataLoader; import ui.thumbs.ThumbnailCollection; public class ImageViewer extends Sprite { private var _imageData:DataLoader; private var _thumbnails:ThumbnailCollection; public function ImageViewer() { _imageData = new DataLoader("data/images.xml"); _imageData.addEventListener(Event.COMPLETE, onImageDataLoad); _thumbnails = new ThumbnailCollection(); addChild(_thumbnails.container); _thumbnails.container.y = 300; } private function onImageDataLoad(e:Event):void { var thumbPaths:Array = _imageData.getThumbnailPaths(); for (var i:uint = 0; i < thumbPaths.length; i++) { _thumbnails.addThumbAt(thumbPaths[i], i); } } } }
At this point, our composition hierarchy looks like this (a line with a diamond on it means that the class on the diamond side is composing the class at the other end of the line):

Note that ImageViewer
is composing the ThumbnailCollection
, and therefore calls methods on in directly. But also notice that the DataLoader
and ThumbnailCollection
are completely independent of each other (successful encapsulation), yet can cooperate through the mediation of the ImageViewer
. Again, the higher-level objects tend to compose the more focused objects, and therefore have knowledge of them. The other side of the coin, though, is that the more focused objects tend to not know anything about the objects composing them, and instead provide the service of dispatching events.
OK, at this point, we should be able to make it work! Test the movie, and instead of traces, we should see some images show up along the bottom of the SWF. You should also be able to interact with the thumbnails, getting that highlight to show up on mouse over.

Step 33: A Custom Event
We're going to handle a click on a Thumbnail
object. The individual CLICK
needs to be detected on the Thumbnail
itself, then dispatched out to the ThumbnailCollection
object. From there, it determines the index of the Thumbnail
that was clicked and redispatches a custom event. ImageViewer
will be listening for this event, and use it to determine what happens next.
Let's start with the custom event class. It's going to be a standard Event
, only it has a spot for an value
property. This will be determined by ThumbnailCollection
and used by ImageViewer
. Our custom event will also define some custom event types.
Create another new text file and save it as ImageChangeEvent in classes/events/
. This will be a typical Event
subclass, which we haven't talked about specifically up to now. Full discussion after the code:
package events { import flash.events.*; public class ImageChangeEvent extends Event { public static const INDEX_CHANGE:String = "indexChange"; private var _value:int; public function ImageChangeEvent(type:String, value:int, bubbles:Boolean=true, cancelable:Boolean=false) { _value = value; super(type, bubbles, cancelable); } override public function clone():Event { return new ImageChangeEvent(type, value, bubbles, cancelable); } override public function toString():String { return formatToString("ImageChangeEvent", "type", "value"); } public function get value():int { return _value; } } }
First, this class is in the events
package. It's not required, but it's common to place your custom events in an events
package. This keeps them grouped together, as well as conceptually separating them from the other classes. A custom event, just like a regular event, should be extremely lightweight and not dependent on too much to get its job done. Putting events in their own package reinforces the notion that this is an event that can be dispatched from, technically, any object in the project, not just, say, the objects in the thumbs
package (this is foreshadowing, by the way. We'll be dispatching this event again, mark my words).
Next we need to import the built-in events package, specifically Event
, because we extend Event
. We're using inheritance here because we pretty much only have one choice. In order to create a custom event, we have to subclass Event
. Just the way it goes.
The first few lines of the class itself are properties. There is a public static constant, which defines a String
that is an event type. Our event will actually only define this one type for now, but it's not uncommon to list several of these types here. This gives us an event type like ImageChangeEvent.INDEX_CHANGE
, not unlike something like Event.COMPLETE
or MouseEvent.CLICK
. The other property is just a private instance variable, in which we'll store the index value of the image to which we're changing.
The constructor is fairly typical for an Event
subclass. The type
, bubbles
, and cancelable
parameters are simply transferred to the super constructor. We just need to make sure we do that. The bit of custom work, and really the whole point of creating a custom event, is the value
parameter. That's our own addition, so we store that in our instance property and pass the rest of the parameters to the super class.
The next two methods are overrides of methods in the super class. It's not strictly necessary to do this, but it's not a bad idea. The clone
method provides an easy way to create an identical copy of a given event. This is generally as simple as creating a new event object using the values stored within the original event, and returning it. The toString
method lets us provide informative output if the event ever gets traced. We take advantage of the super class's formatToString
method, and just pass in the name of the class and any properties we want to include in the output. We'll see this in action in just a little bit.
Lastly, there is a public getter for the _value
property. This provides a read-only property. But this property is going to be extremely useful in our program.
Nothing to test here, so let's move on to actually using this event object.
Step 34: Clicking an Image
So far, we've done pretty well just writing up whole classes and getting them to work together. Our next task involves smaller changes across multiple classes, so pay attention.
We'll start in the Thumbnail
, where we'll first listen for a normal CLICK
event on the ThumbnailGraphic
, and then simply re-dispatch that event.
First, add the CLICK
event listener:
public function Thumbnail() { _container = new ThumbnailGraphic(); _container.addEventListener(MouseEvent.ROLL_OVER, onOver); _container.addEventListener(MouseEvent.ROLL_OUT, onOut); _container.addEventListener(MouseEvent.CLICK, onClick); _container.mouseChildren = false; // ...
Also take it into account in the select
and deselect
methods:
public function select():void { _container.removeEventListener(MouseEvent.ROLL_OVER, onOver); _container.removeEventListener(MouseEvent.ROLL_OUT, onOut); _container.removeEventListener(MouseEvent.CLICK, onClick); _container.mouseEnabled = false; _highlight.alpha = .8; } public function deselect():void { _container.addEventListener(MouseEvent.ROLL_OVER, onOver); _container.addEventListener(MouseEvent.ROLL_OUT, onOut); _container.addEventListener(MouseEvent.CLICK, onClick); _container.mouseEnabled = true; _highlight.alpha = 0; }
And then write the listener function:
private function onClick(e:MouseEvent):void { dispatchEvent(e); }
Turning to ThumbnailCollection
, we need to make sure we listen for CLICK
events on all Thumbnails
, and then dispatch an ImageChangeEvent
as a result, after looking up the index of the event-causing Thumbnail
.
First, import the ImageChangeEvent
:
import events.ImageChangeEvent;
Then, in the addThumbAt
method, make sure to listen for CLICK
events:
public function addThumbAt(url:String, index:uint):void { var thumb:Thumbnail = new Thumbnail(); thumb.load(url); _container.addChild(thumb); thumb.x = 100 * index; thumb.addEventListener(MouseEvent.CLICK, onThumbClick); _thumbnails[index] = thumb; }
And lastly write the event handler. This bit might take a little explanation:
private function onThumbClick(e:MouseEvent):void { var thumb:Thumbnail = e.target as Thumbnail; var index:int = _thumbnails.indexOf(thumb); dispatchEvent(new ImageChangeEvent(ImageChangeEvent.INDEX_CHANGE, index)); }
The code is actually rather simple, but here's the gist: First, grab the event target and cast it as a Thumbnail
. This gives us the Thumbnail
object that was clicked. Then, look up the index in the _thumbnails
array of that object. The indexOf
method does exactly that: it returns the array index of the the object passed into the method. Finally, using that index, we create a new ImageChangeEvent
which we then dispatch.
One last thing to do, and that's to listen for and handle ImageChangeEvent
s in ImageViewer
. In that class, import the ImageChangeEvent
:
import events.ImageChangeEvent;
Then we need an instance property to hold onto the image index we've chosen (the reasons for which will be clearer in the near future):
private var _currentImageIndex:int;
And then update the code to listen for the event in the constructor:
public function ImageViewer() { _imageData = new DataLoader("data/images.xml"); _imageData.addEventListener(Event.COMPLETE, onImageDataLoad); _thumbnails = new ThumbnailCollection(); addChild(_thumbnails.container); _thumbnails.container.y = 300; _thumbnails.addEventListener(ImageChangeEvent.INDEX_CHANGE, onImageChange); }
Finally, we need to write that event-listening method. We'll get to more interesting code in the next step, but for now let's write out the method and trace the index:
private function onImageChange(e:ImageChangeEvent):void { _currentImageIndex = e.value; trace("image changed. index: " + _currentImageIndex); }
That was a lot of editing, but actual code added was just a handful of lines. Just keep telling yourself that this is all in the name of the hierarchy of composition.
And we should be able to test it now! Go ahead and run the movie, and click on the thumbnails. You'll see the index of the thumbnail you clicked on show up in the Output panel.

For your reference, here are the complete listings of the code so far for the three affected classes:
ImageViewer
package ui { import flash.display.*; import flash.events.*; import data.DataLoader; import events.ImageChangeEvent; import ui.thumbs.ThumbnailCollection; public class ImageViewer extends Sprite { private var _imageData:DataLoader; private var _thumbnails:ThumbnailCollection; private var _currentImageIndex:int; public function ImageViewer() { _imageData = new DataLoader("data/images.xml"); _imageData.addEventListener(Event.COMPLETE, onImageDataLoad); _thumbnails = new ThumbnailCollection(); addChild(_thumbnails.container); _thumbnails.container.y = 300; _thumbnails.addEventListener(ImageChangeEvent.INDEX_CHANGE, onImageChange); } private function onImageDataLoad(e:Event):void { var thumbPaths:Array = _imageData.getThumbnailPaths(); for (var i:uint = 0; i < thumbPaths.length; i++) { _thumbnails.addThumbAt(thumbPaths[i], i); } } private function onImageChange(e:ImageChangeEvent):void { _currentImageIndex = e.value; trace("image changed. index: " + _currentImageIndex); } } }
ThumbnailCollection
package ui.thumbs { import flash.display.*; import flash.events.*; import events.ImageChangeEvent; public class ThumbnailCollection extends EventDispatcher { private var _container:Sprite; private var _thumbnails:Array; public function ThumbnailCollection():void { _container = new Sprite(); _thumbnails = new Array(); } public function addThumbAt(url:String, index:uint):void { var thumb:Thumbnail = new Thumbnail(); thumb.load(url); _container.addChild(thumb.container); thumb.container.x = 100 * index; thumb.addEventListener(MouseEvent.CLICK, onThumbClick); _thumbnails[index] = thumb; } public function get container():Sprite { return _container; } private function onThumbClick(e:MouseEvent):void { var thumb:Thumbnail = e.target as Thumbnail; var index:int = _thumbnails.indexOf(thumb); dispatchEvent(new ImageChangeEvent(ImageChangeEvent.INDEX_CHANGE, index)); } } }
Thumbnail
package ui.thumbs { import flash.display.*; import flash.events.*; import flash.geom.*; import flash.net.*; public class Thumbnail extends EventDispatcher { private var _container:ThumbnailGraphic; private var _loader:Loader; private var _highlight:Sprite; public function Thumbnail() { _container = new ThumbnailGraphic(); _container.addEventListener(MouseEvent.ROLL_OVER, onOver); _container.addEventListener(MouseEvent.ROLL_OUT, onOut); _container.addEventListener(MouseEvent.CLICK, onClick); _container.mouseChildren = false; _container.buttonMode = true; _loader = new Loader(); _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError); _highlight = _container.getChildByName("highlight_mc") as Sprite; _highlight.alpha = 0; var highlightDepth = _container.getChildIndex(_highlight); _container.addChildAt(_loader, highlightDepth); } public function load(url:String):void { _loader.load(new URLRequest(url)); } public function select():void { _container.removeEventListener(MouseEvent.ROLL_OVER, onOver); _container.removeEventListener(MouseEvent.ROLL_OUT, onOut); _container.removeEventListener(MouseEvent.CLICK, onClick); _container.mouseEnabled = false; _highlight.alpha = .8; } public function deselect():void { _container.addEventListener(MouseEvent.ROLL_OVER, onOver); _container.addEventListener(MouseEvent.ROLL_OUT, onOut); _container.addEventListener(MouseEvent.CLICK, onClick); _container.mouseEnabled = true; _highlight.alpha = 0; } private function onComplete(e:Event):void { var x:Number = (_loader.width - 100) / 2; var y:Number = (_loader.height - 100) / 2; _loader.scrollRect = new Rectangle(x, y, 100, 100); dispatchEvent(new Event(Event.COMPLETE)); } private function onError(e:IOErrorEvent):void { trace("Problem loading this thumbnail image: " + _loader.contentLoaderInfo.url); } private function onOver(e:MouseEvent):void { _highlight.alpha = .4; } private function onOut(e:MouseEvent):void { _highlight.alpha = 0; } private function onClick(e:MouseEvent):void { dispatchEvent(e); } public function get container():ThumbnailGraphic { return _container; } } }
Step 35: Encapsulate Image Data in a Value Object
To get image data from the DataLoader
, it would be convenient to bundle up all of the necessary information (remember, an
node contains name
, attribution
, and link
attributes) in a single object. We could use a regular old object, like this:
{name:"name", attribution:"Attributed to John", link:"www.flickr.com"}
But this is a tutorial about Object-Oriented Programming, so we're going to add another class. The responsibility will be solely to contain the properties associated with an image. This will be a very simple class, but it will provide us with a strongly-typed value we can use to pass around this information.
Create a new text file, and save it as ImageData.as
in classes/data/
. Add this code:
package data { public class ImageData { public var source:String; public var attribution:String; public var link:String; public function ImageData(source:String, attribution:String, link:String) { this.source = source; this.attribution = attribution; this.link = link; } } }
The class exists to provide three properties. This means we can transfer the data from the
node in the XML to an ImageData
object. While we were writing the class, we also added a convenience to the constructor: pass in three arguments, and get those properties set for you. So we can create an ImageData
object with one line, like this:
new ImageData("path/to/image.jpg", "Attributed to John", "www.flickr.com");
Now, even though this is a very simple class, why go to the trouble and not just use the regular Object? The advantage is datatyping and strict compile time error checking. If you have a variable typed as an Object, there isn't any way for the compiler to know that source
is or isn't a valid property of the variable, or that it is or isn't a String
. Our ImageData
class, however, has only three valid properties, each with its own proper datatype. The compiler will catch unintentional misuses of the object by producing errors.
Not only that, but philosophically, we can think in terms of objects throughout the application. Even something as simple as "data about an image" can represented as an object, and thus we can create a class for just this purpose.
Step 36: Querying the Data for Details
Well, we have an index. We've managed to get the index of the Thumbnail
that was clicked, but now what? We need to go back to the data to get more information relating to the index. Basically, we want all information – as an ImageData
object – that is associated with the index that we have.
Open up DataLoader
and add this method:
public function getImageByIndex(index:int):ImageData { var imageNode:XML = _xml.image[index]; if (imageNode) { return new ImageData(_xml.@detailPath + imageNode.@name, imageNode.@attribution, imageNode.@link); } else { return null; } }
Remember, a single
node looks like this:
<image name="flickr.jpg" attribution="Photo by Flickr, available under an attribution license" link="http://www.flickr.com" />
This method is actually rather simple. We take the index that was passed in and use it to find the matching node in the XML data. Then we take that node and get the attributes out of it. With those values, we create an ImageData
object and return it. However, note the check to see if the
node exists; if we've passed in an out-of-bounds index, we won't have valid data in the XML, so we can't return a valid ImageData
object. This is a simple check that can prevent errors down the line, in the rare event that we'll need it.
Put this method to the test by using it in ImageViewer
. Start by importing the ImageData
class:
import data.DataLoader; import data.ImageData; import events.ImageChangeEvent; import ui.thumbs.ThumbnailCollection;
In the onImageChange
method, we can remove the trace and query the DataLoader
for the relevant information.
private function onImageChange(e:ImageChangeEvent):void { _currentImageIndex = e.value; updateImage(_currentImageIndex); } private function updateImage(index:uint):void { var img:ImageData = _imageData.getImageByIndex(index); trace("image name: " + img.source); }
We'll also pull the logic for doing something with this index into a new method. At the moment, it might seem like overkill to do so, but we'll need to do it later, so I'm using my crystal ball and saving us a little overhead by making the method now.
Test the movie, and you should see a relevant trace when clicking on thumbnails.

Step 37: Detail Image
Now let's turn our attention to the detail image area. As a reminder, the goal here is to load an image, display an attribution, and navigate to a URL when the image is clicked. Simple enough, but let's make sure we approach it with an object-orientation.
Make a new text file and save it as DetailImage.as
in classes/ui/detail/
. Here's are code:
package ui.detail { import flash.display.*; import flash.events.*; import flash.net.*; import flash.text.*; public class DetailImage { private var _container:Sprite; private var _attributionField:TextField; private var _link:String; private var _loader:Loader; public function DetailImage(container:Sprite) { _container = container; _attributionField = _container.getChildByName("attributionField") as TextField; _loader = new Loader(); _container.addChildAt(_loader, _container.getChildIndex(_attributionField)); _container.mouseChildren = false; _container.buttonMode = true; _container.addEventListener(MouseEvent.CLICK, onClick); } private function onClick(e:MouseEvent):void { navigateToURL(new URLRequest(_link), "_blank"); } public function setImage(src:String, attribution:String, link:String):void { _loader.load(new URLRequest(src)); _attributionField.text = attribution; _link = link; } } }
Now, most of this depends on the fact that we'll be passing in a specific movie clip to the constructor. The code controlling this bit of the UI is operating on the assumption that it has a certain Sprite
associated with it, in the property called _container
. Namely, it expects there to be a TextField
somewhere inside the Sprite
called "attributionField".
Here's a quick walkthrough of the class: after the imports and boilerplate, we have four properties. The _container
is the aforementioned Sprite
that is passed in. This will be a Sprite
on the stage of the FLA, and we'll get to this again in the next step. The _attributionField
is a reference to the TextField
inside of the _container
. It will just be a convenience to be able to refer to it through a variable rather than always through getChildByName
. _loader
is a Loader
object we create to load the images, and _link
is just the destination of the click.
The constructor does most of the heavy lifting. It sets up most of the properties, and sets up the entire Sprite
as a button. To round the class out, we have the onClick
method that is the CLICK
handler, and simply navigates to the URL currently stored in _link
. And then there's setImage
; this takes information we need to display the DetailImage
as a unique image and does the appropriate things with the information: it loads the image source in the Loader
, it sets the text to the attribution, and it stores the link for later use in the onClick
method.
Again, this isn't a very complex class. We can try to keep things simpler by creating lots of smaller, focused objects.
There's nothing to test yet; save that for the next step when we set up the DetailImage
.
Step 38: Display a Detail Image
In order to display a detail image, we'll want to first create a DetailImage
object in our document class. So, in ImageViewer
, add the highlighted code:
package ui { import flash.display.*; import flash.events.*; import data.DataLoader; import data.ImageData; import events.ImageChangeEvent; import ui.detail.DetailImage; import ui.thumbs.ThumbnailCollection; public class ImageViewer extends Sprite { private var _imageData:DataLoader; private var _thumbnails:ThumbnailCollection; private var _detailImage:DetailImage; private var _currentImageIndex:int; public function ImageViewer() { _imageData = new DataLoader("data/images.xml"); _imageData.addEventListener(Event.COMPLETE, onImageDataLoad); _thumbnails = new ThumbnailCollection(); addChild(_thumbnails.container); _thumbnails.container.y = 300; _thumbnails.addEventListener(ImageChangeEvent.INDEX_CHANGE, onImageChange); _detailImage = new DetailImage(detailImage_mc); } private function onImageDataLoad(e:Event):void { var thumbPaths:Array = _imageData.getThumbnailPaths(); for (var i:uint = 0; i < thumbPaths.length; i++) { _thumbnails.addThumbAt(thumbPaths[i], i); } } private function onImageChange(e:ImageChangeEvent):void { var index:int = e.value; updateImage(index); } private function updateImage(index:uint):void { var img:ImageData = _imageData.getImageByIndex(index); _detailImage.setImage(img.source, img.attribution, img.link); } } }
The big thing we do is call the setImage
method on our DetailImage
instance, and we should get some payoff. Go ahead and test the project, and see if clicking a Thumbnail
results in something happening in DetailImage
.

Notice that the four lines added are all about the DetailImage
. We expanded the functionality of our program by adding one object, and by making changes to one other class that uses that object. All of the other classes (Thumbnail
, ImageChangeEvent
, etc) have not needed any updates to bring this significant change about. Notice also how our composition hierarchy has grown. Not only do we have our lineage of composition with the thumbnails, but now we have a whole separate branch involving the DetailImage
.

And with that in mind, we now add the final piece to this puzzle, the pagination system.
Step 39: Pagination Buttons
Our pagination UI will consist of the two buttons, the left and right arrow buttons in the FLA. Create yet another new text file, saved as PaginationButton
in classes/ui/pagination/
. Here is the code:
package ui.pagination { import flash.display.*; import flash.events.*; public class PaginationButton extends EventDispatcher { private var _clip:Sprite; public function PaginationButton(clip:Sprite) { _clip = clip; _clip.addEventListener(MouseEvent.CLICK, onClick); _clip.mouseChildren = false; _clip.buttonMode = true; } private function onClick(e:MouseEvent):void { dispatchEvent(e); } public function enable():void { _clip.alpha = 1; _clip.mouseEnabled = true; } public function disable():void { _clip.alpha = 0.5; _clip.mouseEnabled = false; } } }
The main point of this class, as a representation of a button, is to hold a Sprite
(composition) and provide button-ish functionality to that Sprite
. Most of this should be fairly obvious, but we also add the ability to enable
and disable
the button. This gives the object an easy method for, as you can guess, enabling or disabling the button. The functionality is encapsulated, and is the responsibility of the PaginationButton
object. Notice that we are not only managing the interactivity (with _clip.mouseEnabled = true/false;
) but also affecting the appearance, to make a disabled button look disabled (with _clip.alpha = 0.5;
). Again, this is encapsulation; a simple call to disable
disables the button, however the PaginationButton
object actually accomplishes that task.
This probably seems like a fairly complete class, but let's add an extra layer.
Step 40: Adding Pagination
We have a class for the individual buttons, but we'd like to group those together into a single entity. We'll create a Pagination
class, which will compose the two PaginationButton
objects.
Create the last new text file of this project (yay!), call it Pagination.as
, and save it in classes/ui/pagination/
. Add this code:
package ui.pagination { import flash.display.*; import flash.events.*; import events.ImageChangeEvent; public class Pagination extends EventDispatcher { private var _prevButton:PaginationButton; private var _nextButton:PaginationButton; public function Pagination(prevClip:Sprite, nextClip:Sprite) { _prevButton = new PaginationButton(prevClip); _nextButton = new PaginationButton(nextClip); _prevButton.addEventListener(MouseEvent.CLICK, onPrevClick); _nextButton.addEventListener(MouseEvent.CLICK, onNextClick); } private function onPrevClick(e:MouseEvent):void { dispatchEvent(new ImageChangeEvent(ImageChangeEvent.INDEX_OFFSET, -1)); } private function onNextClick(e:MouseEvent):void { dispatchEvent(new ImageChangeEvent(ImageChangeEvent.INDEX_OFFSET, 1)); } public function updateButtons(enablePrev:Boolean, enableNext:Boolean):void { if (enablePrev) { _prevButton.enable(); } else { _prevButton.disable(); } if (enableNext) { _nextButton.enable(); } else { _nextButton.disable(); } } } }
This really isn't doing much, other than acting as a place to compose two PaginationButton
objects. We might call this object a manager, pretty much existing to aggregate and control a collection of other objects. It does, however, let us treat that collection of objects as a single unit, while the PaginationButton
class is a single unit already.
However, a little wrench thrown into our system is how to handle what happens when a button is clicked. Here's what's going to happen: first, we listen for the CLICK
events on our two buttons. Then we dispatch a new event in response. The event will an ImageChangeEvent, but the type will not be INDEX_CHANGE
but instead a new type. We'll then use the value
property to indicate in which direction we need to move from the current image index. We'll need to modify the ImageChangeEvent
class, and that happens in the next step.
You'll also notice the updateButtons
method. This will give us a way to update the state of the collection of buttons. You can think of this as, again, encapsulation of a more complex task into a less complex command.
Before we get to the ImageChangeEvent
, you might be wondering why go to the trouble of creating this class at all. Or possibly why pass in the two Sprite
s and not create the two PaginationButton
s and pass those in. Well, to be honest, the reason is that this is how I chose to do it.
In my opinion (and please keep in mind that a lot of this is just my opinion, laid out in tutorial form), the idea of the pagination system deserves it's own object. When I see individual elements, like the individual pagination buttons, I see those as objects. And if those individual objects can be grouped, even if just conceptually, then that group is also represented by an object. It's not unlike the Thumbnail
and ThumbnailCollection
classes; the collection is represented by a group.
It's not the only way to accomplish our goal, but it's a way that I've found tends to work well. If you want to try different tactics, by all means, do so. One of the best ways to learn Object-Oriented Programming is to try out ideas, and then see what did and didn't work. Of course, for this tutorial, you'll be best served by following along closely.
Step 41: Update the ImageChangeEvent
Let's make the updates to the the ImageChangeEvent
class that we need. This really amounts to just adding the new INDEX_CHANGE
type (highlighted below).
package events { import flash.events.*; public class ImageChangeEvent extends Event { public static const INDEX_CHANGE:String = "indexChange"; public static const INDEX_OFFSET:String = "indexOffset"; private var _value:int; public function ImageChangeEvent(type:String, value:int, bubbles:Boolean=true, cancelable:Boolean=false) { _value = value; super(type, bubbles, cancelable); } override public function clone():Event { return new ImageChangeEvent(type, value, bubbles, cancelable); } override public function toString():String { return formatToString("ImageChangeEvent", "type", "value"); } public function get value():int { return _value; } } }
That's more or less it. If you haven't noticed, we'll use that value
property as something more generic; it's expected that, if it's an INDEX_CHANGE
event, then value
will contain the destination index value. However, if it's an INDEX_OFFSET
event, then value
will represent the offset value (that is, how far off from the current index value we're supposed to travel).
Here's our composition hierarchy as of now:

One more step, and we can see the fruits of this labor.
Step 42: Create the Pagination System
We have the classes, we have the Movie Clips on the stage, we just need to turn the ignition. In ImageViewer
, we need to create create a Pagination
instance and listen for – and handle – its events. Add the following code:
package ui { import flash.display.*; import flash.events.*; import data.DataLoader; import data.ImageData; import events.ImageChangeEvent; import ui.detail.DetailImage; import ui.pagination.Pagination; import ui.thumbs.ThumbnailCollection; public class ImageViewer extends Sprite { private var _imageData:DataLoader; private var _thumbnails:ThumbnailCollection; private var _detailImage:DetailImage; private var _pagination:Pagination; private var _currentImageIndex:int; public function ImageViewer() { _imageData = new DataLoader("data/images.xml"); _imageData.addEventListener(Event.COMPLETE, onImageDataLoad); _thumbnails = new ThumbnailCollection(); addChild(_thumbnails.container); _thumbnails.container.y = 300; _thumbnails.addEventListener(ImageChangeEvent.INDEX_CHANGE, onImageChange); _detailImage = new DetailImage(detailImage_mc); _pagination = new Pagination(previous_mc, next_mc); _pagination.addEventListener(ImageChangeEvent.INDEX_OFFSET, onImageOffset); } private function onImageDataLoad(e:Event):void { var thumbPaths:Array = _imageData.getThumbnailPaths(); for (var i:uint = 0; i < thumbPaths.length; i++) { _thumbnails.addThumbAt(thumbPaths[i], i); } } private function onImageChange(e:ImageChangeEvent):void { _currentImageIndex = e.value; updateImage(_currentImageIndex); } private function onImageOffset(e:ImageChangeEvent):void { _currentImageIndex += e.value; if (_currentImageIndex < 0) { _currentImageIndex = 0; } else if (_currentImageIndex >= _imageData.length) { _currentImageIndex = _imageData.length - 1; } updateImage(_currentImageIndex); } private function updateImage(index:uint):void { var img:ImageData = _imageData.getImageByIndex(index); _detailImage.setImage(img.source, img.attribution, img.link); } } }
By this point, the additions shouldn't be too shocking. First, in line 10, we import the Pagination
class and then create a property for it at line 18. On lines 33 an3420 we create the instance and listen for the INDEX_OFFSET
event. The most substantial change is the addition of the onImageOffset
method, which is very similar to the onImageChange
method, only it needs to pre-calculate the index from the offset value
of the ImageChangeEvent
and the current index. It also does some bounds checking, to make sure we haven't incremented past the end, or decremented past the beginning. Otherwise, it still calls updateImage
with that new index.
Now, the eagle-eyed among you will have noticed the usage of _imageData.length
, which isn't currently a valid property on the DataLoader
class. You're right; we need to add another data accessor method for DataLoader
:
public function get length():uint { return _xml.image.length(); }
This just provides a nice and clean read-only property that queries the XML data for the number of
nodes that exist. With this getter method in place, go ahead and run the movie. You'll be able to click on the arrow buttons now. Note that you can't swing past the beginning or end. This is by design; you could alter the logic to make it loop around to the other end, but for the purposes of this tutorial I want to have hard limits, for purposes of the next step.
Step 43: Keep the UI in Sync
Our last task to make sure the UI updates itself whenever there is any change to the display. For instance, a click on a Thumbnail
currently displays the appropriate image. However, it should also draw a selection around the clicked Thumbnail
, and also disable the next or previous button, if appropriate. Accordingly, a click on one of the PaginationButton
s should not only display the image, but also disable or enable the PaginationButton
as appropriate as well as draw the selection around the matching Thumbnail
.
You may notice that this is an instance where changes from one part of the UI affect a different part of the UI (aside from the more direct effect of changing the detail image). The temptation may be to have, say, the Thumbnail
draw it's own selection when clicked, or the PaginationButton
s to enable or disable themselves when clicked. However, if the Thumbnail
drew its own selection, then how would me manage the selection when we changed the image from the PaginationButton
s? It would be possible, but it would mean we'd be selecting the Thumbnail
s from two different locations.
Instead, we'll let the hierarchy of composition work for us and keep this UI logic in one place: in ImageViewer
. We're already managing the changing of the detail image from there, so why not also manage other UI updates there as well?
Update the updateImage
method like so:
private function updateImage(index:uint):void { var img:ImageData = _imageData.getImageByIndex(index); _detailImage.setImage(img.srouce, img.attribution, img.link); _thumbnails.selectThumbnail(index); _pagination.updateButtons(index > 0, index < _imageData.length-1); }
All we're doing is expanding the work involved when it's time to update the image, keeping the UI in sync.
Now we need to write the selectThumbnail
method on ThumbnailCollection
. First, we'll need a property to store the currently selected Thumbnail
object:
private var _currentThumb:Thumbnail;
And then we can write the selectThumbnail
method that will use that property:
public function selectThumbnail(index:int):void { if (_currentThumb) { _currentThumb.deselect(); } _currentThumb = _thumbnails[index] as Thumbnail; _currentThumb.select(); }
The logic in this method just makes sure the previously stored Thumbnail
(if there is one; there won't the first time we call this method) gets deselected, while the new one gets selected. This technique goes all the way back to the very first AS3 101 tutorial, on variables, if you want a deeper discussion.
Back to the present. It might feel a little strange to have the Thumbnail
CLICK
dispatch up through the ThumbnailCollection
object, then on to the ImageViewer
object, which then calls a method on the ThumbnailCollection
object, which then calls a method on the Thumbnail
object. Why not just have each Thumbnail
activate its own selection? Two reasons.
First, we should ideally go back out to our manager class (ThumbnailCollection
) for the logic, because not only do we want to select the clicked Thumbnail
, but we also want to de-select the previously selected Thumbnail
. As it is, each Thumbnail
is pretty autonomous, and that's the way we want it (encapsulation), and no individual Thumbnail
object knows about any other Thumbnail
object. So, we need the ThumbnailCollection
to perform the deselection, at the least, and it makes sense to perform the selection from the same location.
Second, as already mentioned, we want to be able to update the selection from other parts of the UI. And it's just a whole lot more manageable if we keep the logic to perform a selection in one place. Therefore, it makes sense for that one place to the "lowest common denominator" of the UI; the ImageViewer
class. In our hierarchy, separate branches don't know much about each other. For instance, the pagination branch doesn't know about the thumbs branch, and so a change from the pagination that affects the thumbs would naturally be handle at the place where the branches join, i.e., the ImageViewer
class.
Enough theory; go ahead and try out the movie already! The UI should stay in sync; clicking on a thumbnail affects the state of the pagination, and vice versa. Nice going!

Step 44: A Final Task
One last thing, that's probably bugging you if you have an eye for the useful. We should really bring up an initial image in the DetailImage
view. Should be easy enough...but where should implement the change?
We could feed the DetailImage
class an initial image to load. This would certainly display the image, but it wouldn't keep the UI updated. The PaginationButton
s wouldn't know to disable the previous button (assuming the image is the first image), and we wouldn't get a selection on the Thumbnail
.
We could have the ThumbnailCollection
automatically dispatch an ImageChangeEvent.INDEX_CHANGE
event once it's populated with data, automatically filling in "0
" for the value
. This would solve the problem of keeping the UI in sync. But is it really the ThumbnailCollection
's job to worry about that? I said before that if it works, then it's technically not wrong, but is it the best solution? In my opinion, doing this puts the responsibility in the wrong place. It's not up to the ThumbnailCollection
to have an initial selection. We'd really be doing it there for UI syncing purposes, which isn't what the ThumbnailCollection
object is about.
Why not just update the UI the same way we've established: have ImageView
call updateImage
. This way, it's the responsibility of the main document class to decide which image to display initially, and when, or if at all. It's also extremely simple to do so at this point. Just add one line to the onImageDataLoad
method (highlighted below):
private function onImageDataLoad(e:Event):void { var thumbPaths:Array = _imageData.getThumbnailPaths(); for (var i:uint = 0; i < thumbPaths.length; i++) { _thumbnails.addThumbAt(thumbPaths[i], i); } updateImage(0); }
Run the movie now, and not only do we have a nicely set up initial state, but we've also explored, one more time, the idea of responsibilities and encapsulation.
Step 45: For Further Experimentation
We could take this concept of keeping the UI in sync a bit further, and if you would like the practice I would recommend you do so. However, this tutorial is already longer than average and I must leave you with this challenge:
Make a "slideshow" button that also controls the image display. When clicked, it enters "play slideshow" mode, which starts up a Timer
and consequently redispatches the TimerEvent
s as ImageChangeEvent
s, of the INDEX_OFFSET
type. The slideshow should then pause when clicked again. In addition, it should automatically pause when you click on any of the thumbnails or the pagination (the assumption being that the user is interested in now resuming control of the images, and the auto-advance of the slideshow is no longer wanted).
At its core, this problem is just another branch of the hierarchy of composition, and the same model of dispatching events back the ImageViewer
object will do the trick. The implementation details are, of course, going to be different, but the general idea is the same.
Hopefully this conceptual discussion also brought to light an advantage of Object-Oriented Programming: when objects are properly "sealed" and not dependent on each other (that is, encapsulated), adding features becomes almost trivial. You don't have to hunt through a pasta salad of code finding the right things to update. Just add some new objects, and get them instantiated in the right place (yes, that's an over-simplification. I'm trying to sell you on OOP, right?).
Now, as I said, we don't have time to implement this slideshow feature here, and so I encourage you to try your hand at doing it yourself. I did, however, include a version of the project in the download package that has this finished already. If you need a hint, or are just too lazy to do it yourself but want to see it in action, take a look at the image_viewer_slideshow project.
Summary
In this tutorial we looked at building a simple project by utilizing Object-Oriented techniques. The project itself was (hopefully) something you could probably handle on your own without objects, but our goal was to demonstrate how a project can be constructed out of several objects and not just all lumped into one script.
The hierarchy of composition is something that takes some getting used to, but with practice it becomes rather natural, and you'll be able to start a new project by looking at the desired result and breaking it down into that hierarchy.
If you're new to Object-Oriented Programming, then all of this will probably be quite dizzying. Please don't be discouraged. It will take quite a bit of practice to get comfortable with OOP. The only way to get better, though, is to actually do it. You will probably write some pretty badly managed classes your first couple times out. That's OK; just learn what you can from the experience, and figure out what you would do differently if you had it to do again. I think it took me about a year from when I first learned OOP to get to a point where I actually felt like I didn't need to be ashamed of the code I had written. Just stick with it. Practice makes perfect, as they say.
With that said, I'm glad you've hung in there so far! This tutorial series isn't even over yet, but this particular installment was rather leviathan in nature. Thanks for reading, and we'll get into some even more advanced Object-Oriented techniques in the next article.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post