Am I writing AS3 the right way? - apache-flex

I'm very new to flash and actionscript 3. I've been reading a lot about it and this is also my first aprouch on object oriented programming.
So far, I created an application with a login button, that's all. However, I would like to know what kind of things I am doing wrong or should be doing different (or better). I am using Adobe Flex Builder 3.
The main actionscript file is Client2.as:
package
{
//import required libraries
import flash.display.Sprite;
//set project properties
[SWF(width="800", height="600", frameRate="31", backgroundColor="#C0C0C0")]
//launch main class
public class Client2 extends Sprite
{
public function Client2() { //the constructor
trace("Client launched.");
var loginGui:LoginInterface = new LoginInterface(); //load the login interface object
loginGui.init(); //initialize the login interface (load it)
addChild(loginGui); //add login gui to the display tree
}
}
}
It is loading the login interface object. Is that a good thing, and am I doing it the right way?
Then there's the LoginInterface.as class file:
package
{
//import required libraries
import flash.display.Sprite;
//the LoginInterface class
public class LoginInterface extends Sprite
{
public function LoginInterface() //the constructor
{
trace("LoginInterface object loaded.");
}
public function init():void //initialize the login interface (load it)
{
trace("LoginInterface init method was called.");
var loginButton:CustomButton = new CustomButton(300, 300, 100, 30, 3, 18, "Login!"); //create a new custom button
addChild(loginButton); //add the custom button to the display tree
}
}
}
What about that? Any comments? To make the creation of simple buttons a bit easier, I then created another class file called CustomButton.as -->
package
{
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
public class CustomButton extends Sprite
{
public function CustomButton(xLoc:int, yLoc:int, width:int, height:int, iLabelOffset:int, fontsize:uint, label:String)
{
//create new simple button instance
var myButton:SimpleButton = new SimpleButton();
//create the look of the states
var normal:Sprite = new Sprite();
normal.graphics.lineStyle(1, 0x000000);
normal.graphics.beginFill(0x6D7B8D);
normal.graphics.drawRect(xLoc, yLoc, width, height);
//the mouseover sprite
var over:Sprite = new Sprite();
over.graphics.lineStyle(1, 0x000000);
over.graphics.beginFill(0x616D7E);
over.graphics.drawRect(xLoc, yLoc, width, height);
// assign the sprites
myButton.upState = normal;
myButton.downState = normal;
myButton.hitTestState = normal;
myButton.overState = over;
//add the button to the display tree
addChild(myButton);
//create button label
var tText:TextField = new TextField();
tText.mouseEnabled = false,
tText.x = xLoc;
tText.y = yLoc + iLabelOffset;
tText.width = width;
tText.selectable = false
var Format:TextFormat = new TextFormat();
Format.font = "Arial";
Format.color = 0x000000;
Format.size = fontsize;
Format.bold = false;
Format.align = TextFormatAlign.CENTER;
tText.defaultTextFormat = Format;
tText.text = label;
addChild(tText)
}
}
}
Is there anything to comment on this? I am sure that I'm doing a lot of things wrong, maybe I didn't really get the whole object oriented thing? Also, I have a bad feeling about the way I am using the "extends ..." after a class declaration, mainly because I'm just using Sprite all the time and don't really understand why or what it does (having trouble finding out on the internet aswell). Another thing I am unsure about is the naming of variables in AS3. Should I really be using names such as xLoc, or iLabelOffset? I think I am not being very consistent in my variable naming atleast?
I hope someone can give me a push to a better track than the one I am on now, as I am sure that I should improve my AS3 coding before I continue working on this beast.
Thanks a lot.

My opinion:
A class called Client2 is probably a bad naming choice. Client2 isn't telling me much. How much will it tell you in a year's time?
In CustomButton, initialization is taken care of in the constructor. In LoginInterface, using an instance of the class requires an explicit call to init(). Easy to forget and unnecessary. Unless there's a good reason not to, call init from the constructor.
What does iLabelOffset mean? better to use a less confusing name in a parameter list.
The parameter list of the CustomButton constructor is pretty long. It's not necessary to pass in the x and y. Sprite has an x and y property already, so put everything back to a zero offset and manipulate the x and y properties of the CustomButton once it's constructed.
Of the remaining parameters to the CustomButton constructor, consider reordering them so that you can provide default parameters (which can only go at the end of the parameter list). labelOffset and fontSize seem like good candidates.
Keep function size small by removing repeated code. Create a function to create the button state Sprites that takes a color in its parameters (or better yet, move this functionality into a new type of Sprite derived class), and also add a createLabel function so that you can move that code out of the constructor. Your code will become easier to read and maintain if you try to keep function size small. It also means you have to write less comments ;-)

