This blog isn't maintained anymore. Check out my current project, an agile/scrum management tool.

Thursday, February 28, 2008

ArrayCollection weirdness

I ran into a head-scratcher today...

How can this code:

var index:Number = rows.getItemIndex(partition.placeholderRow );
trace( index + " " + (rows.getItemAt(0) === partition.placeholderRow ) );

give this output?

-1 true

rows is an ArrayCollection with one element. partition.placeholderRow exists and is a valid object. I haven't mucked around with rows.source at all.

Answer follows in comment (so you don't peek ahead and cheat!)

Labels:

Tuesday, February 26, 2008

Been a crazy couple AIR days

I knew about the AIR release for a few weeks now, but never hadn't had the chance to update AgileAgenda for the new runtime by the release yesterday morning.

Boy am I sorry I didn't. I went to work yesterday and my mailbox was clogged with comments about AgileAgenda and notifications from Google Checkout about purchases. WAY more than any other day.

When I got home I saw that AgileAgenda was featured on the AIR marketplace again, and I chalked it up to that. So I scrambled to get a new release out as quick as possible. Without it being updated for the 1.0 release, I have to imagine a lot of people who tried ended up failing to install. I really wish I had stayed up on Sunday to get that release done, I bet a lot more people would have gotten a look at the app. Oh well, live and learn.

So then today I noticed that AgileAgenda wasn't featured in the marketplace anymore, but the traffic hadn't died down. I was perplexed. All the referrers in the logs were from the marketplace, but AgileAgenda wasn't even on the first page anymore so it didn't make sense to me. Then I got a helpful email from a helpful Adobe employee. It seems AgileAgenda is featured right smack in the middle of the thank you page whenever someone downloads AIR! And of course that leads to the marketplace link, which in turn leads to the download.



How cool is that!?!

So anyways, I'm really motivated to work on the project again.  I've been spending a lot of time on the AgileTracker (Previously called the Dev Client) which gives each resource working on the project a time tracking / task list / time sheet interface that can subscribe to AgileAgenda schedules.



It's been fun working on that since I've been using the Parsley framework which gives a nice IoC container and an MVC architecture to boot.  I like learning new things.   I'll be publishing some work I've done with that related to loading and management of assets through the IoC container that might be useful to a lot of people.

But everyone should definitely check out Parsley.  The author, Jens Halm, has been very helpful on the forums over there.




#ifdef

EDIT: Ignore this, they got it. See the comments. Learn something new every day I guess. Don't I feel like a fool now :)

I have a new #1 feature request for Actionscript. (Actually, I guess it's a request for the compiler toolset)

Please add in #ifdef support, even if just for a predefined set of conditions.

#ifdef AIR
#ifdef NOT_AIR

Trying to dual-purpose code for web and AIR applications would be so much simpler with these constructs.

Last night I was adding a popup menu item to an app.

var menu:NativeMenu = new NativeMenu();

The app that I had worked so hard to completely abstract the AIR vs. the universal support was just AIRified. I ended up spending 20 minutes refactoring the code, but it would have been sooo nice to just #ifdef that out.

Wednesday, February 13, 2008

Pulse Particles + BitmapData

Did a little work with getting the Pulse Particle system working with a BitmapData object.  Now you can do fun things like get particle trails, apply blur filters, etc.  Below are two examples of using that.




This first example shows a very simple particle system with some particle trails.  We're also using a new rule called the "ColorTransformRule" which causes a color transformation to happen over time.




package
{
import com.roguedevelopment.pulse.PulseEngine;
import com.roguedevelopment.pulse.emitter.BitmapEmitter;
import com.roguedevelopment.pulse.simple.SimpleParticles;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageScaleMode;

[SWF(backgroundColor="#000000", frameRate="30", width="500", height="350")]
public class BitmapExample extends Sprite
{
[Embed(source="example_assets/spark.png")]
protected var spark:Class;


public function BitmapExample()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
super();
PulseEngine.instance.root = this;
var bmd:BitmapData = new BitmapData( 500, 350, false, 0 );
var bm:Bitmap = new Bitmap(bmd);
addChild(bm);
var emitter:BitmapEmitter = new BitmapEmitter(20, bmd );
SimpleParticles.configureEmitterByObject(emitter, {bound:[0,0,500,350],colorTransform:[300,3000,0,0,0,200,200,0], pps:23,x:250, y:40, width:1, height:1,image:spark, movement:true, minSpeed:80, maxSpeed:100, minAngle:0, maxAngle:360, minScale:0.4, maxScale:0.75, gravity:6, fade:2350, lifespan:2340} );
}
}
}





The second example takes the StarPower example from before and adds the same sort of effect.

Source code follows...






