Category: Actionscript


Although I use AMFPHP RemoteObjects with the Cairngorm Framework everyday, I never had a need for a simple LocalConnection. LocalConnections let you communicate between running SWFs, the only problem is that they are unidirectional. SWF A can make a new LocalConnection to SWF B and invoke it’s methods, but SWF B can’t contact SWF A. The way to get around this is to make another LocalConnection back from SWF B to SWF A. Trying to wrap my head around receiving and sending connections was starting to make me angry! They are annoyingly misleading – the receiving SWF needs to .connect() to a named connection, whereas the sending SWF doesn’t – it just calls .send() with the same named connection. I figured I could do the world a favor and abstract this confusion for you.

Here is the result (demonstrated with a Flex SWF and a Flash SWF):

Flex SWF


Flash SWF



In both the Flex MXML and the Flash FLA, I am including my BiDirLocalConnection class and it does all the hard work for me.

To make this bidirectional concept easier to handle, I introduced something called Roles. There are two roles available to your SWFs: Master and Slave. You must pick one of these roles when you instantiate the BiDirLocalConnection Object. If you have more than one running SWF in a given Role you will get an error on the newest one.

Here’s an example of how to use the class in Flex:

import net.teratechnologies.common.BiDirLocalConnection;

private var connection:BiDirLocalConnection;

private function init():void{
    connection = new BiDirLocalConnection(BiDirLocalConnection.ROLE_MASTER,this);
    connection.connect();
}

The constructor takes three arguments (the third is optional):

BiDirLocalConnection(role:String,callbackScope:Object,connectionBaseName:String="BiDirConnection")

role: The role of this SWF (ROLE_MASTER or ROLE_SLAVE)
callbackScope: When a SWF connects to this one and tries to invoke a method (call a function), where should it look to find the method? Normally you specify this so the remote SWF has access to the functions in your current scope.
connectionBaseName: This optional argument is only required if you have more than one BiDirLocalConnection at the same time. You can use any string here and it will serve as the prefix for the two LocalConnection connectionNames that are used.

Here is the complete code from the Slave in Flash CS3 as seen above. There are three things on the stage: TextArea (receiveText), TextInput (sendText) and a Button (sendButton):

import net.teratechnologies.common.BiDirLocalConnection;
           
var connection:BiDirLocalConnection;

function init():void{
    sendButton.addEventListener(MouseEvent.CLICK,send);
    connection = new BiDirLocalConnection(BiDirLocalConnection.ROLE_SLAVE,this);
    connection.connect();
}
function send(e:Event):void{
    connection.send('showText',sendText.text);
}
function showText(t:String):void{
    receiveText.text = "Received: "+t+"\n"+receiveText.text;
}

init();
stop();

Now here is the complete code for the Flex MXML file:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import net.teratechnologies.common.BiDirLocalConnection;
           
            private var connection:BiDirLocalConnection;
           
            private function init():void{
                sendButton.addEventListener(MouseEvent.CLICK,send);
                connection = new BiDirLocalConnection(BiDirLocalConnection.ROLE_MASTER,this);
                connection.connect();
            }
            private function send(e:Event):void{
                connection.send('showText',sendText.text);
            }
            public function showText(t:String):void{
                receiveText.text = "Received: "+t+"\n"+receiveText.text;
            }
           
        ]]>
    </mx:Script>
    <mx:Panel width="100%" height="100%" layout="absolute" title="Flex 2/3">
        <mx:TextArea id="receiveText" right="10" bottom="39" left="10" top="10."/>
        <mx:TextInput id="sendText" left="10" right="72" bottom="10"/>
        <mx:Button label="Send" id="sendButton" right="10" bottom="10"/>
    </mx:Panel>
   
</mx:Application>

It really is quite simple, the SWFs are calling each other’s “showText” functions with the text of the TextInput box as an argument. You can pass as many arguments as you want to connection.send(), and they don’t need to be simple Strings.

Finally, here is the ActionScript Code for the BiDirLocalConnection class itself:

package net.teratechnologies.common {
    import flash.net.LocalConnection;
   
    public class BiDirLocalConnection{
       
        public static const ROLE_MASTER:String = "master";
        public static const ROLE_SLAVE:String = "slave";
       
        private var txConnName:String;
        private var rxConnName:String;
        private var txLC:LocalConnection = new LocalConnection();
        private var rxLC:LocalConnection = new LocalConnection();
        private var callbackScope:Object;
       
        public function BiDirLocalConnection(role:String,callbackScope:Object,connectionBaseName:String="BiDirConnection"){
            if(role == ROLE_MASTER){
                txConnName = connectionBaseName + "_TX";
                rxConnName = connectionBaseName + "_RX";
            }else{
                rxConnName = connectionBaseName + "_TX";
                txConnName = connectionBaseName + "_RX";
            }
            this.callbackScope = callbackScope;
        }
        public function connect():void{
            trace(rxConnName);
            rxLC.connect(rxConnName);
            rxLC.client = callbackScope;
        }
        public function send(methodName:String,...rest):void{
            txLC.send(txConnName,methodName,rest);
        }
    }
}

