Sunday, May 31, 2009

colorizing text as you type


For a project I worked on, I needed an input TextTield that would colorize specific kinds of words as the user is typing them. For instance, if the user types "To @be or not to be, @that is the @question," I need the words starting with at-signs to turn red. And, as I said, I needed this to happen "live," as the user types. In other words, "@be" needs to turn red before the user types "or."



Here's the Regular Expression I used: /\B@[A-Za-z0-9]+/g

It starts by looking for a word boundary (\B). This is so it will match @be but not the @be in bumble@bee. More important, I don't want it to match email addresses, which have at-signs in the middle. My RegExp will only match words starting with at-signs.

Next in the RegExp, I added the literal @ and then [A-Za-z0-9]+, which will match any number of characters, as long they are letters or digits. Finally, I added the global switch (g), so that the RegExp would match all @words, not just the first one it finds.

Here's the code, followed by some notes:



var input : TextField;

input.addEventListener( Event.CHANGE, onTextChange);

function onTextChange( event : Event ) : void
{
        var tfSpecial : TextFormat = new TextFormat();
        var tfDefault : TextFormat = new TextFormat();
        var allText : String = input.text;
        var caretIndex : int = input.caretIndex;
        var startIndex : int = ( caretIndex - 100 < 0 ) ? 0 : caretIndex - 100;
        var localText : String = allText.substr( startIndex, caretIndex );
        var regExp     : RegExp = new RegExp( /\B@[^ ]+/g );
        var obj : Object = regExp.exec( localText );
        tfSpecial.color = 0xFF0000;
        tfSpecial.bold = true;
        tfDefault.color = 0x000000;
        tfDefault.bold = false;
                
        input.setTextFormat( tfDefault, allText.length-1, allText.length  );

        
        while( obj != null )
        {
                input.setTextFormat( tfSpecial, obj[ "index" ],  obj[ "index" ] + String( obj ).length );
                obj = regExp.exec( allText );
        }
}



NOTES:

All those statements that reference "carretIndex" grab a chunk of the text, starting 100 characters before the cursor position and running to the cursor position. (Unless there's less than 100 characters of text, in which case the chunk is from the beginning to the cursor.) I do this because if the text gets long, I don't want -- or need -- to check all of it. 100 characters is probably overkill, but it allows the user to type @supercalifragilisticexpialidocious if he so chooses.

In this statement...

input.setTextFormat( tfDefault, allText.length-1, allText.length );

...I make sure the text after the cursor changes back to its default black, so that non @words won't be red.

Finally, I loop through all the RegExp matches and colorizes them.

Friday, May 29, 2009

my replacement for trace()

When debugging a Flash app online, it always sucks that you can't use trace(). There are all sorts of tricks to get around this, including the great DeMonster Debugger.

As much as I like such tools, I've always wished for something simpler. Something like trace() -- but a trace() that works in the browser. Recently, my wish was granted. I learned that Firebug's console accepts logs from Flash. (Firebug is a must-have Firefox plugin for web developers.)

Firebug has a Javascript API which contains a function called console.log. That's great, because Flash can call JS functions, via ExternalInterface.call(functionName,param[s]).

So, all you have to do to send a message from Flash to Firebug is...

1. make sure that the allowScriptAccess parameter is set to "always" in the html page's object and embed tags (it's often set to "sameDomain" by default).

2. In the Actionscript, import Flash.external.ExternalInterface (works in AS2 and AS3).

3. When you want to "trace" to the console, type something like this: ExternalInterface.call( "console.log", "hello, world" );

That's great, but I don't want have to type something that long every time I want to run a quick trace-like action. So I made this little utility function (below). Feel free to use it. Just...

1. Copy it into the folder that contains the fla.

2. Make sure allowScriptAccess is set to "always" in the HTML's object and embed tags.

3. When you want to "trace," just call log(param1, param2, ... paramN);

The cool thing is that log() calls trace() internally, so you can use it for both local development and in-browser testing.

NOTE: with one angry client of mine, the ExternalInterface.call() method cased JavaScript errors on their site. So before going live, I recommend you comment out the final line in the log() function and recompile.