/*
This examples creates three particle emitters.

Each of those emitters is on a container sprite which is not in the display list.

We have an ENTER_FRAME event handler which copies the image from those containers to a BitmapData object which is displayed on screen. Every
frame we also apply a color transformation to cause previous draws to fade out. This leaves a pleasing trail.

Finally, we use Tweener to rotate, scale, and position the containers randomly giving some fairly interesting visual behavior.
*/
package
{
import caurina.transitions.Tweener;

import com.roguedevelopment.pulse.emitter.GenericEmitter;
import com.roguedevelopment.pulse.simple.SimpleParticles;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.filters.BitmapFilter;
import flash.filters.ColorMatrixFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.Timer;

[SWF(backgroundColor="#000000", frameRate="30", width="500", height="350")]
public class StarPower extends Sprite
{
protected var containers:Array = [];
protected var timer:Timer;
protected var ease:int = 0;

[Embed(source="example_assets/star.png")]
protected var star:Class;
[Embed(source="example_assets/spark.png")]
protected var spark:Class;
[Embed(source="example_assets/snowflake.png")]
protected var snow:Class;

protected var bmd:BitmapData;
protected var bm:Bitmap;
protected var fullRect:Rectangle = new Rectangle(0,0,500,350);
protected var origin:Point = new Point(0,0);

protected var filterz:Array = [ //new BlurFilter( 1, 1, 1 ) ,
new ColorMatrixFilter( [ 1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0.85,0 ] ) ];


public function StarPower()
{
super();

addEmitter(star,10);
addEmitter(snow,5);
addEmitter(spark,15);

bmd = new BitmapData(500,350,false,0x000000);
bm = new Bitmap( bmd );
addChild(bm);

stage.scaleMode = "noScale";
onTimer(null);
timer = new Timer(5000);
timer.addEventListener(TimerEvent.TIMER, onTimer );
timer.start();
addEventListener(Event.ENTER_FRAME, onEnterFrame );
}



protected function onEnterFrame(event:Event) : void
{
// First, apply any filters we set up, in this example it's just the one color transform
for each (var filter:BitmapFilter in filterz )
{
bmd.applyFilter( bmd, fullRect, origin, filter );
}

// Then, for each of our particle containers, copy that to the bitmap with the appropriate transformations.
for each ( var container:Sprite in containers )
{
var m:Matrix = new Matrix();// container.x, container.y);
m.rotate( container.rotation * Math.PI / 180);
m.translate( container.x, container.y );
m.scale( container.scaleX, container.scaleY );
bmd.draw(container,m);
}
}

/**
* This creates a simple emitter that was configured using the particle explorer.
**/
protected function addEmitter( image:Class, pps:Number ) : void
{
var container:Sprite = new Sprite();
var emitter:GenericEmitter = SimpleParticles.createEmitter({pps:pps,x:100, image:image, y:100, width:1, height:1,size:15, color:2588900, movement:true, minSpeed:63.3, maxSpeed:353.6, minAngle:0, maxAngle:360, minScale:0.2, maxScale:1, lifespan:3140} ) as GenericEmitter;
emitter.root = container;
emitter.start();
containers.push(container);
}

// Every 5 seconds we cause the containers to go through a new random tweening
protected function onTimer(e:Event) : void
{
for each( var container:Sprite in containers )
{
animateContainer( container );
}
}

protected function animateContainer(container:Sprite) : void
{
var eases:Array = ["easeinoutsine","easeoutcirc","easeoutback","easeoutbounce","easeoutelastic"];
var scale:Number = Math.random() + 0.05;
ease++; ease %= 5;
Tweener.addTween( container, { y:Math.random() * 350, x:Math.random()*500, scaleX:scale, scaleY:scale, time:5, transition:eases[ease] });
Tweener.addTween(container, {rotation: Math.random() * 360, time:5, transition:eases[ease] }) ;
}
}
}

Monday, February 11, 2008

Pulse Particles - StopMovementRule


A new version of Pulse particles is out with a new rule "StopMovementRule".  This rule lets you set an area on screen.  When a particle enters that area, it will stop moving.  This is useful to have particles fall onto something and come to rest.


The particle explorer and Flash .mxp packages have also been updated.

Sunday, February 10, 2008

AIR Install Badge + Google Analytics


I use Google Analytics to track web usage.  It's a great tool and since I mainly use AdWords for advertising, it really fills my needs.

A big part of Analytics is "goal" tracking.  You set up a goal page, and Analytics will tell you all kinds of information about the users who hit that goal.  In the past I set up my general "Download" page as a goal.  But visiting the download page, and actually downloading the application are two very different things.  It would be a lot more valuable to track who actually downloads vs. who went to the download page.

My main product is an AIR application.  It uses an AIR badge installer to let people install the application.  

By modifying the install-badge code slightly, and adding a javascript function we can detect when someone clicks the install badge.  Furthermore, we can even track if they had to install the application + the AIR runtime, or just the application.  Here's how...

First, open up the badge.fla file that comes with the AIR SDK in Flash.

Next, open up the AIRBadge.as source file.  Find the "onButtonClicked" event handler and add in some ExternalInterface calls to report back to the webpage on what's clicked.




