case
ADM Blog
5Jun/090

How to clone (duplicate) an object in ActionScript 3

For a project I needed to clone an object of unknown type. And by clone I mean to create a new instance of that same type and then fill out all its properties (including getters and setters) to mirror the original object.

Thanks to a friend, I discovered the describeType function in AS3. But this alone will only take care of the copying part. To create an object of the same type as another one we use getDefinitionByName.

Although Flash reflection is pretty basic, with a little work it will do the trick.

Get the application files.

Here's the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
< ?xml version="1.0" encoding="utf-8"?>
<mx :Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*" creationComplete="init()">
</mx><mx :Script>
 < ![CDATA[
 
     import mx.controls.Alert;
 
     private var source:DataObject = new DataObject();
     private var cloneObject:DataObject;
 
 
     private function init():void {
 
         source.name = 'John Doe';
         source.howMany = 4.5;
         source.when = new Date(0);
         source.complexProp = new DataObject();
         source.complexProp.name = 'Name in sub-object';
 
         cloneObject = UtilFunctions.clone(source) as DataObject;
 
         Alert.show("Clone:\nname = " + cloneObject.name + "\nhowMany = " + cloneObject.howMany + "\nwhen = " + cloneObject.when + "\ncomplexProp.name = " + cloneObject.complexProp.name);
     }
 
     /**
 
      * describeType will produce this (for a DataObject instance):
      *
      * <type name="DataObject" base="Object" isDynamic="false" isFinal="false" isStatic="false">
 
           <extendsclass type="Object"/>
           <accessor name="isHandicap" access="writeonly" type="Boolean" declaredBy="DataObject"/>
 
           <variable name="howMany" type="Number"/>
           <accessor name="complexProp" access="readwrite" type="DataObject" declaredBy="DataObject"/>
 
           <variable name="name" type="String"/>
           <variable name="when" type="Date"/>
 
 
      *
      * */
 
 ]]>
 
</mx>

And the UtilFunctions.as file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package
{
 import flash.utils.describeType;
 import flash.utils.getDefinitionByName;
 import flash.utils.getQualifiedClassName;
 
 public class UtilFunctions
 {
 
 
     public static function newSibling(sourceObj:Object):* {
         if(sourceObj) {
 
             var objSibling:*;
             try {
                 var classOfSourceObj:Class = getDefinitionByName(getQualifiedClassName(sourceObj)) as Class;
                 objSibling = new classOfSourceObj();
             }
 
             catch(e:Object) {}
 
             return objSibling;
         }
         return null;
     }
 
     public static function clone(source:Object):Object {
 
         var clone:Object;
         if(source) {
             clone = newSibling(source);
 
             if(clone) {
                 copyData(source, clone);
             }
         }
 
         return clone;
     }
 
     public static function copyData(source:Object, destination:Object):void {
 
         //copies data from commonly named properties and getter/setter pairs
         if((source) && (destination)) {
 
             try {
                 var sourceInfo:XML = describeType(source);
                 var prop:XML;
 
                 for each(prop in sourceInfo.variable) {
 
                     if(destination.hasOwnProperty(prop.@name)) {
                         destination[prop.@name] = source[prop.@name];
                     }
 
                 }
 
                 for each(prop in sourceInfo.accessor) {
                     if(prop.@access == "readwrite") {
                         if(destination.hasOwnProperty(prop.@name)) {
                             destination[prop.@name] = source[prop.@name];
                         }
 
                     }
                 }
             }
             catch (err:Object) {
                 ;
             }
         }
     }
 }
}



5Jun/090

Create professional Flex components

Flex Builder has a great way to organize its components in tree mode, which is very a good way to organize things and make things clear to any user who are coming to Flex world.

By default, every component you create that is not part of default Flex components you will have placed in the [Custom] directory of Flex Components view in your Flex/Flash Builder, and no matter what properties you add to them, they will never be visible in the Flex Properties standard view.

But what if you want to customize that and create a component that have them all like flex components do? Well, it's not that hard so let's do that.

Create the Component

First, you have to create a new Flex Library Project. Do this by going to File->New and choose Flex Library Project. Give it a name, a location, choose whatever Flex SDK you wish to build this for and then click finish.