package 
{
    import flash.external.ExternalInterface;

    public function log( message : *, ... rest ) : void
    {
        var s : String = "";
                var a : Array;
                
                a = rest as Array;
                
                a.unshift( message );
        
        trace.apply( this, a );
        
        for ( var i : int = 0; i < a.length; i++ )
        {
                s += a[ i ].toString() + " ";
        }
        
        ExternalInterface.call( "console.log" , s );
    }
}





There are a few of things worth noting about this function: first of all, it's in a package by itself -- it's not part of a class. Normally, I think of that as a no-no, but in this one case, I let myself do it. I want to be able to type just log(), not Debug.log() or something like that. But if the stand-alone function offends you, just pop it into a class.

log() accepts an arbitrary number of parameters, via the relatively obscure structure ...rest. So you can call it like this log("hello"); like this: log("hello", true); like this: log("hello", true, 45.6, [1,2,3], mySprite.x); etc.

In the above examples, "hello" would be stored in the message parameter and the rest of the parameters would be stored in rest. rest is an Array-like object, but it's not actually an Array. That's a problem, because the Function.apply() method, which I use to call trace(), needs an Array. That why I create the a Array and set it to rest as Array(). Then I push message onto the beginning of it, so that all the parameters are in one big Array.

That allows me to call trace.apply( this, a ). The Function.apply method isn't very well known. It allows you to call a function and use an Array as that function's list of parameters. For instance, the following two snippets have the same effect:

SNIPPET 1:
trace(1, 2, 3);

SNIPPET 2:
var a : Array = [1, 2, 3];
trace.apply( this, a );

(The first parameter to apply allows you to apply the function to a specific object. It's not important here, so I just used "this.")

Finally, for Firebug's console, I concatenate all the a values into a space-separated string and output them using the Console.log JS function. If you comment out the last line, log() will still work for local traces but it will no longer try to call a JS function on the web page housing the swf.

grab just the filename without the extension

var filename1: String = "monster.jpg";
var filename2: String = "big.fat.hippo.flv";

filename1 = filename1.replace( new RegExp( "(.*)\.(.*)", "g" ), "$1" );
filename2 = filename2.replace( new RegExp( "(.*)\.(.*)", "g" ), "$1" );

trace(filename1); //monster
trace(filename2); //big.fat.hippo

Constrain Proportions

Here's a really simple solution that I couldn't figure out for a long time: say you need to downsize an image so that the width changes to 80 pixels. You want to change the height, too, so that the image stays in proportion, but by how much should you change it?

I actually figured out a complex way to calculate the height, but then it hit me that there's a really simple way to do it:

myImage.width = 80;
myImage.scaleY = myImage.scaleX;

Dumb Bugs

Hi. Welcome to Grumbleblog.

I'm a programmer, and I currently code mostly in Actionscript 3.0, Flash's language (a sister tongue to JavaScript). In this blog, I will post info about Actionscript, other languages, and various technical topics that are too arcane for my regular blog.

To start the ball rolling, here's a list of dumb mistakes I frequently make when I'm coding. Though I'll come back here and update the list (as I catch more mistakes), the list will probably only useful to me.

However, I recommend that you make a similar list for yourself. It's useful when you're stuck. Before you start pounding your head against the wall, take a deep breath and check your list.

MY COMMON BUGS

- typing function when I mean var and vice versa.
EXAMPLE: public var killMyself() : void...

- omitting the underscore prefix on private variables (my convention).
EXAMPLE: private var highscore instead of private var _highscore

- forgetting to make classes public (when they should be).
EXAMPLE: class ISuck... instead of public class ISuck...

- messing up close curly braces.
EXAMPLE: if (true) { while(true) { trace(true) } //missing a close }

- omitting the word "function."
EXAMPLE: private addTwo()... instead of private function addTwo()...

- omitting a var's type.
EXAMPLE: private var name = "Bob" instead of private var name : String = "Bob";

- leaving off var in for loops.
Example: for ( i : int = ... instead of for ( var i : int =...
NOTE: I'm especially prone to this with for-in loops.
NOTE: I also sometimes leave off the type:
for (var i = 0... instead of (var i : int = 0...

- forgetting to call new on classes that are sub objects:
EXAMPLE: o.point = {x:10,y:20}; instead of...
o.point = new Point();
o.point = {x:10,y:20};

- adding an event listener to a Loader to test for load completion. I should be attaching it to loaderInstance.contentLoaderInfo instead.