You can also download the Flex 2/3 file, Flash FLA and Class file in a ZIP file.

After battling with Flex 2’s DataGrid ItemRenderer for a few hours, I have finally figured out how they really work.

The Scenario

Let’s say you want to have a DataGrid with a thumbnail in one of the cells. To do this you would assign the DataGridColumn an ItemRenderer. Here’s a quick and dirty MXML ItemRenderer that will work:

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" verticalScrollPolicy="off" horizontalScrollPolicy="off">
  <mx:Image id="thumbnail" width="25" height="25"/>
</HBox>

Now, when the DataGrid goes to render the cell the first time, it instantiates the ItemRenderer and sets the “data” property to the data of the current row in the DataGrid. Let’s say you have a property called “thumb_src” in the data. You could just bind the image’s source property to “data.thumb_src” and you probably wouldn’t have any problems, but if you wanted to do anything to the data before you use it you will run into trouble. The problem is that DataGrids only create as many ItemRenderers as are visible in the grid, so if you have 1000 rows in your DataGrid but you can only see 10, only 10 ItemRenderers will be created (more will be created on demand if you resize the DataGrid). Once a cell is scrolled off the top of the grid it is reused at the bottom (and visa-versa). When the ItemRenderer is reused, the DataGrid sets the data property with the new data and that’s it – it is not destroyed re-instantiated.

The Problem

All this becomes a major problem when you use the creationComplete event to setup some stuff on your renderer, since this event is only fired once – then renderer will be used and re-used to display the data from many different cells. When this problem occurs, you will see data in the ItemRenderer cells being thrown around seemingly at random when you scroll the DataGrid – it will probably drive you crazy!

The Solution

You need to setup / clear everything in the renderer when the data property is set. The easiest way to do this is to override the Container’s “data” setter function like this:

public override function set data(value:Object):void{
  // This will fire off the FlexEvent.DATA_CHANGE Event
  super.data = value;
  // if the value is null this cell is empty
  if(value == null){
    // clear all the controls
    return;
  }
  // set the controls with this data
}

You could also add an event listener for FlexEvent.DATA_CHANGE, which is fired off by the Container superclass.

Here’s a great example that I made that shows the problem. You can right click on the application and hit “View Source” to see the code, or go there directly.

Wow – so today I figured out (after yelling for a short while) that Flash’s Actionscript 2 does not make copies of objects, it makes references. If you don’t know the difference you probably won’t benefit from this article, but here’s an analogy anyway. Consider this code:

var date1 = new Date(2007,0,1); // January 1, 2007 00:00:00
var date2 = date1;
var date2.setMonth(1); // Febuary 1, 2007 00:00:00

