Nested Functions

January 11th, 2010

As most Flash developers are aware Actionscript 3 supports nested functions. This essentially allows you to have functions within functions… within functions etc. This gives rise to an interesting set of opportunities and pitfalls.

This post won’t go into the pitfalls which are basically variable scope and garbage collection, there are plenty of posts on the net that focus on these two topics, google can help you find them. The purpose of this post is to show how clever use of nested functions can simplify your code.

To illustrate appropriate use of nested functions I am going to use the following example. We have the following data structure:


var data:Array = [
{
image:"img01.png",
link:"http://site1.com"
},
{
image:"img02.png",
link:"http://site2.com"
},
{
image:"img03.png",
link:"http://site3.com"
},
{
image:"img04.png",
link:"http://site4.com"
}
];

Our objective is to load the images, place them on the stage and when the user clicks on them to navigate to the url related to that image. We are also going to assume that we have a nice Image.as class that we have written. This Image.as class extends sprite and takes a single “url” parameter to its constructor to tell it what to load.

Using nested functions this can be done in a few tidy lines of code.


package {

import com.roddeh.media.Image;

import flash.display.Sprite;
import flash.display.MouseEvent;
import flash.net.*;

public class NestDemo extends Sprite{

public function NestDemo(){
var data:Array = [
{
image:"img01.png",
link:"http://site1.com"
},
{
image:"img02.png",
link:"http://site2.com"
},
{
image:"img03.png",
link:"http://site3.com"
},
{
image:"img04.png",
link:"http://site4.com"
}
]
drawImages(data);
}

private function drawImages(data:Array):void{
data.forEach(drawImage);

function drawImage(imgData:*, index:int, arr:Array):void{
var img:Image = new Image(imgData.image);
img.addEventListener(MouseEvent.CLICK, function(event:MouseEvent):void{
navigateToURL(new URLRequest(imgData.link), “_blank”);
});
addChild(img);
}
}
}
}

Ok, so you might be thinking “Yeah, that is one way of doing it, but what is the advantage?”. So, here are the advantages IMO:

1. It creates a more concise interface within your classes. When somebody scans over my code they will see a constructor and a single private function named “drawImages”. How the “drawImages” method works may well be irrelevant to them. All, they need to know is that it somehow draws the images based on a data structure. If I had built the same functionality without nested functions then the reader of my code would have had three methods to deal with “drawImages”, “drawImage” and “handleImageClick”.

2. The code does not jump around as much. Something that often irritates me when reading other people’s code is having to scroll up and down a class to work out what is going on. Very often you will see events being handled in a separately declared private function within which there is only a single line of code. Nested functions allow you to neatly package the result of the event with the code that adds the listener.

3. But most importantly, the alternative would have been really ugly. To illustrate how ugly I am going to pseudo-implement the alternative.


package {

import com.roddeh.media.Image;

import flash.display.Sprite;
import flash.display.MouseEvent;
import flash.net.*;

public class NestDemo extends Sprite{

public function NestDemo(){
var data:Array = [
{
image:"img01.png",
link:"http://site1.com"
},
{
image:"img02.png",
link:"http://site2.com"
},
{
image:"img03.png",
link:"http://site3.com"
},
{
image:"img04.png",
link:"http://site4.com"
}
]
drawImages(data);
}

private function drawImages(data:Array):void{
data.forEach(drawImage);
}

private function drawImage(imgData:*, index:int, arr:Array):void{
var img:Image = new Image(imgData.image);
img.addEventListener(MouseEvent.CLICK, handleImageClick);
addChild(img);
}

private function handleImageClick(event:MouseEvent):void{
navigateToURL(new URLRequest(
// Oh s***, what was that link again…
), “_blank”);
}
}
}

So, you may notice that I am actually kinda screwed with the above implementation because I have lost the reference to “imgData” which is what held the relationship between the image I have clicked on and the link to navigate to.

There are of course a number of ways to solve this.

If I can assume that the “Image” class has a “url” property then I could use the “event.target” (which would reference the image), then loop through the “data” array until I found the image that matched. However, I would then also need to store the “data” array in a private variable. Furthermore, I would be assuming that the same image is not being used twice with different links. Assumptions when coding will f*** you up before long :)

Alternatively I could have written a wrapper class around “Image” or perhaps extended “Image” and called my new class something like “LinkableImage” which takes two parameters: “url” and “link”. But personally I find superfluous classes even more offensive than superfluous functions within a class. Instead of scrolling up and down a class I now have to jump between classes just to work out something really simple.

—–

In conclusion, nested functions when used carefully can really make your life easier. Be aware of garbage collection when using them in conjunction with event listeners but do not be afraid of them.

AS * Factory

November 1st, 2009

Too often do I find myself writing code that looks something like the following.

