Home ProjectsThe Computus Engine Building an AS3 component framework: The Finished BaseComponent Class

Building an AS3 component framework: The Finished BaseComponent Class

by John

Download the code

After a few weeks of working with the AbstractComponent class I think it’s time for it’s graduation. I’ve made quite a few changes since the version I posted back in June. These have mostly been to simplify the class so I’ll run through some of the thinking first then post the code for the first time as a downloadable class.

AbsractComponent is now BaseComponent
Okay I’ve been mulling this one for a few weeks now but I’m going to change the name of the base class from AbstractComponent to BaseComponent. I know it seems like a little thing but I think BaseComponent is more descriptive. It also leaves the way open for an AdvancedComponent (possibly with child management and granular invalidation) at a later date.

suspend() and resume() are gone
Although I still like the idea of being able to suspend and resume a component whether it is on stage or off I found that in practice it led to over complicated code. So suspend() and resume() are gone and there function is now absorbed into the existing init() and destroy() functions. Just to recap:

init()
This function is automatically called when the component is added to the stage either by placing it on the timeline or by addChild() in AS3. You should place all your initialisation code here.

destroy()
This function is automatically called when the component is removed from the stage either by removing it from the timeline or by removeChild() in AS3. You should place all your suspend and garbage management code here.

I have reworked the functions that call init() and destroy() so that you no longer need to call super.init() or super.destroy() when you directly extend the BaseComponent class.

Garbage Management
Garbage Collection continues to be a real hot topic in the Flash development community. Last month Colin Moock posted an unofficial list of activities that need to be carried out in order to release a SWF or MovieClip for garbage collection. One of the aims of my BaseComponent class is to provide a simple standard structure and then automate the process where possible. If you didn’t catch the post then here’s the list in full:

  • Tell any loaded .swf child assets to disable themselves.
  • Stop any sounds from playing.
  • Stop the main timeline, if it is currently playing.
  • Stop any movie clips that are currently playing.
  • Close any connected network objects, such as instances of Loader, URLLoader, Socket, XMLSocket, LocalConnection, NetConnections, and NetStream.
  • Release all references to cameras and microphones.
  • Unregister all event listeners in the .swf (particularly Event.ENTER_FRAME, and mouse and keyboard listeners)
  • Stop any currently running intervals (via clearInterval()).
  • Stop any Timer objects (via the Timer class’s instance method stop()).

A new IComponent interface
If you need a formal reminder to add an init() and destroy() function to any class that extends BaseComponent then you can implement the new IComponent Interface.

draw() is now public
I’ve simplified the invalidation() and draw() code by making draw() public. If you need to redraw something straight away then call draw(). If you want to buffer requests and schedule a redraw then call invalidate().

That’s it really. The new class is a lot simpler and cleaner but the startup and shutdown (garbage management functions) are still fully automated. Nice.

Override warnings for init() and destroy()
I’ve added a couple of trace statements to these functions. If you override the functions in your own class then you’ll never see these. They are really there as a warning to remind you to always override these two functions. Thanks to Vic for pointing out that using the interface won’t throw any errors – oops!

The Code: BaseComponent.as

package org.computus.core
{
	import flash.events.Event;
	import flash.display.MovieClip;

	public class BaseComponent extends MovieClip
	{
		// ------------------------------------------
		// CONSTRUCTOR

		public function BaseComponent():void
		{
			super()

			// n.b. These events require a minimum Flash Player version of 9.0.28.0
			addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true )
			addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true )
		}

		// ------------------------------------------
		// MEMORY MANAGEMENT

		public function init():void
		{
			// Warning is raised if subclass doesn not override init()
			throw new Error( "WARNING: " + this + " did not override init()" )

			// initialise component here.
		}

		public function destroy():void
		{
			// Warning is raised if subclass doesn not override destroy()
			throw new Error( "WARNING: " + this + " did not override destroy()" )

			// suspend component here.
		}

		// ------------------------------------------
		// EVENTS

		private function onAddedToStage(e:Event):void
		{
			init()
		}

		private function onRemovedFromStage(e:Event):void
		{
			stop()

			removeEventListener(Event.ENTER_FRAME, onInvalidate)
			removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage)
			removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage)

			destroy()
		}

		private function onInvalidate(e:Event):void
		{
			removeEventListener(Event.ENTER_FRAME, onInvalidate);
			draw();
		}

		// ------------------------------------------
		// DRAW

		protected function invalidate():void
		{
			addEventListener(Event.ENTER_FRAME, onInvalidate, false, 0, true);
		}

		public function draw():void
		{
			// redraw component state here
		}
	}
}