private function onButtonClicked(e:Event):void {
try {




switch (_air.getStatus()) {
case "installed" :
root.statusMessage.htmlText =
"<p align='center'><font color='#" +
_messageColor +
"'>Downloading... Click the 'Open' button when prompted.</font></p>";
_air.installApplication( _appURL, _airVersion );

try
{
ExternalInterface.call("badgeClicked","INSTALL_APP");
}
catch( e:Error ) {} // eat any errors to not interfere with the installation

break;
case "available" :
try
{
ExternalInterface.call("badgeClicked","INSTALL_AIR_APP");
}
catch( e:Error ) {} // eat any errors to not interfere with the installation

root.statusMessage.htmlText = "<p align='center'><font color='#" +
_messageColor +
"'>Downloading... Click the 'Open' button when prompted.</font></p>";
_air.installApplication( _appURL, _airVersion );
break;
case "unavailable" :
try
{
ExternalInterface.call("badgeClicked","INSTALL_FAIL");
}
catch( e:Error ) {} // eat any errors to not interfere with the installation

// do nothing
break;
}
} catch (e:Error) {
root.statusMessage.text = e.message;
}
/* clearInterval( _global.installIntId ); */
}

Notice that I also modified the message displayed to the user.   I always thought the message displayed was a bit confusing.

As you can see, we're calling a badgeClicked function with three different parameters depending on the status of the currently installed AIR runtime.  Now... over to the HTML for the download page we need to define that function.


function badgeClicked( clickType )
{
urchinTracker("/download/badge/" + clickType );
}

Assuming you already have the analytics code set up on the page, that's it!  
Now, when a user uses the download badge to install, you'll see an entry like one of these:

/download/badge/INSTALL_AIR_APP
/download/badge/INSTALL_APP
/download/badge/INSTALL_FAIL

in your google analytics, and you can track that like any "real" page view.

If you have multiple AIR badges throughout your site, you can modify your badgeClicked method to differentiate them.

A few things to make this work:
  • Make sure your allowscriptaccess is set to "always" in your flash embed code.
  • Make sure your regular analytics code runs before the user can click on the badge (just setting it up like normal will do)
You can see a full HTML example by viewing the source of this page.

I've zipped up my modified badge.fla and badge.swf if you want to download and use it just like my example without changes.

Friday, February 08, 2008

Are there any open source user management systems?

Blogs, CMS, bulletin boards, chat systems, social networks, most RIA's... they all have something in common.  Users.  Users need to sign up and log in.  Why is it that each system, even open source systems, end up implementing their own user management system?  I bet there's several million login form implementations out there by now, that's stupid.

Are there any open source stand alone user-management systems out there?  Something that does the basics and lets you build upon it.

I've seen plenty of user management systems integrated into other products, but they all seem tightly integrated into a much larger product.  I'd rather not start with a huge codebase (of potential security problems) and whittle it down.  I'd much prefer to have a simple, small system that handles things like:
  • Allowing users to sign up (email verification, catpcha support, configurable list of user details to require)
  • Assign various access levels (or attributes?) to users.
  • Allow users to log in / log out 
  • Detect multiple failed logins for a user or from a source host with configurable temporary lockouts
  • Provide a simple API to use in applications that build upon it to get login status & access level (preferably language-agnostic)
  • Mechanism for retrieval of forgotten passwords (email? security question(s)?, combination?)
  • Provide a simple html based UI to handle all of these functions (including administrative functions like approving, disabling, changing access, etc.).
  • Provide an XML-RPC based interface to perform all of the functions so it's easily customizable by application that build upon it.
Bonus points for 
  • A Java or PHP solution since that's what I'm generally working in :)
  • Time based subscriptions
  • Configurable database back end (MySql minimum)
  • Session inactivity timeouts
  • AS3 library
I've done a little searching, but haven't found a simple solution.  (Plenty of complex single-sign on solutions!)


Thursday, February 07, 2008

Agile Agenda update

Been a while since I wrote about AgileAgenda, so here's an update on some of the recent happenings. If you haven't heard of it before, AgileAgenda is an AIR application for project scheduling that I've been working on in my spare time for a few months now.

It's been a big couple weeks of development for me. I've fixed a bunch of bugs and improved a few of the outstanding usability problems. I got Basecamp integration working to a point where I'm ready to release after a bit more testing.

But the big news is the Dev Client has been completely revamped. I've also renaming it the "Agile Tracker" since not only "Developers" might be using it, but anyone who wishes to track their time on AgileAgenda tasks.

Using the AgileTracker you'll be able to subscribe to multiple schedules published to the AgileAgenda.com website. Once you're subscribed, you'll be able to track your time across projects or tasks, view how much time on the project you've spent, and view an overall timesheet for time spent week to week.

Here's a couple screenshots for you to wet your appetite with.





There's also been some work to help future proof the file format so new versions of the software will still be able to read schedules made with older versions of the software.


As we near a real release more and more attention is being focused on only releasing quality, well tested, builds. The changes mentioned above mean we have a huge chunk of code to test in the application, in the agile tracker, and in the server code. We're shooting for a new beta build in about two weeks depending on how our internal testing goes.