If you’re anything like me you would expect “date1” to still be “January 1, 2007” and “date2” to be “Febuary 1, 2007” – NOPE, WRONG! Stupid Actionscript tries to save RAM by automatically making references, or “pointers” (or “nicknames”) to Objects instead of copying them – this is not a real bad idea for Actionscript 1 I guess – since it’s a n00b language, but I can extend dynamic classes in AS2 – it’s an OOP language, so it should behave like one! So, since there’s no character to make a reference (in PHP you can use $date2 =& $date1), you can’t not make it a reference. To fix this problem I searched the net a bit and ran across an Object prototype that adds a clone() method to it. Unfortunately the clone method didn’t work for Date object, so naturally I was forced to make it work :(. Well, here it is – just paste this code somewhere in the beginning of your FLA or in an AS file that is included:

/*
************************************************************
Object.clone
by  : Steve Kamerman (kamermans at teratechnologies.net)
Blog: http://www.teratechnologies.net/stevekamerman
Ver : 1.0
Date: 13Jan2007
************************************************************
Object.clone creates a perfect copy of an object
Modified from R.Arul Kumaran's version to support Date Objects
*/


Object.prototype.clone = function() {
  if (this instanceof Array) {
    var to = [];
    for (var i = 0; i<this.length; i++) {
      to[ i] = (typeof (this[ i]) == "object") ? this[ i].clone() : this[ i];
    }
  }else if(this instanceof Date){
    var to = new Date(this.getTime());
  }else if (this instanceof XML || this instanceof MovieClip) {
  // can't clone this so return null
  var to = null;
  trace("Object.clone won't work on MovieClip or XML");
  } else {
    var to = {};
    for (var i in this) {
      to[ i] = (typeof (this[i ]) == "object") ? this[ i].clone() : this[ i];
    }
  }
  return(to);
}
ASSetPropFlags(Object.prototype, ["clone"], 1);
/*
Usage:-
var date1 = new Date(2007,0,1); // January 1, 2007 00:00:00
var date2 = date1.clone();
var date2.setMonth(1);

// now date2 contains the Date Object for Febuary 1, 2007 00:00:00
// changing date2 will not affect date1
*/

I hope you like it!

UPDATE 25Feb2008 – Adobe has published the recommended workaround for this problem.

This is another major release – I rewrote some of the code with some inspiration from Zelph.com’s onDOMload as suggested by Geoff Stearns, Author of SWFObject. I have now optimized the code to the point where all you need to do is include it in the head of your document and as the page loads, each object will be fixed, so by the time the page is done loading, everything is fixed automatically!

———EDIT 19Jan2007———
I found yet another IE bug related to this topic. After a page is cached by IE and reloaded, the SWF is loaded before it’s embedded, so any callback functions the the SWF tries to setup when it loads (like the JS->Flash ExternalInterface code) will fail with an error “objectID” is undefined. Then when you try to use the callback function you get Object doesn’t support this property or method because the functions didn’t get assigned to the Flash object properly.

To fix this error you need to put this line above your SWFObject code (or above your <object> tag):

window["objectid"] = new Object();

Here’s an example document:
http://devel.teratechnologies.net/swfformfix/extinterface-swfformfix2.php

You can download SWFFormFix2 Here:
http://devel.teratechnologies.net/swfformfix/swfformfix2.js

You can see the nicely formatted and highlighted source code here:
http://devel.teratechnologies.net/swfformfix/swfformfix2.js.source

Here’s a great example of bi-directional communication with Flash / Actionscript and Javascript inside an HTML form using SWFObject with my SWFFormFix patch.

http://devel.teratechnologies.net/swfformfix/extinterfaceexample.php

Unlike my previous example pages, this one includes the source code for the Flash movie (the FLA). Let me know if you like it!

I have patched the newest version of SWFObject (1.5) to include FlashFormFix 1.0.0. If you use this version in place of the standard SWFObject it will automatically apply SWFFormFix (bugfix for ExternalInterface() in a Form with IE) when you use it – this means all you have to do is replace your standard swfobject.js with the new swfobject_swfformfix.js and you’re done!

Here’s the optimized version:
http://devel.teratechnologies.net/swfformfix/swfobject_swfformfix.js
(to download it right click and “Save Link As” or something similar)

Here’s the human-readable version in case you want to see what I changed:
http://devel.teratechnologies.net/swfformfix/swfobject_swfformfix_source.js
(to download it right click and “Save Link As” or something similar)

I ran across a problem today where I wanted to generate random colors in Actionscript. Naturally my first step was to generate a random number for the red, green and blue between 0-255:

var red:Number = Math.random() * 255;
var green:Number = Math.random() * 255;
var blue:Number = Math.random() * 255;

Now I have each color, but I need to combine them so Flash can use it. You may or may not know this but Flash doesn’t care if you give a hexidecimal or decimal number as a color, for example:

var fmt:TextFormat = new TextFormat;</code>

// these 2 lines are identical
fmt.color = 0xFF0000; // HEX, aka 'base16', aka 'radix 16'
fmt.color = 16711680; // DEC, aka 'base10', aka 'radix 10'

Don’t belive me? Try this:

trace(16711680 == 0xFF0000); // true

That having been said – you still can’t specify the Red, Green and Blue in decimal form without combining them. To convert all three colors to one decimal number you need to look at a HEX color first:

RRGGBB <– two digits for each color.
…..10000 <– Red
………100 <– Green
………….1 <– Blue

The red is in the 10000’s place, the green is in the 100’s and the blue is in the 1’s place. In HEX you can multiply the Red by 10000, the Green by 100 and the Blue by 1 to get the end product; but we are in decimal. No problem – just convert the 10000,100,1 from HEX to DEC – I’ll save you some time: 0x10000 = 65536, 0x100 = 256, 0x1 = 1. That’s it! Just multiply each color by it’s number and you’ve got a Flash/Actionscript friendly number.

function rgb2col(red:Number,green:Number,blue:Number){
    // input:   red, green and blue DEC colors (i.e. 255,0,0)
    // returns: decimal color (i.e. 16711680)
    return((red * 65536) + (green * 256) + (blue));
}

If you would like to see the decimal color in the traditional hexidecimal form you can use this function I came up with to convert it for you:

function decColor2hex(color:Number){
    // input:   (Number) decimal color (i.e. 16711680)
    // returns: (String) hex color (i.e. 0xFF0000)
    colArr = color.toString(16).toUpperCase().split('');
    numChars = colArr.length;
    for(a=0;a<(6-numChars);a++){colArr.unshift("0");}
    return('0x' + colArr.join(''));
}

Putting it all together we have a simple, yet complete, random color generator:

function randColor(){
    var red:Number = Math.random() * 255;
    var green:Number = Math.random() * 255;
    var blue:Number = Math.random() * 255;
    return(rgb2col(red,green,blue));
}
function rgb2col(red,green,blue){
    return((red * 65536) + (green * 256) + (blue));
}

As an experiment I have created a full-featured drop down (slide down) menu in Actionscript – there are no movie clips, buttons, bitmaps, shapes – nothing. My library is empty! Check it out here The nice thing about this menu is that it does not have to stick out over the HTML since the submenu items appear under the menu item. I have tested it in IE, FireFox and Opera and it looks good.