Now you have a blank library project in which you can create whatever components you want.
Is important to use packages correctly and namespaces and not just drop the component in the [src] folder (you'll see later why). First create a package (eg. com.adm.component) in which you can add your custom component.

package com.adm.component
{
      import mx.containers.Canvas;
 
      public class mycomponent extends Canvas
      {
      }
}

Okay, now let's create some methods to have something going. We'll make this component be a big button with a method to enable the button and one to change it's caption. We'll use setters and getter's for those properties because some other actions might be required when changing them. So:

package com.adm.component
{
     import mx.containers.Canvas;
     import mx.controls.Button;
 
      public class mycomponent extends Canvas
      {
            private var _title : String = 'Title';
            private var _active : Boolean = true;
 
            private var btn : Button = new Button();
 
            public function CustomComponent()
            {
                      super();
                      this.btn.width = 100;
                      this.btn.height = 100;
                      this.btn.label = this.label;
                      this.addChild(this.btn);
            }
 
            public function set title(val : String) : void
            {
                      this._title = val;
                      this.btn.label = val;
            }
 
            public function get title() : String
            {
                     return this._title;
            }
 
            public function set active(val : Boolean) : void
            {
                     this._active = val;
                     this.btn.enabled= val;
            }
 
            public function get active() : Boolean
            {
                    return this._active;
            }
      }
}

So, if you now build your project, a .swc file will be generated in your [bin] folder, which you can add in other projects and use. But now, the component will be placed in the [Custom] directory in the Components View and not the one you want. We'll do that a bit later now let's just....

Give it an icon

This is really simple. All you have to do is get a nice looking .png icon (16x16 pixels preferably) and place it next to your component. Then, use the IconFile metadata tag to link it to your component.

....
[IconFile("icon.png")]
public class mycomponent extends Canvas
{
       private var _title : String = 'Title';
       .......

Inspectable properties

If you have extra information about the property that will help code hints or the Property Inspector (such as enumeration values or that a String is actually a file path) then also add [Inspectable] metadata with that extra info. For our methods we have:

       ...
       [Inspectable(category="General", type="String", defaultValue="")]
       public function set title(val : String) : void
       .....
       .....
       [Inspectable(category="General", type="Boolean", defaultValue="true", enumeration="true,false")]
       public function set active(val : Boolean) : void

This will also help a lot when we'll add this properties in the Flex Properties panel. For more informations about the [Inspectable] metadata tag visit http://livedocs.adobe.com/flex/2/docs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001658.html

Create custom component folder

So, if you want to build a professional component, you can't leave Flex add your component in the default [Custom] directory in the Components tree. So, in order to create your own folder, we must use few tricks.

First of all, you need two .xml files to describe the structure you want flex to use and overide it's default behavior.
The first file is the manifest.xml which describes the components in the package and their namespaces. In our case we'll have:

< ?xml version="1.0"?>
<componentpackage>
    <component id="mycomponent" class="org.adm.component.mycomponent"/>
</componentpackage>

Second, we need another xml to describe the way the designer will interpret all this.

< ?xml version="1.0" ?>
<design>
	<namespaces>
		<namespace prefix="adm" uri="http://www.adm.org" />
	</namespaces>
	<categories>
		<category id="Test" label="Test Panel" defaultExpand="true" />
	</categories>
	<components>
		<component name="mycomponent" namespace="adm" category="Test" displayName="Rename Me" />
	</components>
</design>

In the design.xml, you can specify the namespaces you used for the components, in this case adm will point to components in the org.adm folder.

Categories tag describes the folders you want added in the Components panel. Each category must have an id which you'll use to tell components where they should reside, a label for the category to stand as the directory name in the Components panel. defaultExpand is an optional parameter which if set to true, the folder will be showed expanded by default.

In the components tag, you specify which component goes in what category and under what title. The name parameter must match the id of a component listed in the manifest.xml. All the other parameters are pretty self explanatory.

Next, you have to include this two files in .swc package. To do that, follow the steps:

* Right Click at your project and select Properties
* In the left choose Flex Library Build Path
* Select the assets tab and mark to include manifest.xml and design.xml files
* Now select the Flex Library Compiler and include your namespace URL (in this case http://www.adm.org)
* Include the manifest file .xml you've created
* Click apply and ok to finish

design
manifest

After this, you should end up with something like this

comppanel

Add properties to the Flex Properties view

As I said before, no matter what properties you add to your component, they will never be visible in the Flex Properties standard view, only in the category view and that only if you use the [Inspectable] metadata tag. But few more lines in the design.xml file should take care of that.

	<component name="mycomponent" namespace="adm" category="Test" displayName="Rename Me">
		<mxmlproperties>
			<textfield id="title" name="Component Title:" />
			<combo id="active" name="Active:" />
		</mxmlproperties>
		<defaultattribute name="active" value="true" />
	</component>

The id of the mxmlProperties tag should be the function/variable names from your component you want to edit. You can also define default values for those properties using the defaultAttribute tag below. Here we've only used the textfield and the combo type but there are few more you can use.

<textfiled id="propertyOrStyle" name="Label:" [multiline="false"] />
<combo id="propertyOrStyle" name="Label:" />
<colorpicker id="propertyOrStyle" name="Label:" />
<filepicker id="propertyOrStyle" name="Label:" [wrapInEmbed="false"] />
<slider id="propertyOrStyle" name="Label:" min="0" max="10" increment="1" />

For combo boxes, if you use it for a Boolean property, it will automatically be populated with [true,false] values but if want something else, the [Inspectable] metadata tag has the enumeration property where you can define the properties from this combo.

flexprop

Another thing you must do in order to apply the values as soon as you change them from the properties view, is to set the functions/variables as [Bindable].

    ....
    [Inspectable(category="General", type="String", defaultValue="")]
    [Bindable]
    public function set title(val : String) : void
	....
	....
    [Inspectable(category="General", type="Boolean", defaultValue="true", enumeration="true,false")]
    [Bindable]
    public function set active(val : Boolean) : void
	....
	....

One more thing

Now, compiling this will give you a 300 something KB .swc file which is a bit large to distribute. The user will use this component inside a flex component so embedding all the libraries and SDK inside your .swc is useless. So the next step is to go to Properties->Flex Library Build Path->Library path tab, expand the build path library try and edit everything inside the SDK tree on Link Type and choose External

paths

Now the swc component only have 4KB. Big cut down, eh ?

Done

If this is to much trouble for you or you've missed something, I've attached the sources for this tutorial here. You can import this skeleton rename the package, namespace and the component and start building on this. I hope it all made sense and....happy coding.

2Jun/090

Flex Camp

Print

For all interested, we host a Flex Camp this Saturday (6 june) in Cluj-Napoca, for more details click the poster or go to http://flexcluj.eventsbot.com/ and register. See you there

12Apr/090

How To – Center a image on a canvas in flex


This is the solution for the anoying behaivor of the image control into a canvas or hbox or whatever. If the image is set to be scaled to a maximum width and height and maintaining the aspect ration, it won't center itself even if it has the horizontalCenter set to "0". So...here's how it looks (top) and how it suppose to look (bottom)

canvas

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 
    <mx :Script>
        < ![CDATA[
 
                // Maximum  width
                private var mxw:Number = 500;
                // Maximum height
               private var mxh:Number = 250;
 
               public function onImageComplete(e:Event):void
               {
                var wthMaxRatio:Number = mxw / mxh;
 
                // no resize needed
                if (e.target.contentWidth <= mxw && e.target.contentHeight <= mxh)
                {
                    e.target.x = (mxw - e.target.contentWidth) / 2;
                    e.target.y = (mxh - e.target.contentHeight) / 2;
                }
                else
                {
                    var wthImgRatio:Number = e.target.contentWidth / e.target.contentHeight;
 
                    if (wthImgRatio > wthMaxRatio)
                    {
                        // will max out the width
                        e.target.width = mxw;
                        var imgHeight:Number = Math.round( (mxw / e.target.contentWidth) * e.target.contentHeight );
                        var newY:Number = Math.round( (mxh - imgHeight) / 2 );
                        e.target.x = 0;
                        e.target.y = newY;
                    }
                    else
                    {
                        // will max out the height
                        e.target.height = mxh;
                        var imgWidth:Number = Math.round( (mxh / e.target.contentHeight) * e.target.contentWidth );
                        var newX:Number = Math.round( (mxw - imgWidth) / 2 );
                        e.target.x = newX;
                        e.target.y = 0;
                    }
                }
              }
         ]]>
    </mx>
 
 
      <mx :Canvas width="500" height="250"
            verticalScrollPolicy="off" horizontalScrollPolicy="off"
            backgroundColor="#000000">
 
            <mx :Image id="theimage"
                maintainAspectRatio="true"
		scaleContent="true"
                complete="onImageComplete(event)" />
 
        </mx>

3Apr/090

Right click and custom context menu in Flash/Flex


Anyone know you can customize your flash context menu with ContextMenu class. But what if you want to really get rid of that and use your on context menu or just use the functionality of the right button of the mouse ?

The idea is fairly simple:

1 - Use Javascript in the HTML container page to disable the right-click on top of the SWF.
2 - Capture the event and pass it to a function that communicates with Flash via the External Interface
3 - In Actionscript the function called from Javascript does whatever you need to display your own custom context-menu.

Why would anyone want to do this?

Well, there are several very important reasons:

1. Games – the power of AS3 has brought Flash to the world of digital entertainment. At last it is possible to focus on the idea of your game rather than on how to improve the laggy experience. One thing that is still missing – right click functionality. We had this forever in desktop games, now it is time to let your casual RTS, RPG and FPS creations conquer the web.

2. User Experience – 2 buttons are better than 1. Every experimentalist's dream is to be able to have more input options, not just one button. I can bet someone would soon create a stunning interface using this new functionality

3. RIA – Rich Internet Applications. My clients are often asking if it is possible to remove embeded Flash Player menus from their applications and replace them with their company’s branding stuff.

And the answer is : YES! You can hack it to use custom right-click functionality in Flash and Flex.

Here is the demo and because you won't be able to right click it to View the Sources :) here they are

Javascript source code looks like this:

var RightClick = {
	init: function () {
		this.FlashObjectID = "RightClickDemo";
		this.FlashContainerID = "flashcontent";
		this.Cache = this.FlashObjectID;
		if(window.addEventListener){
			 window.addEventListener("mousedown", this.onGeckoMouse(), true);
			 document.oncontextmenu = function() { document.getElementById("RightClickDemo").rightClick(); }
		} else {
 
			document.getElementById(this.FlashContainerID).onmouseup = function() { document.getElementById(RightClick.FlashContainerID).releaseCapture(); }
			document.oncontextmenu = function(){ if(window.event.srcElement.id == RightClick.FlashObjectID) { return false; } else { RightClick.Cache = "nan"; }}
			document.getElementById(this.FlashContainerID).onmousedown = RightClick.onIEMouse;
		}
	},
	killEvents: function(eventObject) {
		if(eventObject) {
			if (eventObject.stopPropagation) { eventObject.stopPropagation(); }
			if (eventObject.preventDefault) { eventObject.preventDefault(); }
			if (eventObject.preventCapture) { eventObject.preventCapture(); }
		    if (eventObject.preventBubble) { eventObject.preventBubble(); }
		}
	},
	onGeckoMouse: function(ev) {
		return function(ev) {
		if (ev.button != 0) {
			RightClick.killEvents(ev);
			if(ev.target.id == RightClick.FlashObjectID && RightClick.Cache == RightClick.FlashObjectID) {
                document.getElementById(RightClick.FlashObjectID).rightClick();
			}
			RightClick.Cache = ev.target.id;
		}
	  }
	},
	onIEMouse: function() {
        // stupid ie fix
        if (document.getElementById(RightClick.FlashObjectID + 'x'))
            document.getElementById(RightClick.FlashObjectID + 'x').id =  RightClick.FlashObjectID;
 
		if (event.button> 1) {
			if(window.event.srcElement.id == RightClick.FlashObjectID && RightClick.Cache == RightClick.FlashObjectID) {
				RightClick.call();
			}
			document.getElementById(RightClick.FlashContainerID).setCapture();
			if(window.event.srcElement.id)
			RightClick.Cache = window.event.srcElement.id;
		}
	},
	call: function() {
		document.getElementById(RightClick.FlashObjectID).rightClick();
	}
}

On the Flash side is as simple as this code (AS3):

private function init() : void
{
    ExternalInterface.addCallback("rightClick", onRightClick);
}
 
private function onRightClick():void
{
    var mx:int = stage.mouseX;
    var my:int = stage.mouseY;
 
    if(my > 0 && my < stage.stageHeight && mx > 0 && mx < stage.stageWidth)
    {
        // show a custom context menu or do someting here
    }

On Opera this will not work, the browser forces the context menu to appear and blocks mouse events by default.

Few things you shouldn't forget to make this work

- 2 extra parameters you have to add to the flash object in your html
menu="false"
wmode="opaque"

- add to the body onload event RightClick.init(); function

Note: If you download the sources, you'll see in the html that the "object" tag has an extra 'x' character in the id. that's important to make it work in ie.