Category: Flash / Flex


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.

I happened to be browsing around tonight and noticed that NVIDIA has been giving away 1 prize per day for a year! Here's the catch – you need to visit the site every 7 days to see if you won or your prize will go to the next guy! Stupid! To make matters worse, only way to see if you won is to use this annoying marquee on their site!

http://www.slizone.com/object/slizone_sli_giveaway.html

So naturally I decided to make everyone's life a little easier and pumped out a handly little Flex2 app that shows who won for the last week. It is updated in real time since I'm just grabbing the winners off the same RSS feed that's feeding the NVIDIA website. Check it out – it's cool, it's Flex, and it might just save your butt!

http://devel.teratechnologies.net/nvidia_winners/

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 just finished SWFFormFix 1.0.0 – a BIG update from the 0.1.0 prerelease! This new version introduces Auto Mode which takes care of everything for you! All you need to do is include the script in you and call SWFFormFixAuto() before the closing tag!

For more specific instructions or to download it go here:

http://devel.teratechnologies.net/swfformfix/swfformfix.js
(to download it right click and “Save Link As” or something similar)

This version also contains an updated version of the previous function “SWFFormFix()” that is more optimized and probably faster than the Auto version.

It seems every time I try something new I need to fix other people’s problems to get it to work, arrrrg! [:(] I decided to try use Flash 8’s ExternalInterface() class to communicate with the Javascript function on my page. I liked the sound of this method because I can pass an array of arguments with it and return a variable back from the Javascript to my Actionscript – all in one command! I whipped something up real quick and bam! it works! Then I moved the SWF into it’s destination: the middle of an HTML form – to my amazement it didn’t work! I was using Internet Explorer 6, so I tried Firefox 2.0 and it worked fine. It also worked fine in Firefox 1.5 (a little less compatible) and even Opera 9.00 Beta! Wow, seriously IE – you got beat by Opera Beta [:P]. It also failed in IE7. To make a long story short, I spent 6+ hours trying to figure out what was wrong – finally I realized the code was fine and it was a IE bug. There’s a bunch of speculation and suggested fixes on the Adobe / Macromedia Flash 8 ExternalInterface() LiveDocs page, but they’re either overly-complicated or they don’t work. So I sat down and figured it out, then I made my own fix, which I consider to be very easy :D.

Here’s the solution: SWFFormFix by me, Steve Kamerman 😀. Here’s the deal, basically, you need to trick Internet Explorer because if you put an object in a form, IE’s implementation of Javascript seems to want to look for “window.yourObject” and not “document.form[x].yourObject”, so my script figures out which form you are in and makes an alias at the wrong location that points to the right location. Don’t believe me? Try it out.

Here are some examples:
A SWF outside of a FORM (using SWFObject to embed)
A SWF inside of a FORM (using SWFObject to embed)
A SWF inside of a FORM (using std Flash EMBED tags)
A SWF inside of a FORM (using SWFObject and SWFFormFix)

As you should see, SWFFormFix fixes the problem and leaves you with zero errors – plus it’s free!

Download SWFFormFix:
http://devel.teratechnologies.net/swfformfix/swfformfix.js

Usage:
1. Include the swfformfix.js script in the head of your document

<html>
<head>
<script type="text/javascript" src="swfformfix.js"></script>
</head>

2. Run the SWFFormFix(yourObject) command on your SWF.

Example with SWFObject (recommended):

<div id="swfdiv_ttpreview" style="width:200px;height:100px;">
This is replaced by the Flash movie.
</div>
<script type="text/javascript">
// <![CDATA[
 var so = new SWFObject("myMovie.swf", "myMovieObjectName",
"200", "100", "6.0.0", "#ffffff");
 so.addParam("quality", "high");
 so.write("swfdiv_ttpreview");
 SWFFormFix("myMovieObjectName");
// ]]>
</script>

Example with std Flash EMBED tags:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
width="200" height="100" id="myMovieObjectName" align="middle">
<param name="movie" value="myMovie.swf" />
<param name="quality" value="high" />
<embed src="myMovie.swf" quality="high" width="200" height="100"
name="myMovieObjectName" type="application/x-shockwave-flash" />
</object>
<script type="text/javascript">
// <![CDATA[
 SWFFormFix("myMovieObjectName");
// ]]>
</script>

If you are still using the old school EMBED tags, you should really get with the program and start using SWFObject!! SWFObject (by Geoff Stearns) not only cleans up your code, but it removes the annoying gray box around your Flash movies in Internet Explorer 7 (or earlier with updates). I made a nice little script called HTML2SWFObject a couple months back that converts your old EMBED code to SWFObject code – check it out!

Have you ever seen the annoying gray box around Flash content on websites? If you have, you must have done the unthinkable and used Windows Update [:O]. To make a long story short, a company called Eolas Technologies got in a yelling match with Microsoft and the two companies have since tried to make life more difficult for each other. Microsoft decided to require Internet Explorer users to click once on EVERY ActiveX control (actually anything in an EMBED or OBJECT tag) as a safety precaution. You can read more about that battle here: Microsoft tweaks browser to avoid liability | CNET News.com. The solution to this problem is to embed the object in the page dynamically – after the page is loaded. This is exactly what SWFObject by deconcept does – and it does it well! SWFObject is also capable of Flash Player version detection in Javascript and has been used on some big name sites like YouTube.

Everytime I want to use SWFObject I am annoyed that I don’t remember the syntax, so I wrote a nice little script that will convert the <object>…</object> code that you get when you publish in Flash into SWFObject friendly code! The script is located here:

http://devel.teratechnologies.net/swfhelp/

As an example, it will convert this HTML code:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash
/swflash.cab#version=8,0,0,0" width="100" height="50" id="flashtab"

align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="flashtab.swf" />
<param name="quality" value="high" />
<param name="wmode" value="transparent" />
<param name="bgcolor" value="#ffffff" />
<embed src="flashtab.swf" quality="high" wmode="transparent"
bgcolor="#ffffff" width="100" height="50" name="flashtab"
align="middle" allowscriptaccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>

To this SWFObject code:

<div id="swfdiv_flashtab">  
This text is replaced by the Flash movie.  
</div>  
<script type="text/javascript">  
  var so = new SWFObject("flashtab.swf", "flashtab", "100", "50",
"8.0.0", "#ffffff");  
  so.addParam("allowscriptaccess", "samedomain");  
  so.addParam("quality", "high");  
  so.addParam("wmode", "transparent");  
  so.write("swfdiv_flashtab");  
</script>

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));
}