var spr:Sprite = new Sprite();
spr.x = 20;
spr.y = 100;
spr.alpha = 0.5;
spr.rotation = 15;


Some programming languages like Python support “named parameters”. This can be emulated in Actionscript using the following technique. Ruby often uses a similar technique by having a single hash as a parameter to a constructor.

package com.roddeh.utils{

public class Factory{

public static function create(clazz:Class, properties:Object):*{
try{
var object:* = new clazz();
}
catch(e:Error){}
for(var ind:String in properties){
try{
object[ind] = properties[ind];
}
catch(e:Error){}
}
return object;
}
}
}


Which would then turn the first set of code into a nice one liner like so.

var spr:Sprite = Factory.create(Sprite, {x:20, y:100, alpha:0.5, rotation:15});

Telstra CEC

October 21st, 2009

Telstra CEC (Consumer Experience Centre) is a touchscreen kiosk application that is deployed to the Telstra T-Life stores throughout Australia.

The application is developed and maintained by C4. As an employee of C4 I was responsible for all of the flash development and was heavily involved in designing the webservices and data structures that support the application.

Screenshots of the application below.

Or if you are really keen you can check it out in one of the T-Life stores ;)

Dimensions

October 21st, 2009

Dimensions is a site that was built for a Malaysian TV show that is to be distributed through the Malaysian mobile phone network. The site was designed to present each of the TV episodes along with additional media content, character profiles etc.

The website was built in Flash and integrated with a .net Umbraco CMS that presented the data as JSON. As an employee of C4 I was responsible for developing the Flash front end and designing the data structure of the JSON.

The site can be viewed here if you are in Malaysia.

Otherwise you can view the screenshots below.

NRL Live Scoreboard

October 20th, 2009

NRL Live Scoreboard is a Flash app that gives in depth information of NRL games as they are played. The Flash app continuously loads XML files that provide details of the games being played including game statistics, player statistics, possession flow, video content and audio feeds.

As an employee of C4I was responsible for developing the Flash app.

The Live Scoreboard is released to the nrl.com (A site managed by C4) and can be viewed here.

Screenshots available below

McHugh Sites

October 17th, 2009

My first job at C4 was to work on the McHugh sites: a series of flash based websites for pubs around Sydney.  They connect to a C# .net CMS (Umbraco) using flash remoting and were built using Flex 3.

You can check the sites out here.

http://colombian.com.au/
http://shoreclub.com.au/
http://steynehotel.com.au/

Or a slideshow of screenshots here.

Roddeh CMS Released

September 8th, 2009

After months of development I am finally ready to release version 0.0.0 of Roddeh CMS.

WTF is Roddeh CMS?

It is a CMS that is built purely for use with Flash websites.

Why does the internet need another CMS?

Because there are no decent CMS solutions for flash websites and I am fed up with adapting other CMS solutions to service a flash website.

Tell me more…

Go here!

Snow Trip 2009

August 14th, 2009

A group of mates and I recently went to the snow for a weekend. Check out our snowboarding and skiing skills (or lack thereof) here.

Arty Party

April 20th, 2009

My flatmates and I recently held our “art” themed house warming. The idea was that the guests would help us decorate our new flat by painting canvases. Here are the results.


Overall it was a great night and our living room now features the works.

Actionscript Syntax Highlighter

March 9th, 2009

A syntax highlighter written in actionscript for actionscript.

Seeing I will be posting code snippets on this blog I thought it might be fun to write a syntax highlighter. I have never written anything like this before and I am sure there are be better ways to do it. However, for my purposes it is good enough :)

You can download the AS class here.

Or here it is highlighted below

Note: Care must be taken with the following characters > <

//
// CodeFormatter
//
// Created by Simon Rodwell on 2008-11-19.
//