Spender hit the nail on the head. Those are definitely the issues that are raised when I looked over your code. The things he mentioned are not nessesarly Actionscript issues, (issue's not quite the right word, perhaps "areas to note"), these are issues general to all programing languages. Descriptive naming for example is extremely important.
There are few books that focus on this side of programming, and even fewer that do it well. I would highly recommend picking up the following two books if you want to grow more in this area, (I'll recommend it even if you don't want too :)
Code Complete
The pragmatic programmer
There both books that every programmer should read, so check them out. You're code from an Actionscript point of view is fine, but that's just syntax. It's important to note that these skill will never develop unless you actually write code, so by all means "continue working on this beast" and the rest will follow suit.

Just as a matter of style, I like to declare my variables outside of the constructor. It helps me to feel that I won't have any surprises with public vs private or scope. Also notice the added white space, which can improve readability.
public class CustomButton extends Sprite
{
private var myButton:SimpleButton;
private var normal:Sprite;
private var over:Sprite;
// etc ...
public function CustomButton(xLoc:int, yLoc:int, width:int, height:int, iLabelOffset:int, fontsize:uint, label:String)
{
//create new simple button instance
myButton = new SimpleButton();
//create the look of the states
normal = new Sprite();
normal.graphics.lineStyle(1, 0x000000);
normal.graphics.beginFill(0x6D7B8D);
normal.graphics.drawRect(xLoc, yLoc, width, height);
//the mouseover sprite
over = new Sprite();
over.graphics.lineStyle(1, 0x000000);
over.graphics.beginFill(0x616D7E);
over.graphics.drawRect(xLoc, yLoc, width, height);
// etc ...

Related

How do you create a general popup that can have many different types of dataproviders?

If I have a flex component that is a general popup, it's basically just a white popup that I pass an Array named "modules" to.
For instance:
var array:Array = ["mainArticle","title"];
or
var array:Array = ["creditCard"];
These are two examples that I might pass in. The first one would add my modules to the popup so the popup will be used for editing an "article." The second would add the Credit Card Change module, which would be a form that would allow the user to update their credit card information.
My question resides in the dataProvider for this popup. If I am passing in the article updater, I need a dataProvider that contains information like "font," "color," "size," etc. If I am passing in the credit card updater, I need a dataProvider that contains information like "number," "securit code," "expiration date," etc.
I could have a dataProvider class that has all of the information and only sets what I need, but it could get huge if I did something like:
public class myDataProvider {
public var mainTextFont:String;
public var mainTextSize:int;
public var mainText:String;
public var cardNumber:String;
public var cardExpiration:Date;
public var cardSecurity:String;
}
This is sort of an abstract idea, but I am looking for a solution that allows me to give my popup dataproviders without using one central dataProvider that would have a copy for every possible situation.
Thanks!
The simplest way to approach this is to create different dataProviders, depending on the class. My main popup has a "ModuleList" (custom list of strings) and it adds "modules" (not an actual flex module) to itself, giving each one the correct type of dataProvider.
public var recipientList:RecipientList;
private function setupModules():void {
for each( var s:String in moduleList ){
switch( s ){
case 'recipients':
var recipients:Recipients = new Recipients();
recipients.list = recipientList;
break;
case 'article':
// Article Logic
break;
case 'creditCard':
// Credit Card Logic
break;
}
}
}
This is just a generic idea, I was wondering what the best way to do this was, and this is how I decided to approach it.
Would you consider use a generic Object class and wrap any param you need in the Object?
var data:Object = new Object();
data.mainTextFont = "";
data.mainTextSize= "";
data.mainText= "";
For component, you may also consider use "State" value to control the display/layout.
Hope this helps.

Can I control multiple instances of movieclips in a loaded swf at once?

I am loading an swf created in flash professional cs5 via the loader class into a flex 4.1 application. The flash file contains multiple movieclips that are exported for actionscript and those movieclips exist in many instances throughout the movie.
Iterating through everything, comparing class types seems to be the most easy but also the most redundant way to solve this. Is there any way of using the class name as a kind of global selector to access the clips?
I could also make the sub-clips in the flash listen for an event on which they perform an action, but I am not really sure what might be best.
In cases like these, I find that a good way to solve the problem is to create a statically accessable class that manages instances of other classes that are registered with it on instantiation. As an example...
public class GlobalStopper{
private static var clips:Array = [];
public static function add(mc:MovieClip):void{
clips.push(mc);
}
public static function stop():void{
var mc:MovieClip;
for(var i:int = 0, ilen:int = clips.length ; i < ilen ; i++){
mc = clips[i] as MovieClip;
if (mc) mc.stop();
}
}
}
and...
public class GloballyStoppableMovieClip extends MovieClip{
public function GloballyStoppableMovieClip(){
GlobalStopper.add(this);
}
}
Any and all instances of GloballyStoppableMovieClip are instantly registered with the GlobalStopper, so calling
GlobalStopper.stop();
...will stop all registered movieclips.
You can add in any other functions you want. Furthermore, instead of having add accept MovieClip instances, you could have it accept IStoppable or IPlayable objects that implement public functions stop() and play() that your movieclip subclass (or non-movieclip object that also might need to stop and play!) then implements.
But as for jQuery-like selectors? Not really the way I'd handle this particular issue.
i guess typing it out did the trick. i used the event solution:
in the root timeline i placed a function like this:
function cause():void {
dispatchEvent(new Event("do stuff",true));
}
and in the library clip's main timeline goes:
DisplayObject(root).addEventListener("do stuff", function (e:Event=null) {
... whatever ...
});
this is dirty but you get the idea.

Flex 4 Desktop App TooltipManager

I have a flex app that used to be an internet based app. In it I have a function that creates tooltip error messages when I tell it to create an error message. I pulled this app out verbatim to a desktop app and restructured things a bit to get it to run, but I did not mess with the core fundamentals of the mxml file that utilizes this aside from changing the root tag from a type of 's:Group' to 's:Window'
Everything runs correctly, but the tooltips are not displaying. I can't seem to figure out why, so I thought I'd run this by you guys. Here's the tooltip code (inline mxml code in the fx:script cdata tag):
import mx.controls.ToolTip;
import mx.managers.ToolTipManager;
public var errorTip:ToolTip;
private function createErrorMsg(errorMsg:String, object:Object):void {
if (errorTip){ToolTipManager.destroyToolTip(errorTip);};
errorTip = ToolTipManager.createToolTip(errorMsg,object.getBounds(root).x + object.width,object.getBounds(root).y) as ToolTip;
errorTip.setStyle("styleName", "errorTip");
errorTip.visible = true;
errorTip.enabled = true;
}
Basically, I pass the function a string and an object (text input, checkbox, button, etc...etc...) and it positions it and displays the error message. This fully works in my web version, but not in my desktop version.
Here's the code that instantiates the window:
var window:LoginWindow = new LoginWindow();
Window.systemChrome = NativeWindowSystemChrome.NONE;
Window.transparent = true;
Window.open(true);
Window.maximize()
Any ideas?
On a side note, I check to see if the errorTip exists at the beginning of the function and then destroy it so that the higher scoped variable 'errorTip' always equals the reference to the currently displayed error. This allows me to just destroy that error tip on form validation and then error check again, but it only allows one tooltip to be displayed at a time. Is there a better way to query the tooltip manager for all of it's currently displayed tooltips and destroy them all?
To resolve this you must change the line that calls 'createTooltip' to the following:
errorTip = ToolTipManager.createToolTip(errorMsg,object.getBounds(root).x + object.width,object.getBounds(root).y, null, object as IUIComponent) as ToolTip;
The idea behind this is that you can now just call 'createErrorMsg("myError",myComponent)' and it'll display an error message there, which you can then add another function:
private function clearError():void
{
if(errorTip) errorTip.visible = false;
}
to actually remove the error message. The down side of this is that it only displays one message at a time but I'll tweak it a bit to change that up. So here's my better resolution:
Error messages will (traditionally) only be displayed in a batch set. (Basically you will only have a handful of tooltips displayed at one point in time and they will all be related to the form you are on). This won't always be true, but that's not my concern here. I created a utility class that performs error message handling:
package Utils
{
import mx.controls.ToolTip;
import mx.core.IUIComponent;
import mx.managers.ToolTipManager;
public class ErrorUtils
{
private static var errorCache:Array = new Array();
public function ErrorUtils()
{
}
public static function createErrorMsg(errorMsg:String, object:Object):void {
var errorTip:ToolTip = ToolTipManager.createToolTip(errorMsg,object.getBounds(object.root).x + object.width,object.getBounds(object.root).y, null, object as IUIComponent) as ToolTip
errorTip.setStyle("styleName", "errorTip");
errorTip.visible = true;
errorTip.enabled = true;
var position:int = errorCache.length;
errorCache.push(errorTip);
}
public static function clearAllErrors():void
{
for each(var error:ToolTip in errorCache)
{
ToolTipManager.destroyToolTip(error);
}
}
}
}
To use the class you simply call ErrorUtils.createErrorMsg("my message",invalidComponent), and to clear the errors you simply call ErrorUtils.clearAllErrors()
That way if you have a form and you have a validation function for it, you simply call that function every time it comes back as invalid. The only thing that this is missing is any kind of way to clear a specific error, but to handle that you could place all of the creation calls inside of a master validation function that validates all the fields in your form, then call that each time you gain or loose focus on a field and call 'ErrorUtils.clearAllErrors()' at the beginning of the function.
BTW:
this is the source of the original tutorial I received to do this, I just abstracted it a bit
http://aralbalkan.com/1125
so credits go to the above site for the original code

Can't inherit from classes defined in an RSL?

Note: This is an Actionscript project, not a Flex project.
I have class A defined in an RSL (technically, it's an art asset that I made in the Flash IDE and exported for actionscript. The entire .FLA was then exported as a SWC/SWF).
I my main project I have class B, which inherits from class A. The project compiles fine, with no errors.
However, when when the project runs and I try to create an instance of Class B, I get a verify error. Creating an instance of Class A works just fine, however:
import com.foo.graphics.A; // defined in art.swf / art.swc
import com.foo.graphics.B; // defined locally, inherits from A
...
<load art.SWF at runtime>
...
var foo:A = new A(); // works fine
var bar:B = new B(); // ERROR!
// VerifyError: Error #1014: Class com.foo.graphics::A could not be found.
For reference, here is how I'm loading the RSL:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onArtLoaded);
var request:URLRequest = new URLRequest("art.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
loader.load(request, context);
Class B is defined as follows:
import com.foo.graphics.A;
class B extends A {}
I don't think this is a bug. It's more a linkage problem.
The verifier error doesn't happen when you try to create an instance of B. It happens as soon as your main swf is loaded and verified by the player. This is an important distinction. To see what I mean, change this code:
var bar:B = new B();
to
var bar:B;
You'll still get the error.
I don't know how you are builing the swf, but from the error it seems evident that the A class (B's parent) is being excluded from the swf. I can reproduce this using this mxmlc switch:
-compiler.external-library-path "lib.swc"
However, changing it to:
-compiler.library-path "lib.swc"
The problem goes. Obviously, this kind of defeats the purpose of loading the assets at runtime, since these assets are already compiled into your main.swf (in fact, it's worse, because by doing that you've just increased the global download size of your app).
So, if you set your art lib as external, the compiler can do type checking, you'll get auto-complete in your IDE, etc. Your B class still depends on A being defined, though. So, at runtime, A has to be defined whenever B is first referenced in your code. Otherwise, the verifier will find an inconsitency and blow up.
In the Flash IDE, when you link a symbol, there's a "export in first frame" option. This is how your code is exported by default, but it also means it's possible to defer when the definition of a class is first referenced by the player. Flex uses this for preloading. It only loads a tiny bit of the swf, enough to show a preloader animation while the rest of the code (which is not "exported in first frame") and assets are loaded. Doing this by hand seems a bit cumbersome, to say the least.
In theory, using RSL should help here if I recall correctly how RSL works (the idea being the a RSL should be loaded by the player transparently). In my experience, RSL is a royal pain and not worth the hassle (just to name a few annoying "features": you have to hard-code urls, it's rather hard to invalidate caches when necessary, etc. Maybe some of the RSL problems have gone and the thing works reasonably now, but I can tell you I've been working with Flash since Flash 6 and over the years, from time to time I'd entertain the idea of using RSL (because the idea itself makes a lot of sense, implementation aside), only to abandon it after finding one problem after the other.
An option to avoid this problem (without using RSL at all) could be having a shell.swf that loads the art.swf and once it's loaded, loads your current code. Since by the time your code.swf is loaded, art.swf has been already loaded, the verifier will find com.foo.graphics.A (in art.swf) when it checks com.foo.graphics.B (in code.swf).
public function Shell()
{
loadSwf("art.swf",onArtLoaded);
}
private function loadSwf(swf:String,handler:Function):void {
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handler);
var request:URLRequest = new URLRequest(swf);
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
loader.load(request, context);
}
private function onArtLoaded(e:Event):void {
loadSwf("code.swf",onCodeLoaded);
}
private function onCodeLoaded(e:Event):void {
var li:LoaderInfo = e.target as LoaderInfo;
addChild(li.content);
}
In your current main class, add this code:
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
Move your constructor logic (if any) to the init method, and it should work fine.
But what I don't like about this approach is that you have to create another project for the shell.
What I do, generally, is have a class that proxies the graphic asset.
private var _symbol:MovieClip;
public function B() {
var symbolDef:Class = ApplicationDomain.currentDomain.getDefinition("com.foo.graphics.A") as Class;
_symbol= new symbolDef();
addChild(_symbol);
}
Since com.foo.graphics.A is just a graphical asset, you don't really need to proxy other stuff. What I mean is, if you want to change x, y, width, etc, etc, you can just change these values in the proxy and the result is in practice the same. If in some case that's not true, you can add a getter / setter that actually acts upon the proxied object (com.foo.graphics.A).
You could abstract this into a base class:
public class MovieClipProxy extends MovieClip {
private var _symbol:MovieClip;
public function MovieClipProxy(linkagetName:String) {
var symbolDef:Class = ApplicationDomain.currentDomain.getDefinition(linkagetName) as Class;
_symbol = new symbolDef();
addChild(_symbol);
}
// You don't actually need these two setters, but just to give you the idea...
public function set x(v:Number):void {
_symbol.x = v;
}
public function get x():Number {
return _symbol.x;
}
}
public class B extends MovieClipProxy {
public function B() {
super("com.foo.graphics.A");
}
}
Also, injecting the app domain as a dependency (and moving the instantiation mechanism to other utility class) could be useful for some projects, but the above code is fine in most situations.
Now, the only problem with this approach is that the linkage name in the constructor of B is not checked by the compiler, but since it's only in one place, I think it's manageable. And, of course, you should make sure your assets library is loaded before you try to instantiate a class that depends on it or it will trhow an expection. But other than that, this has worked fairly well for me.
PS
I've just realized that in your current scenario this could actually be a simpler solution:
public class B extends MovieClip {
private var _symbol:MovieClip;
public function B() {
_symbol = new A();
addChild(_symbol);
}
}
Or just:
public class B extends MovieClip {
public function B() {
addChild(new A());
}
}
The same proxy idea, but you don't need to worry about instantiating the object from a string using the application domain.

AS3: removing objects by array item reference

I am trying to add some Sprite objects as the contents of an array, and I would like to be able to "clear" them from the stage. I would assume that if there are loaders involved, I need to do
_imgArray[i].close();
_imgArray[i].unload();
And if I am using a sprite, I can do:
removeChild(_imgArray[i]);
None of the above work. WHY???
For an example and/or description of how I am setting this up, see Joel's post here
...but note that he hasn't included a reference for deleting them from view.
Currently I try:
for(i = 0; i < _localXML.length(); i++)
{
var tmp:BMLink = new BMLink(_localXML[i], _bw, _bh, i);
_imgArray[i] = tmp;
_imgArray[i].x = (_bw + _mainpad) * i;
_base.addChild(_imgArray[i]);
}
But this doesn't work.
I would love it if someone could explain to me why this wouldn't be proper syntax.
The class instances that are populating the array are all extending sprite, but they have their own individual loaders inside w/ progress events etc.
jml
OK; I finally figured it out through a bunch of trial and error.
It seems that I was attempting to remove the child of my main class sprite (this) rather than the sub-sprite that I had added the children to.
Sorry for the noise, but for the record, if you find that you can't do
this.removeChild(_imgArray[i]);
it's not because you don't have the correct syntax, but because you might not have an
_imgArray[i]
at that particular point of your display list hierarchy... so...
_base.removeChild(_imgArray[i]);
...worked in this case.
jml
You can make an Interface IDestroy for example with a destroy method who will manage all cleaning/removing stuff :
public interface IDestroy{
function destroy():void;
}
public class MySprite extends Sprite implements IDestroy {
..
public function destroy():void{
// remove events
..
// remove loader
..
//remove from parent
if (parent!==null){
parent.removeChild(this);
}
// etc.. more cleaning
}
}
then when you have an object who is an instance of IDestroy you can call the destroy method
if (myObject is IDestroy){
IDestroy(myObject).destroy();
}
or another way
var id:IDestroy=myObject as IDestroy;
if (id!==null)
id.destroy();
Edit:
I don't understand why any of the method i gave you in the comment will not work but _base.removeChild(_imgArray[i]) will :
addChild and removeChild accept only a DisplayObject as a parameter, so if you can do _base.addChild(_imgArray[i]) it means that _imgArray[i] inherits from DisplayObject and _imgArray[i] has a parent.
So var myDisplayObject:DisplayObject=_imgArray[i] as DisplayObject; will not return null and you will be able todo myDisplayObject.parent.removeChild(myDisplayObject); which is a general approach to your problem without relying on your _base DisplayObjectContainer (MovieClip/Sprite/...)

Resources