The Code: IComponent.as

package org.computus.core
{
	public interface IComponent
	{
		function init():void
		function destroy():void
	}
}

You may also like

8 comments

P.J. Onori August 7, 02008 - 3:21 am

Quite a helpful series of posts on this subject – thanks so much for the effort in putting these posts together.

Reply
John August 7, 02008 - 9:34 am

Cheers P.J. – I’ve found the blogging process to be really helpful. It forces you to articulate your ideas. You can’t explain a concept to someone else before you fully understand it yourself. Glad you found it useful.

Reply
Vic August 12, 02008 - 8:56 am

Some nice articles!
We have a very similar approach, including PageManager and that skinning has to happen with the flash tools for a quick or nice design.

One question though,
I also work with an interface, because that’s best practice. But assume that I have the following:

public class Combobox extends BaseComponent implements IComponent

my combobox won’t give errors, even if I don’t have the init() and destroy(). So I’m not forced to implement an init() and destroy() as long as I extend BaseComponent. It’s logic, because those functions exist in BaseComponent. But hmm, isn’t there any way to enforce init() and destroy() to be implemented in Combobox.

grtz

Reply
John August 12, 02008 - 2:14 pm

Ah you got me! The interface was a bit of a last minute addition to the post. I use it as a reminder to myself to always have an init() and destroy() function, but your absolutely right, you don’t get any errors if you compile without them.

One way to do it could be to put trace() statements into the BaseComponent class. These could trace out something like: “WARNING: xxx class has not overridden init()” – I quite like that actually, might add that!

Reply
Vic August 12, 02008 - 3:44 pm

maybe even better than a trace, to be really sure it’s implemented 🙂

throw new Error(“WARNING : ” + this + “did not override init()”);

but then you have to replace draw() in your init, otherwise it will never be executed

I have another question 🙂
Is there a reason why you chose to initialize your component after it’s added to the stage.
Like this, you can’t just hold your component in memory and add it when needed. Same with removeChild(), the component is automatically destroyed, but in some cases you just want to add it to another sprite. It’s kinda all or nothing 🙂

grtz

Reply
John August 12, 02008 - 6:56 pm

your right, throw Error is better – I’ll update the code.

The reason for calling init() after the component hits the stage is that it is only at that point that it has access to the ‘stage’ and the DisplayObject hierarchy. This is essential info for something like a liquid layout where you need to be able to get to stage dimensions. I always assumed if you wanted to do an pre-initialisation that didn’t depend on the stage then you could put that code in the constructor.

The draw() not getting fired isn’t too much of a problem as the ‘deferred rendering’ functionality was always meant to be optional. I’ve started building out some basic components (which I’ll post shortly) and found that the simplest ones really don’t need it – it’s the more complex ones that make use of it. I might remove the draw() call from init() as I can see how it would be confusing.

Thanks again for the great feedback!

Reply
Jakob E November 19, 02008 - 5:47 pm

Hi John,

Nice and thorough work 🙂

One question about the invalidate method.
How about using invalidate(e:*=null) – allowing call using listeners?

Example:
override public function init():void {
stage.addEventListener(Event.RESIZE,invalidate);

}

Best,
Jakob E

Reply
John November 20, 02008 - 10:33 am

Hi Jakob,
Interesting idea. Hadn’t really considered tying events straight to invalidate(). I’d always imagined some intermediate step that updates the model then schedules a redraw. I see how it could be useful for reacting to changes outwith the component though. Nice idea.
jd

Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.