package com.roddeh.text{

public class CodeFormatter{

//==================================================================
// PUBLIC PROPERTIES

public static var defaultColour:String = "#EEEEEE";
public static var keywordColour:String = "#FBDE2C";
public static var commentColour:String = "#AEAEA9";
public static var quoteColour:String = "#5BB231";
public static var typeColour:String = "#779ECE";
public static var constantColour:String = "#D14DEE";

//==================================================================
// PROTECTED PROPERTIES

//==================================================================
// PRIVATE PROPERTIES

private static const KEYWORDS:Array = [
"dynamic",
"final",
"internal",
"native",
"override",
"private",
"protected",
"public",
"static",
"class",
"const",
"extends",
"function",
"get",
"implements",
"interface",
"namespace",
"package",
"set",
"var",
"include",
"import",
"false",
"null",
"this",
"true",
"break",
"case",
"continue",
"do",
"while",
"else",
"for",
"each",
"in",
"if",
"label",
"return",
"super",
"switch",
"try",
"catch",
"finally",
"throw",
"with",
":",
"="
];

private static const TYPES:Array = [
"Number",
"String",
"Boolean",
"int",
]

private static const BREAK_CHARS:Array = [
" ",
" ",
"(",
")",
":",
";",
"+",
"-",
"*",
"/",
"%",
"n"
]

//==================================================================
// CONSTRUCTOR

public function CodeFormatter(){

}

//==================================================================
// PUBLIC METHODS

public static function format(code:String, colours:Object = null):String{
if(colours){
for(var i:String in colours){
try{
CodeFormatter[i] = colours[i];
}
catch(e:Error){};
}
}

var coloured:String = “”;
var counter:int = 0;
var quoting:Boolean = false;
var openQuoteChar:String;
var commenting:Boolean = false;
var lineCommenting:Boolean = false;
var c:String;
var chunk:String = “”;
while(counter < code.length){
c = code.charAt(counter);

// Check to see if we are quoting.
if(quoting){
chunk += c;
if((c == “"” || c == “‘”) && c == openQuoteChar){
quoting = false;
coloured += setTextColour(chunk, quoteColour, false);
chunk = “”;
}
counter++;
continue;
}

// Check to see if we are commenting
if(commenting){
chunk += c;
if(c == “*” && code.charAt(counter + 1) == “/”){
commenting = false;
chunk += “/”;
coloured += setTextColour(chunk, commentColour, false);
chunk = “”;
counter++; // Increment a second time to allow for the closing comment
}
counter++;
continue;
}

// Check to see if we are line commenting.
if(lineCommenting){
chunk += c;
if(c.charCodeAt(0) == 13){
lineCommenting = false;
coloured += setTextColour(chunk, commentColour, false);
chunk = “”;
}
counter ++
continue;
}

// Check to see if we need to start quoting.
if(c == “"” || c == “‘”){
if(!commenting && !lineCommenting){
quoting = true;
openQuoteChar = c;
coloured += colourText(chunk);
chunk = c;
counter++;
continue;
}
}

// Check to see if we need to start commenting.
if(c == “/”){
if(code.charAt(counter + 1) == “/”){
lineCommenting = true;
coloured += colourText(chunk);
chunk += c;
counter++
continue;
}
if(code.charAt(counter + 1) == “*”){
commenting = true;
coloured += colourText(chunk);
chunk = c + “*”;
counter += 2;
continue;
}
}

// Otherwise we are writing normal code.
if(BREAK_CHARS.indexOf(c) != - 1){
coloured += colourText(chunk);
coloured += colourText(c);
chunk = “”;
counter++;
continue;
}

chunk += c;
counter++;
}

if(chunk != “”){
coloured += colourText(chunk);
}

function colourText(text:String):String{
if(KEYWORDS.indexOf(text) != - 1){
return setTextColour(text, keywordColour, false);
}
if(TYPES.indexOf(text) != - 1){
return setTextColour(text, typeColour, false);
}
if(isConstant(text)){
return setTextColour(text, constantColour, false);
}
return text;
}

// TODO: I am sure this could be rewritten more robustly as a RegExp
function isConstant(t:String):Boolean{
var i:int = 0;
while(i < t.length){
var charCode:int = t.charCodeAt(i);
if(!((charCode &gts 47 && charCode < 58) || (charCode > 64 && charCode < 91) || charCode == 46 || charCode == 95)){
return false;
}
i++
}
return true;
}

// Colour everything else to the default text colour.
coloured = setTextColour(coloured, defaultColour, false);
// Replace tabs with 4 spaces.
return coloured.replace(/t/g, ” “);
}

//==================================================================
// PROTECTED METHODS

//==================================================================
// PRIVATE METHODS

private static function setTextColour(text:String, colour:int, stripInner:Boolean = true):String{
if(stripInner){
text = stripInnerColour(text);
}
return “<font color="” + getHTMLColour(colour) + “">” + text + “</font>”;
}

private static function stripInnerColour(text:String):String{
var reg:RegExp
reg = /<fontb[^>]*>/g
text = text.replace(reg, “”);
reg = /</font>/g;
text = text.replace(reg, “”);
return text;
}

private static function stripWhite(text:String):String{
var reg:RegExp = /^[ t]+|[t]+$/
return text.replace(reg, “”);
}

private function getHTMLColour(color:int):String{
var col:Colour = new Colour(color);

function getHexString(hex:int):String{
return getHexChar(Math.floor(hex / 0×10)) + getHexChar(hex % 0×10);
}

function getHexChar(hex:int):String{
if(hex < 10){
return String(hex);
}
else{
switch(hex){
case 10: return “A”;
case 11: return “B”;
case 12: return “C”;
case 13: return “D”;
case 14: return “E”;
case 15: return “F”;
}
}
return String(hex);
}

return “#” + getHexString(col.red) + getHexString(col.green) + getHexString(col.blue);
}

//==================================================================
// SET METHODS

//==================================================================
// GET METHODS

}
}