Linking the Main Timeline to a class

Source File: 

Next, we will link the main timeline to a custom class. Now that you know how to link a MovieClip from the library to a class, you should know that the main timeline itself is really just another MovieClip, and it links to a class in pretty much the same way, except we do it in a different place, the properties panel. The properties panel is context-sensitive. This means that it shows you the properties of whatever is currently selected on the stage, or in the Flash authoring tool in general. Click on a frame and it shows frame properties, for example. So we have to make sure that the document itself is selected to get Document properties. You can do this just by clicking a blank area on the stage, or by clicking the gray area around the stage.

Setting a Document Class

Now the properties panel will say "Document" at the top, and if we look a bit further down, there will be a Class field available. Type the word "Main" into this field, like so:

Notice that in this case, there is no checkmark button next to the field, but there is a "pencil" button. This pencil button is an "open for editing" button. We haven't written the Main class yet, but if we test our movie to compile the swf, it works fine. The reason is that if we don't supply a Document class, Flash will make one for us. Yes, this special class that links to the Main timeline is also known as the Document Class. Note that you do not include the .as extension, just as before when linking a library symbol to a class.

If you click the pencil icon, in Flash CS5 you get a window that opens up for editing and some code automatically inserted for you! I got this:

package  {
	
	import flash.display.MovieClip;
	
	
	public class Main extends MovieClip {
		
		
		public function Main() {
			// constructor code
		}
	}
	
}

The file has a temporary name of "Script-1" so let's save it to the Working Folder as Main.as. Now we're linked. But just to make sure, a lot of times I put a simple trace message in the constructor:

trace("working");

Then I save and run it. If I get the message being traced to the output window, I know for a fact that the linkage is working properly. Another check is the pencil button. If I click it and the same file opens for editing, I know I'm linked.

Next, delete the trace message from the constructor again, and save the file. Later we'll make it do something else. But first, go back to the fla file, and delete all the instances of circle from the stage. You can do this just by deleting the content layer. All that will remain will be just an empty actions layer. Click on the first frame of the actions layer. Press F9 to get the Actions panel. Type these two lines into the window:

var circle:Circle = new Circle();
addChild(circle);

At this point, the fla file is still named code_in_custom_class.fla. Let's save it under a new name. From the File menu, choose "Save As..." and save it as code_in_document_class.fla.

Press CTRL-Enter to test the movie. An instance of the Circle class is dynamically added to the stage, and it has the hand cursor when we hover, and it's drag-and-drop-able, like we would expect! Next, close the running swf. Highlight those two lines in the actions panel and cut them to the clipboard. Instead, paste them into the constructor function of the Main.as file. Be sure to save this file. Press CTRL-S, then press CTRL-Enter. I do this in rapid succession to quickly test something. Conveniently, when you press CTRL-Enter, even if you are focused in a script window, Flash will run whatever fla file is associated with your script file, so you don't even have to change windows.

We can see from this quick example that it matters not whether our code is on the first frame of the main timeline, or in the constructor of the document class -- they are equivalent. Just like the linking of classes to symbols in the library, the linking of our main timeline to the document class makes them one and the same.

A new and bigger playground

When you program on the timeline a lot, it's easy for that to become your only (narrow) focus, and you tend to think in terms of only the fla file, which simply publishes to the same folder. When you begin programming with external classes, it broadens your horizons a bit. Now the playground extends beyond the fla file and into the file and folder system itself ("To infinity.... and beyond....!). The true role of the fla file becomes a lot more clear:

  1. It's a holder, a library for your graphics, animations, and (potentially) other assets, too.
  2. It's a compiler. It draws from the well of your code, and produces a swf file for you.

Let's try a little experiment. We're going to create a Test class. But first, let's try instantiating it without actually having one. Add this line to the constructor of the Main class:

var test:Test = new Test();

Save the file and run it. This produces the following two error messages:

Main.as, Line 12 1046: Type was not found or was not a compile-time constant: Test.
Main.as, Line 12 1180: Call to a possibly undefined method Test.

What happened is that Flash tried to compile our project. It went through the main class, noticed that we were trying to instantiate a class called Test. It searched the classpath for a Test.as file. It didn't find one, so it refused to compile the project.

Now, instead of changing the code, let's supply a Test.as file. Choose File, New (CTRL-N) and create a new Actionscript file. Type in (or paste) the following:

package {
	public class Test {
		public function Test() {
			//constructor
			trace("Test class working");
		}
	}
}

Save the file to the Working Folder as Test.as. Press CTRL-Enter to compile the project. You should get the words "Test class working" traced to the output window. This time Flash finds our file, just by virtue of the fact that we saved it to the same folder, and that folder is in the classpath.

So what's happening is that we now have three classes, they reside in the same package (folder) which is in the classpath. These classes are able to create instances of each other and communicate freely if they define any public methods. Let's give Test.as a public method:

package {
	public class Test {
		public function Test() {
			//constructor
			trace("Test class working");
		}
		public function talk():void {
			trace("My public method is working!")
		}
	}
}

Save the file. Next, add a line to the Main.as file so that it now looks like this:

package  {
	import flash.display.MovieClip;
	
	public class Main extends MovieClip {

		public function Main() {
			var circle:Circle = new Circle();
			addChild(circle);
			var test:Test = new Test();
			test.talk();
		}
	}
}

Making our classes "talk" to each other

Save Main.as, and press CTRL-Enter to test the movie. You will now get two trace messages to the output window, because we just made the instance of the Test class "talk." Let's consider what's happening. Classes don't usually do anything unless we make an instance of them. Then the instance can be made to do something by calling its public methods, perhaps from another class. We didn't make an instance of Main, but the Flash compiler does that for us. Then, Main is programmed to create the other two instances for us.

One of the classes it makes an instance of is linked to a library symbol in the fla, and we can display that instance on the screen because it extends the MovieClip class. We can't display the Test class (not as it is, anyway) because that class doesn't extend any display object. But since the Main class created the Test class instance, it has a reference to the instance, and it can use that reference to call any public methods defined in the class. The instance of the Circle class can't call public methods on the instance of Test, because it does not have a reference to the instance. But if it did, it could, because their classes are in the same package.

Let's prove it. Open the Circle.as file. We need to give it a new private variable to hold the reference:

private var _test:Test;

Next, we'll give it a new public method where it can gain a reference to an instance of the Test class, and a new public method that calls the talk() method on the instance of Test that it gained. So here's the new version of the Circle class:

package {
	import flash.events.MouseEvent;
	import flash.display.MovieClip;
	
	public class Circle extends MovieClip {
		private var _test:Test;
		
		public function Circle() {
			buttonMode = true;
			addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
		}
		private function mouseDownHandler(event:MouseEvent):void {
			startDrag();
		}
		private function mouseUpHandler(event:MouseEvent):void {
			stopDrag();
		}
		public function set test(value:Test):void {
			_test = value;
		}
		public function talkToTest():void {
			_test.talk();
		}
	}
}

Finally, here's the new version of Main:

package  {
	import flash.display.MovieClip;
	
	public class Main extends MovieClip {

		public function Main() {
			var circle:Circle = new Circle();
			addChild(circle);
			var test:Test = new Test();
			test.talk();
			circle.test = test;
			circle.talkToTest();
		}
	}
}

Now, Here's everything that Main is doing: it makes an instance of the Circle class, and adds it to the display list. Then it makes an instance of the Test class. Next, it calls the talk() method on the Test instance (named test). Next, it sends a reference to test to the Circle instance, using the setter function we defined for the Circle class.

We didn't cover the set keyword. I have covered getters and setters in my previous tutorials. I'll just say now that the use of the set keyword allows you to create a method in your class that is treated exactly like a property from the outside. So we can pretend (from the outside) that the Circle class has a property called test, but when we set its value using the dot:

circle.test = test;

...we are really calling the set test() public method inside the Circle class. I admit this can be confusing at first, but trust me, you'll get used to it and eventually find it as handy as sliced bread. Anyway, now that the circle instance has a reference to the test instance, Main next tells the circle instance to run its public talkToTest() method, and in that method the circle instance tells the test instance to run its talk() method. So now we have our classes talking to each other, with Main basically acting as the puppeteer. Save all the files and press CTRL-Enter to test the movie. Everything should be working smoothly, and there is no code at all inside the fla file!

Here's the latest version of our Working folder diagram. The classpath and publishing paths are still the same. We're still drawing from the same folder for classes, and publishing the swf to the same folder:

On the next page, I'll elaborate even more on the classpath, and we'll explore how the import statement really works!