Monday, March 24, 2008

AIR / Browser API example

AIR / Browser API example

The other day I blogged about a wrapper class that I wrote to handle the AIR / Browser integration API.  Below is an example of another class that I wrote that uses that wrapper class.  It's capable of detecting two different AIR applications, installing them, and running them.  It relies on the wrapper class to do all the heavy lifting, but I thought this might be a good example to help people get started.

package com.agileagenda.web
{

import com.roguedevelopment.air.AIRBrowserRuntime;

import com.roguedevelopment.air.AIRBrowserRuntimeEvent;

import flash.events.IOErrorEvent;

public class InstalledApplications

{

public static function get instance() : InstalledApplications

{

if( _instance == null ) { _instance = new InstalledApplications(); }

return _instance;

}

protected static var _instance:InstalledApplications = null;

protected var api:AIRBrowserRuntime;  

protected static const PUBLISHER_ID:String = "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1";

protected static const TRACKER_ID:String =  "com.agileagenda.AgileTracker";

protected static const MAIN_APP_ID:String = "com.agileagenda.AgileAgenda";


protected static const AGILE_AGENDA_INSTALL:String =  "http://www.agileagenda.com/download/AgileAgenda.air";

protected static const AGILE_TRACKER_INSTALL:String = "http://www.agileagenda.com/download/AgileTracker.air";


[Bindablepublic var isReady:Boolean=false;

[Bindablepublic var hasAIR:Boolean;

[Bindablepublic var agileAgendaVersion:String = "";

[Bindablepublic var agileTrackerVersion:String = "";

public function InstalledApplications()

{

  api = new AIRBrowserRuntime();

  api.addEventListener(AIRBrowserRuntimeEvent.READY, onReady );

  api.addEventListener(IOErrorEvent.IO_ERROR, onAirFail );

  api.load();

}


protected function onAirFail(event:IOErrorEvent) : void

{

  trace( event.text );

}

protected function onReady(event:AIRBrowserRuntimeEvent ) : void

{

  hasAIR = api.getStatus() == 'installed';

  isReady = true;

  api.addEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onTrackerVersionResult );

  api.getApplicationVersion( TRACKER_ID, PUBLISHER_ID );

}

protected function onTrackerVersionResult(event:AIRBrowserRuntimeEvent) : void

{

  trace("Tracker version installed: " + event.detectedVersion );

  agileTrackerVersion = event.detectedVersion

  api.removeEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onTrackerVersionResult );

  api.addEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onAppVersionResult );

  api.getApplicationVersion( MAIN_APP_ID, PUBLISHER_ID );

}

protected function onAppVersionResult(event:AIRBrowserRuntimeEvent ) : void

{

  trace("Application version installed: " + event.detectedVersion );

  agileAgendaVersion  = event.detectedVersion

  api.removeEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onAppVersionResult );

}

public function launchAgileAgenda( args:Array ) : void

{

  api.launchApplication( MAIN_APP_ID, PUBLISHER_ID, args );

}


public function launchAgileTracker( args:Array ) : void

{

  api.launchApplication( TRACKER_ID, PUBLISHER_ID, args );

}

public function installAgileAgenda(args:Array) : void

{

  api.installApplication(AGILE_AGENDA_INSTALL, "1.0", args );

}


public function installAgileTracker(args:Array) : void

{

  api.installApplication(AGILE_TRACKER_INSTALL, "1.0", args );

}


}

}

Labels: ,

Sunday, March 23, 2008

Interacting with an AIR app from a browser based app

Interacting with an AIR app from a browser based app
We've all seen the AIR installation badges that let you install an AIR application from a website. But the API exposed to do that lets you do more than just a simple badge. I've been working on a web-based service for AgileAgenda. One of the components of that is to manage the list of files you've saved to the service and be able to open those in the desktop AIR application. So right from within the online Flex based app it sure would be nice to detect if the application is installed, give the user the option to install, and then launch it for them and automatically open the desired file. Something like this...
To implement that we need to:
  1. Detect whether or not the application is installed.
  2. Display the version number if it is. (Disable the "Open schedule in..." button if it's not)
  3. When clicking on the "Open" link, launch the application with a few parameters so it know what to open.
  4. When clicking on the "Install" link, install the application and pass a few parameters so it know what to open when it launches directly after the install.
Doing things like that falls outside of the normal AS3 web development API. But Adobe provides a swf that you can load at runtime and them make calls to. You can read all about that over here: http://livedocs.adobe.com/air/1/devappshtml/help.html?content=distributing_apps_3.html.
But there's an annoying thing with that. You need to load up that remote swf and then access it like a dynamic object. No compiler-time type checking. Not the standard event mechanism. And no code-completion from within Flex Builder.
Luckily for you, I went through and made a wrapper class that you can download from here:
This will handle the loading of the remote swf, dispatching events when it loads (or fails to load) and then wraps the API for the entire process. There's nothing revolutionary in there, it's mostly takendirectly from that livedocs page above.
So now that you have that handy-dandy wrapper class, lets look at how to actually get something done.
Setting up your AIR application to work with your website (Important)
First thing, go into your AIR application's -app.xml file and make sure allowBrowserInvocation is set to true. By default it's set to false.

<allowBrowserInvocation>true</allowBrowserInvocation>

If you don't do this, you won't even be able to query version information on your application. But, be careful. By doing this you're letting any website launch your AIR application from a web page. You need to be careful in how much your app trusts command line arguments passed to it. For instance, you should never pass a file to delete on the command line.

Now that you've set allowBrowserInvocation to true, create a new .air file and post that to your website somewhere.

Using the wrapper class to interact with your application

Either open an existing Flex or Actionscript project in FlexBuilder and put the source to the AIRBrowserRuntime.as somewhere that the compiler will find it. Somewhere in your main application, create an AIRBrowserRuntime object, set some event listeners, and call the load() method to load the air.swf file from Adobe's servers.

var api:AIRBrowserRuntime;

...

api = new AIRBrowserRuntime();

api.addEventListener(AIRBrowserRuntimeEvent.READY, onReady );

// Optional: api.addEventListener(IOErrorEvent.IO_ERROR, onAirFail );

api.load();

Once the READY event is dispatched, you can start calling methods to query application versions, install apps, or launch applications.

Checking if AIR is installed

To see if the AIR runtime is installed, call the getStatus() method. It will return one of three values.

available - The AIR runtime can be installed on this computer but currently it is not installed

unavailable - The AIR runtime cannot be installed on this computer.

installed - The AIR runtime is installed on this computer.

Example:

switch( api.getStatus() )

{

case "available": trace("AIR is available, but not installed."); break;

case "unavailable": trace("AIR is not available for this computer."); break;

case "installed": trace("AIR is already installed on this computer."); break;

}

Checking the version of an installed application

The getApplicationVersion method will check to see if a given application is installed and give you the version if it is. This method operates asynchronously so you have to create an event listener before you call it. If you look at the method signature...

public function getApplicationVersion(applicationID:String, publisherID:String) : void

You'll see that it takes an applicationID and a publisherID. The applicationID is just value of the <id> tag of your application descriptor (the -app.xml file). It'll probably be something like this, but you need to make sure to make each application unique:

<id>com.agileagenda.AgileTracker</id>

The publisherID is a little trickier to find. That doesn't get assigned until you actually sign your .AIR file with your code-signing key. I know of 2 ways of finding it, but there's probably an easier way (please leave a comment if you know how).

Warning about Publisher ID:  If you change the key your sign your app with, the publisher ID will change.

Option 1: Get it at runtime.

In your application's main MXML throw up an Alert message with the publisher ID. Then build a .air, install the .air and run it. Copy & Paste the result.  Example:

<?xml version="1.0" encoding="utf-8"?>

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"

    creationComplete="init()" >

  <mx:Script>

  <![CDATA[

    protected function init() : void 

    {

      Alert.show( nativeApplication.publisherID);

    }

  ]]>

  </mx:Script>

</mx:WindowedApplication>

Which results in something resembling:

It's hard to see in that picture, but the publisher ID is: F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1 as it wraps to two lines.
Option 2: Get it after install
This is probably a bit easier, but I'm not 100% sure where this directory goes on a windows box.
On OSX (maybe on Windows, I'm not sure exactly where) the application storage directory that gets created for your application has the publisher ID appended to it. So if you look in your user directory -> Library -> Preferences, you should see a directory that starts with your application ID and ends with your publisher ID.
As you can see there, we get the same publisher ID value as Option #1.
On to checking installed version
Now that you have your application ID and your publisher ID you can make a call from your web application to the API to request the installed version of your AIR application.  This operates asynchronously so you'll need an event listener for the result.
Example:

api.addEventListener(AIRBrowserRuntimeEvent.APP_VERSION_RESULT, onTrackerVersionResult );

api.getApplicationVersion( "com.agileagenda.AgileTracker", "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1" );

...

protected function onTrackerVersionResult(event:AIRBrowserRuntimeEvent) : void

{

trace("Tracker version installed: " + event.detectedVersion );

}

The event.detectedVersion that comes back will be the value in your application descriptor version tag.  Mine looks like this:

<version>v1</version>

So the trace output looks like this:


Tracker version installed: v1

Launching an installed AIR application

Assuming you followed along in the previous section, you have an Application ID and a Publisher ID ready. To launch an AIR app from a web app you just call

api.launchApplication( "com.agileagenda.AgileTracker", "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1" );

launchApplication has a third, optional, parameter called arguments and is typed as an array. Anything you pass into that will be passed along to the application in an INVOKE event. This way you can pass information from the web application to the AIR application. Example:
api.launchApplication( "com.agileagenda.AgileTracker", "F49A4D8DF78A1FEE7A3BE440DC11BAB18D922274.1", ["Argument1","Argument2" ] );
Be careful, the arguments are very restrictive in what characters can be passed. Things like dashes, percent signs, underscores, all cause a runtime exception on the web application. You're probably safest only using alphanumeric characters. I haven't found a comprehensive list of what characters are or are not valid. If someone has that, please leave a comment below.  This was done for security reasons.
Installing an AIR application
To install an AIR application, all you need to do is call the installApplication with the absolute URL to the .air file you want to install. Example:
api.installApplication("http://www.agileagenda.com/download/AgileTracker.air");

This will take care of installing the AIR runtime, installing the application, and launching it for the first time. installApplication has two more optional parameters. The AIR runtime that the application requires, and arguments that can be passed to the application for it's first run. Example:

api.installApplication("http://www.agileagenda.com/download/AgileTracker.air","1.0",["Arg1","Arg2"]);
That's all there is to it.
So that's it, pretty simple, huh? You might also want to look into using a LocalConnection to do realtime communication between your web-app and your air-app once the air-app has been launched. Recap of the links:
Wrapper Class:
Adobe's Documentation:
My Blog (where any updates to the wrapper class will be posted)
The actual air.swf that does all the heavy lifting (This URL is wrong in some of the docs!)

http://airdownload.adobe.com/air/browserapi/air.swf


Labels: ,

Saturday, March 22, 2008

ObjectHandles Demo

Here's a short demo of some of the stuff that ObjectHandles (my Flex library for moving & resizing stuff) can do with a very minimal amount of code.  



The custom things I did:
  • Has a MOVING / RESIZING event handler to show a custom tooltip (hides the tooltip on MOVED / RESIZED)
  • Has custom resize handle images that look like grey horizontal bars
  • Only allows vertical resizing (allowHResize = false).
  • On a MOVED event, the objects have an animation that snaps them to a column.
It's a little hard to see in that video, but the duration & start time in the tooltip update as you move or resize the boxes around.

Labels: ,

Monday, March 17, 2008

Custom Cursors + Multiple Windows

Quick tip.  Don't use CursorManager.setCursor anymore.  It works fine for single-window flex apps, but breaks once you have an AIR app with multiple windows.

UIComponent now has a cursorManager property that references the cursor manager for the current window.  If you use that it'll work all the time, not just in web Flex apps.

I understand why they left CursorManager in there for backwards compatibility, but it's a shame we can't mark it as @deprecated like in the Java world.

Labels:

Monday, January 07, 2008

AgileAgenda - New Version

Posted a new version of AgileAgenda over the weekend.  Fixed a few minor bugs, redesigned the opening screens, and put in our new logo.  The entire initial user experience should be better now.   Here's a quick glimpse of what it looks like now:



Labels: , , ,

Tuesday, December 04, 2007

ObjectHandles 1.0.9 available for download

New version is up. It fixes the selectNone() bug, has the correct
metadata for events, and no longer creates event listeners on the
parent object for every single OH you instantiate.
I also added an updateAfterEvent call when things are getting moved,
it really made the motion a lot smoother, never thought it would
matter much.

http://code.google.com/p/flex-object-handles/downloads/list

ASDocs now posted as well, it's pretty sparse so it looks like I need to add
in a bunch of comments :)

http://rogue-development.com/objecthandles/asdocs/

-Marc

Labels: ,

Tuesday, November 27, 2007

Spreadsheet Demo #2 - ComboBox

UPDATE: http://www.rogue-development.com/blog/2008/03/first-public-release-of-my-flex.html

Last night I wanted to see how my component would work with custom editors that weren't just text boxes.

Here's a quick demo of my spreadsheet component with a custom renderer descended from a combo box.  Notice a few things:
  • 1 click on a cell to open, not one click to activate the cell and another to open it.
  • You can keyboard navigate to it, hit the down-arrow, open the list of choices, use the arrows to select one, and then hit enter to select it, just like you should be able to.

Labels: ,

Thursday, November 22, 2007

New ObjectHandles Build

I posted a new ObjectHandles build yesterday.  It fixes a couple bugs and integrates some patches people have sent me including:

1) Graphical handle support
2) Fixed aspect ratio support
3) Ability to detect transparency & clicks 

There will likely be another build in the next week to integrate another patch and fix a few a performance issue.

Labels: , ,

Friday, September 28, 2007

Modal dialog blur

I usually want to turn off the modal dialog blur in applications but can never remember how. This blog post is here so I can find it quickly next time.

Set this style:

Application {
modalTransparencyBlur: 0;
modalTransparencyDuration: 0;
modalTransparency: 0;
}

Labels:

Tuesday, September 25, 2007

DataGrid can go DIAF

(That's "Die in a fire" for those of you not hip to texting lingo and don't have a little sister to help make you hip)


Making the DataGrid do things it's not meant to do is amazingly hard. Look at this gem that's found in the itemEditorItemEditEndHandler method:

if (event.reason == DataGridEventReason.OTHER || !event.isDefaultPrevented())
{
destroyItemEditor();
}

If the event is canceled it goes ahead and destroys your item renderer.

So if you want to say, NOT end editing and you cancel the event, it goes ahead and kills your editor anyways.

No problem... we could just override the method and fix it. Except someone on the 2.0 SDK team got private method happy with that one.

Are there any good DataGrid alternatives out there? Especially for mimicking spreadsheet functionality where you're adding and removing rows dynamically through user input?

Labels: ,

Sunday, September 23, 2007

My first Red5 experience

I've worked with both Chris Allen and John Grden on some projects for my employer. During that time, I can't count the number of times I heard about Red5, but I never got a chance to try it out.

This past week I caught Chris' "Building Red5 Applications" session at FlashForward. I finally got up the motivation to download and try it out.

Here's my experience. It's my first time playing with Red5 (but I have plenty of java & eclipse experience) so it's almost guaranteed I'm not doing everything the "right way". But this way worked for me, maybe it'll help someone else. If it does help you, please leave a comment. Writing this up takes a while and I'm not going to bother in the future for other things if nobody gets value out of it.


Attempt #1

I wanted to try out the .tar.gz package so I would know how to deal with it on Linux as well as Windows. That way I'd be able to deploy anything I built on my webhost fairly easily.

I unpacked it, ran ant, and let it all build. So far so good.

Then I ran the red5.bat file. Lots of exceptions. Like millions, maybe billions, I lost track. Seems my sample applications weren't found. I poked around a bit, but no good. Screw this.

Attempt #2
I went for the windows installation package. That unpacked everything and the server ran fine. I could open up a web browser to http://localhost:5080/ and view the demos, admin panel, etc.

Note: The default admin username/password is admin/admin. You can change that in conf/tomcat-users.xml I'd suggest changing it.

Eclipse

So now I want to get it rocking with eclipse. I launch eclipse (I'm lazy and like things easy so I use easy eclipse.)

I select File->Import->Existing project, then pick my Red5 project. Bamn, I got an eclipse project set up.

But there's compile errors.

Severity and Description Path Resource Location Creation Time Id
Project red5_server is missing required source folder: 'test' red5_server Build path 1190574481375 173990
The project cannot be built until build path errors are resolved red5_server Unknown 1190574530687 173992


So I closed the red5 eclipse project, fired up a text editor, and looked in my .classpath file inside the red5 folder. It had the offending entry.

<classpathentry kind="src" path="test"/>

Note... I delete this line later on:
<classpathentry kind="con" path="org.jayasoft.ivyde.eclipse.cpcontainer.IVYDE_CONTAINER/ivy.xml/test,"/>
You might as well do it now since you're in there.



So I removed it, and repopened eclipse.

A quick Project->Clean and...
3656 Errors

Ok, seems it doesn't see some libraries. Right click on my project, select properties. Go into "Java Build Path", select the "Libraries" tab, click the "Add Jars..." button, and add all of the .jar files in the lib directory.

A quick Project->Clean and...
0 Errors

WOOT

Running it from Eclipse

Now I go into src/org/red5/server, find the StandAlone.java, right click it, and select "Run as..."->"Java Application".

I check my eclipse console panel and get a bunch of lines the last one being:

[INFO] 2007-09-23 15:14:20,875 main:( org.red5.server.Standalone.main ) Startup done in: 5875 ms

Nice.

I point a web browser at: http://localhost:5080/ and see that the red5 server is indeed running. I launch the ball demo and push the connect button. Then I make a new browser window and launch the demo again. When I move the red5 logo in one window, it moves in the other. That means it's all working.

Eclipse also helpfully added an entry to my run button (the green circle with the play button) in my toolbar so I don't have to find the StandAlone.java file again.

I want a quick launch button for debugging too, so I kill the server (click the red square on the console panel) and right click the StandAlone.java and select "Debug as java application". The server starts in debug mode, and eclipse adds it to my debug button on the toolbar. (the green beetle button)

Making something

Now, I want to make something. But first, I want to get rid of the samples. So I kill the server, close the eclipse project, and edit the .classpath file again. I know there's a way to do this through eclipse, but I find editing the file quicker.

I get rid of these entries:
<classpathentry excluding=".svn/*" kind="src" path="webapps/echo/WEB-INF/src"/>
<classpathentry excluding=".svn/*" kind="src" path="webapps/tutorial/WEB-INF/src"/>
<classpathentry excluding=".svn/*" kind="src" path="webapps/fitcDemo/WEB-INF/src"/>
<classpathentry excluding=".svn/*" kind="src" path="webapps/oflaDemo/WEB-INF/src"/>
<classpathentry excluding=".svn/*" kind="src" path="conf"/>
<classpathentry excluding=".svn/*" kind="src" path="webapps/test/WEB-INF/src"/>
<classpathentry excluding=".svn/*" kind="src" path="webapps/midiDemo/WEB-INF/src"/>
<classpathentry excluding=".svn/*" kind="src" path="webapps/agile/WEB-INF/src"/>

And I add one for my project, I'm going to work on something for AgileAgenda, so I'll call it agile.

<classpathentry excluding=".svn/*" kind="src" path="webapps/agile/WEB-INF/src"/>

Then I go rename the "echo" example to "agile", and delete all the other samples (I deleted admin too, not sure if that was a good idea or not)

Open up the project in Eclipse again.

And I don't see my agile webapp. WTF? Oh yeah... Eclipse is retarded. Right Click->Refresh and it shows up.

I want to rename the Application class in my webapps folder (see the picture).



Luckily, Eclipse rocks Java more than it rocks Flex, so I can just right click, select refactor, and pick move. Then I click "Create Package" enter in com.agileagenda.services and click ok a bunch of times.



I delete the rest of the webapp echo source tree.

A project->Clean and...



I think I broke something. Oh well.

Project->Properties->Builders and remove the invalid entry.



Project->Clean and build successful.

I hit the debug button, and search the console output. No errors reported and:

[INFO] 2007-09-23 15:44:45,375 main:( /agile.invoke0 ) Initializing Spring root WebApplicationContext

So I guess it found my new app. Yay!

Kill the server.

So I go back to my Application class and make a new method.

public function login(username:String, password:String) : Boolean
{
return true;
}

Then I remember that this is Java and not Actionscript so I rewrite it...

public boolean login(String username, String password)
{
return true;
}

I go to my project menu and turn on "Build Automatically" and get...

Severity and Description Path Resource Location Creation Time Id
Unbound classpath container: 'org.jayasoft.ivyde.eclipse.cpcontainer.IVYDE_CONTAINER/ivy.xml/test,' in project red5_server red5_server Build path 1190576857156 182811


WTF?

Close the project, open my .classpath file, remove the offending line:
<classpathentry kind="con" path="org.jayasoft.ivyde.eclipse.cpcontainer.IVYDE_CONTAINER/ivy.xml/test,"/>

Open the project, Project->Clean, build success.

I have no idea why that didn't bite me earlier.

I press the debug button. The red5 server launches.
[INFO] 2007-09-23 15:51:26,062 main:( org.red5.server.Standalone.main ) Startup done in: 4937 ms

Yay.

Writing some actionscript

Now that I have a (dumb) login method, I want to try it out in Flex.

I have a "Sandbox" AIR application set up in Eclipse that I use when I want to quickly play with something. I highly suggest everyone do that. So I open my sandbox, open the MXML and create a quick login form.

<mx:Label x="26" y="43" text="Username:"/>
<mx:TextInput x="101" y="41" id="txtUsername"/>
<mx:TextInput x="101" y="71" id="txtPassword" displayAsPassword="true"/>
<mx:Label x="26" y="73" text="Password"/>
<mx:Button x="196" y="101" label="Login" click="onLoginClick()"/>
<mx:Label x="58" y="148" text="Label" id="lblResult" width="281"/>

And then add in some code (remembering that this is Actionscript!)...

private var netConn:NetConnection;

protected function onLoginClick() : void

{
NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;
SharedObject.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;

netConn = new NetConnection();
netConn.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus );
netConn.addEventListener( SecurityErrorEvent.SECURITY_ERROR, errorHandler );
netConn.addEventListener( AsyncErrorEvent.ASYNC_ERROR, errorHandler );
netConn.addEventListener( IOErrorEvent.IO_ERROR, errorHandler );

netConn.connect("rtmp://localhost/agile");

}

protected function sendLogin() : void
{
var responder:Responder = new Responder( onLoginResult );
netConn.call("login",responder,txtUsername.text, txtPassword.text);
}

protected function onLoginResult( result:Boolean ) : void
{
lblResult.text = result ? "Login Success!" : "Login Fail!?!";
}

protected function onNetStatus(event:NetStatusEvent) : void
{
switch( event.info.code )
{
case "NetConnection.Connect.Success":
lblResult.text = "Connected, sending login";
sendLogin();
break;

default:
lblResult.text = "ERROR!! " + event.toString();
break;
}

}

protected function errorHandler(event:Event) : void
{
lblResult.text = "ERROR! " + event.toString();
}


I start the server, run my sandbox app, click the login button, and get a connection error. Something went wrong somewhere.

I bet it's a configuration problem. I open webapps/agile/WEB-INF and notice 3 config files.

in web.xml I make this change:

<context-param>
<param-name>webAppRootKey</param-name>
<param-value>/agile</param-value>
</context-param>

In red5-web.xml I make this change:

<bean id="web.handler"
class="com.agileagenda.services.Application"
singleton="true" />

In red5-web.properties, I make this change:

webapp.contextPath=/agile

Kill the server, restart it (debug mode), try my app again. "Login Sucess" YES!

Now I head into the java class and put a breakpoint on the "return true;" line. I hit the button on my AIR app and Eclipse correctly breaks on the line. Yay!

I inspect the username and password parameters, and the values I had filled into the AIR app are present. Yay!

I'm now to the point where I can start actually writing my application.


Labels: , , ,

Wednesday, September 05, 2007

AirDerbyEntries++

Last night I submitted agile agenda for the AIR derby. I wasn't going to originally since I didn't think I had a chance in hell, but then I got thinking. Even making some kind of entry-list might generate some good publicity. With beta starting within the next week or two, I could use a little press.

Labels: , ,

Monday, September 03, 2007

AgileAgenda facelift progress

I've been feverishly working on some of the UI elements of AgileAgends that have been sorely lacking in recent revisions. I've gotten a lot closer to something I'm proud to admit to doing. Here's a couple screenshots.







Go see a full listing of screenshots.


Also, we're still accepting beta apps...

Labels: , ,

Thursday, August 30, 2007

XRayViewer download page

Yesterday, I wrote about the XRayViewer AIR application.

There's now a download page with a slightly updated version. Any future releases will be there.

http://www.rogue-development.com/xrayviewer.xml

Labels: , ,

Wednesday, August 29, 2007

XRay Viewer

A lot of the time a designer gives me a swf, and I want to know how they structured it. I have two options.

1) Open up the Flash IDE and explore it
2) Add an instance of it to my project, and then use XRay to explore it.

Now, I have a third option. I can open it up in the XRayViewer AIR application I just put together and then use XRay to look through it.

XRayViewer.air

All this little app does is host the XRay connector and let you load a local swf (File->Open). Then it displays the swf with some simple controls to play/stop/advance/back.




But the real power is you can go off to the XRay interface and look through that loaded swf!

Labels: , , ,

Saturday, August 25, 2007

AgileAgenda progress

With the release of AlivePDF, I've started on the printed reports of the project, arguably one of the most important features of software like this. Here is a sample report for the first few hours of working on this stuff. I'm quite pleased with how easily and quickly it was to develop that. It needs a lot of work visually, but I think it's actually a useful report. When printing these out you can choose which sections of the report will go in. Eventually I want to get the outline/bookmark PDF features working to make it easy to jump around in the document. I still have to tackle printing out the gantt style chart, but that's for another day. (Note on sample... that project is an amazingly simple project not really needing software like this. I've also been using this software for a project I've been working on at my "real job" and that is actually useful)

I've also been working with a young designer from France on some of the visual aspects of the software. He's been making icons for a while now and agreed to let me use some of them in my project. On top of that he did a mockup on a screen that took my thoughts on design to a completely new direction. He's working on a project of his own as well, I'll make sure to post about it when it's ready. Here's an example of what the software might look like come release time:



Even that look still has a couple rounds of revisions left, but it's getting closer.

We're still accepting beta applications for anyone interested in trying this stuff out in the first round of beta.

Labels: , ,

Friday, August 24, 2007

AlivePDF Flex/AIR Example

It took me a couple minutes to figure out how to write a PDF to disk using AlivePDF, hopefully this short example will save you those few minutes.

public class ProjectPDFExporter

{
private var filename:String = "test.pdf";
private var pdf:PDF;

public function exportPdf(filename:String) : void
{
this.filename = filename;

pdf = new PDF();

pdf.addEventListener(Event.COMPLETE, onComplete);

pdf.setDisplayMode (Display.FULL_PAGE,
Layout.SINGLE_PAGE);



pdf.addPage();
pdf.setFont( FontFamily.ARIAL );
pdf.addText("My Teax",1,10);
pdf.setFont( FontFamily.ARIAL , "", 32);
pdf.addText("Some more text",10,30);

pdf.finish();
}

protected function onComplete(event:Event)
{
var f:FileStream = new FileStream();
var file:File =
File.applicationStorageDirectory.resolve( filename );

f.open( file, FileMode.WRITE);
var bytes:ByteArray = pdf.getPDF()
f.writeBytes(bytes);
f.close();
}
}

Also, it looks like the first version of AlivePDF.swc is missing at least one class (FontFamily), so you're probably safer using the source instead of the swc for now.

The more I look into it, the more I'm impressed by this library. I'm really surprised Adobe had never made something similar before.

Labels:

Generate PDF's from Actionscript!

Thibault Imbert has released the first alpha version of AlivePDF which allows PDF generation through Actionscript. I'm hoping to be able to use this library for most of my "print" needs in the future since it's pretty easy to generate a pdf and then open a reader on it.

Labels:

Wednesday, August 22, 2007

Mysterious flex compiler error

1119: Access of possibly undefined property someOtherMember through a reference with static type SecondClass ...

Sometimes, the flex compiler just decides to start throwing this error around. It's usually on a line such as:

SomeClass.someMember.someOtherMember = "Hi";

With some simple definitions like...

class SomeClass
{
public static var someMember:SecondClass;
}

class SecondClass
{
public var someOtherMember:String;
}

A clean-build will often make the reported error go away. But sometimes it won't. The error will start happening at seemingly random times when I've been working on completely unrelated sections of the code. One compile everything is fine, then the next it just starts complaining when nothing related was changed.

It happens in FB2, FB3 (using the FB2 SDK), and when compiling from the command line using the FB2 SDK. I haven't seen it using the FB3 SDK, but that doesn't help me since it happens fairly infrequently.

This is a compile-time error, not a run-time error, so it has nothing to do with variables not being initialized. There's no other errors (or even warnings for that matter) in the project.

Labels:

Monday, August 20, 2007

Debugging a screenweaver application in Flex Builder

To debug a screenweaver application in Flex Builder on OSX I do the following:
  1. In your main screenweaver app, add in a command line parameter (-swf) to specify the swf to use. This will let you specify to load the debug version of the swf.
  2. Create your ScreenWeaver bundle however you normally do.
  3. Create a shell file called launch.command (the extension .command is neccessary) next to your bundle
  4. In launch.command enter in a command to launch the app. I use something like:
    /somepath/MyApplication.app/Contents/MacOS/swhx -swf MyApp-debug.swf
  5. Click the little tiny arrow next to the debug button on your FlexBuilder toolbar.
  6. Select "Other..."
  7. Select your application on the left.
  8. On the right, uncheck "Use Defaults"
  9. Set the "Debug" line to your launch.command you created





Now, you should be able to debug your application. It should launch in your screenweaver shell and full debugging functionality from Flex Builder should work correctly.

This should work on windows if you replace the .command with a .bat file.

Our Screenweaver app will load a default non-debug swf if no "-swf" parameter is passed. That way the loader doesn't have to change for production vs. debug usage.

Labels: ,

Friday, August 17, 2007

Waiting on Flex Builder


Right now, as I post this, I'm waiting for FlexBuilder to do whatever it is that it does. I've looked around but have yet to find a suggestion on why occasionally it'll just take a minute or more to refresh & build. Arghhh.

Some suggestions that have made it better for me (but not 100% better)...

Project->Properties->Flex Applications ...
make sure only the ONE main mxml file you care about is listed there.

Project->Properties->Flex Compiler ...
Turn on "Generate HTML template".
Turn off "Copy non-embedded assets" (if you do that, you'll have to remember to refresh your assets by copying them to your bin folder when they change.)

There's certain JRE versions to stay away from, when I upgraded to 1.6.something it got a lot better for me.

Occasionally, edit your .actionscriptproperties and remove "weird crap" that's in there. I've seen refereneces in there like "src/src/src/src/src/src/src" for some unknown reason.

Labels:

Sunday, August 12, 2007

Image/screenshot viewer with hotspots

Yesterday, I found myself wanting to post some screenshots of a project I'm working on, but I wanted to be able to define "hotspots" on the image that would display more information upon mousing over them. Here is what I came up with:

http://www.agileagenda.com/tour/FeatureDisplay.html

But that's not the cool part. The cool part is in only a couple hours, I was able to extend that viewer into an AIR application that could edit the XML config file. Here's some screenshots of that tool:


I'm using ObjectHandles to handle moving & resizing the hotspots. Then I'm using a simple RichTextEditor to edit the text that is displayed upon mouseover.

This tool needs some work, but I could see it being useful to other people someday.

Labels: ,

Monday, July 30, 2007

Object Handles updated

[ObjectHandles is a library to easily manage user movable and resizeable onscreen objects]

It's been a while, as I've been overly busy lately, but there is now a new ObjectHandles release.
This release fixed one bug with non-visible components, and added a new set of mouse cursors as the default. Since my last build, there's a been a bunch of submissions for new mouse cursors. I thank you all! In the near future I'll create a separate swc with them all embedded so developers can pick and choose. But for now I've embedded one of those new options into the library as the default. You can download the new release at

http://code.google.com/p/flex-object-handles/downloads/list

Now, you can also set your own mouse cursors, outside of the library. To do this, implement the OHMouseCursors interface, instantiate your class, and assign it to the ObjectHandles.mouseCursors property. A sample cursor class follows:

package your.package
{
public class YourMouseCursorClass implements OHMouseCursors
{
[Embed("../../../assets
/cursors/set1/resize- v.png")]
protected var sizeNS:Class;
[Embed("../../../assets/cursors/set2/move.png")]
protected var sizeAll:Class;
[Embed("../../../assets/cursors/set1/resize- l.png")]
protected var sizeNESW:Class;
[Embed("../../../assets/cursors/set1/resize-r.png")]
protected var sizeNWSE:Class;
[Embed("../../../assets/cursors/set1/resize- h.png")]
protected var sizeWE:Class;

protected var map:Object = new Object();

public function getCursor(name:String) : MouseCursorDetails
{
return map[name];
}

public function YourMouseCursorClass () : void
{
// Numbers are offsets to the hot-spot of the cursor (the point of the arrow, the middle of the resize bar, etc.)
map["SizeNS"] = new MouseCursorDetails(sizeNS, -5, -8 );
map["SizeAll"] = new MouseCursorDetails(sizeAll, -11, -13 );
map["SizeNWSE"] = new MouseCursorDetails(sizeNESW, -5, -6 );
map["SizeNESW"] = new MouseCursorDetails(sizeNWSE, -5, -6 );
map["SizeWE"] = new MouseCursorDetails(sizeWE, -9, -6 );
}
}
}

Then in your mxml...

<oh:objecthandles mousecursors="{new YourMouseCursorClass()}" allowrotate="false" x="10" y="90" width="307" height="30" minheight="30" minwidth="100">

If you have many ObjectHandles objects around, you can reuse the same cursor class to reduce memory usage.

The OH website, including a sample, is still at
http://www.rogue-development.com/objectHandles.xml

Enjoy!

Labels: ,

Friday, July 27, 2007

Custom DataGrid headerRenderer based on Canvas

I ran into a problem that I'm still not sure what was happening today. I had something like this:

public class MyHeader extends Canvas
{
...
}

...

myDataGridColumn.headerRenderer = new ClassFactory( MyHeader );

It worked, but upon mouse over or mouse move it would flicker like crazy. Extending Label or Button works no problem. I tried implementing various interfaces like IDataRenderer, IDropInListItemRenderer, and IListItemRenderer but still had the same results.

A quick fix I found was to intercept and cancel those mouse events in my header class.

addEventListener(MouseEvent.MOUSE_OVER, onMouseOver, true );
addEventListener(MouseEvent.MOUSE_MOVE, onMouseOver, true );
...
protected function onMouseOver(event:MouseEvent) : void
{
event.preventDefault();
}

Now everything seems to work fine. But I'm still at a loss on why this happened in the first place and I worry my hack of a fix is the wrong way to do it. Has anyone successfully gotten a custom header renderer based on a container working?

Labels:

Thursday, July 26, 2007

Sharing Flex Projects

We've got 5 developers all working on the same Flex based project. What's the best way to share the project so everyone can work on it together? (I don't mean a version control system, I mean the actual Flex project).

We tried checking into version control and all using the same .project, .actionscriptProperties, and .flexProperties files, but that was a disaster. Flex likes to open them for read/write and make little modifications at seemingly random times. Then merging changes between developers sucks.

What we do now is each developer creates their own Flex project outside of version control.

Then we link in the src folder of our application into our project. (In eclipse you can make a link to another folder by selecting "new folder" then clicking the advanced checkbox) ... see the picture:



Next, we set that src folder as the default source folder of the project.




Then we delete the .mxml that flex builder auto-created for us when we made the project since we don't need it anymore.

Then we set-as-default application our main mxml file.

We use a few external libraries, so we also add a swc folder under our project's build directory.

It seems to work pretty well. We even have some people using FB2 and some on FB3 using this technique. How do other people handle this?


For those of you following the AgileAgenda progress, two new screenshots of the web-view:
http://www.agileagenda.com/screenshots/screenshot8.JPG
http://www.agileagenda.com/screenshots/screenshot9.JPG

Labels:

Wednesday, July 25, 2007

OSX Flex app is slow...

So I tried my scheduling software on my mac today. It took about 18 seconds to calculate my schedule on my duo-core 2ghz MacBook Pro.

My home WinXP 2.1ghz DuoCore2 based machine takes 2 seconds. A 9x improvement.

But the shocker... my work WinXP laptop with a 1.7ghz Pentium M, a MUCH slower machine, takes about 4 seconds.

Why is the Mac so much slower on compute intensive actionscript?

I've heard of it being slower on graphical animation type things, but not something like this.

Labels: , , ,

Friday, July 06, 2007

Object Handles now availbe from Google Code

I've moved the download location of Object Handles to take advantage of the download functionality of Google Code. This should let me manage versions much easier.

http://code.google.com/p/flex-object-handles/downloads/list

The neat thing is, I used an ant script to automatically post files there!

Labels: , ,

Thursday, July 05, 2007

SwfControls (Graphical Buttons and Popup Menus)

On Saturday I blogged about a new component I was working on and I got a few responses saying it looked really useful. So I threw up a project page and it's available for download.

I'm calling it "swfControls" for now, but I'm hating the name the more I think of it. Any suggestions? At this point, I'm really considering bundling ALL of my controls together, maybe I should just do that to avoid naming them all.

http://rogue-development.com/swfControls.xml

Right now there's just a binary SWC for download, I'll get full source posted soon.

Enjoy!

Labels: , ,

Saturday, June 30, 2007

Popup Menus and Graphical Buttons

Skinning Flex controls can be a pain. You need a designer who really knows what they're doing and you have to embed your assets in your application (or at least I haven't seen a way around that).

On a current flex project we want some buttons nicely designed by a flash guy. Those buttons should open a popup menu with more buttons on it. (like any standard toolbar)

Requirements:
  • Easy way for designer to create graphical buttons.
  • A way of using those graphical buttons in a button/popup combination
  • Load assets at runtime so they can be swapped out easily (for different languages for instance)
  • Include keyboard navigation
  • Simple to use

Solution:

First, I created a graphical button that will load in a standard swf with some specially named frames. The designer puts the different button states in the different frames. (There HAS to be a standard Flex control for this, but I couldn't find it. Any suggestions? I'd love to replace my little custom implementation with a standard one.)

Then I created a combo-box like control that uses those buttons with a popup menu.

Results:
http://www.rogue-development.com/popup_menu/sandbox.html


I'll package this up and publish under an MIT license sometime soon if anyone else is interested.

Labels:

Thursday, June 07, 2007

ObjectHandle component updated

ObjectHandles, the easy way to add user resizing & movement of objects to your flex application, has been updated.

Changes include:

- Initial rotation support added (still needs some tweaks). Thanks
goes to Alexander Kludt for contribution of most of this feature.
- Metadata for events added. (Thanks Thomas Jakobi!)
- Bug fix for making ObjectHandles dynamically through actionscript
instead of in an MXML document.
- Ability to turn mouse cursor support off (since they are ugly right
now).

A new demo is up and downloads can be found at:
http://www.rogue-development.com/objectHandles.xml
(Clear your browser cache before viewing the demo again, I've seen it
not refresh sometimes, even on a shift-reload on my browser)

Enjoy!

Labels: , ,

Sunday, June 03, 2007

ObjectHandles now has mouse cursor support.

ObjectHandles, the easy way to add user resizing & movement of objects to your flex application, has been updated.

Now, when mousing over the component or the various handles an appropriate mouse cursor will be displayed.

There's been a lot of interest from people about this component. It's seen over 400 downloads and I've received quite a few emails about it. If you interested in following this project more closely, I've set up a google-group where I plan to announce new versions and people can ask questions. To join, go over to:
http://groups.google.com/group/objecthandles

Downloads, examples, etc can be found on the project page:
http://www.rogue-development.com/objectHandles.xml

If the example on the project page seems out of date, you may have to clear your cache. I've found some browsers don't refresh i-frames like they should.

Labels: , ,

Friday, June 01, 2007

Log Viewer

I've started a new project to make a flex component to quickly display largish amounts of text. It's main purpose is to display logging information on screen. It's uses a semi-intelligent algorithm to re-use on screen components and batch UI updates in groups.


http://www.rogue-development.com/logViewer.xml

Labels: , ,

Thursday, May 10, 2007

ObjectHandle updated, and binary now available

I've spent a bit more time on my move/resize flex component and there's now an expanded demo available online.

I've also set up a dump of the latest binary .swc that I've been working on to the website. With any luck this will be of some use to someone.

http://www.rogue-development.com/objectHandles.xml

New in this version:

- Horizontal & Vertical Anchors
- A selection manager

Labels: ,

Monday, May 07, 2007

First look at a Flex Component to resize on screen objects.

I've been spending some time working on a flex component to add "handles" to an on screen object to allow an end-user to resize and move it. It's exactly the same type of functionality when you're using design mode of Flex Builder.

Below is the first look at the current progress I've had.



As you might have noticed it has a few bugs and is far from done feature-wise. It you'd like to keep up on it, take a look at the project page.

If anyone knows of a project doing this same type of functionality, please let me know. I hate to duplicate efforts.

Labels: , ,

Sunday, May 06, 2007

Programatically add constraints through Actionscript

Flex Builder has that great "Constraints" UI that allows you to anchor components to their parent that I'm sure everyone has used.


But what happens if you create an object in actionscript and then you want add constraints to it? There is no "top" property you can set to anchor the component to the top. It turns out the constraint system is entirely based on Flex's style system and can be used as follows:

var someComponent:SomeComponent = new SomeComponent();
var style:CSSStyleDeclaration = new CSSStyleDeclaration();
style.setStyle("top", 0);
style.setStyle("horizontalCenter", 0);
someComponent.styleDeclaration = style;

And BAMN, someComponent will be anchored to the top, center of it's parent (assuming it's parent is a container that supports anchors.) Available style selectors are:

  1. top
  2. left
  3. right
  4. bottom
  5. horizontalCenter
  6. verticalCenter
You could also specify the styles in a css sheet, or by adding them to an already existing style.

Edit...

As a commenter has posted you can also use the setStyle method in addition to the various other methods I mentioned. So the following would also work:

someComponent.setStyle("top",0);

Labels: , ,

Friday, May 04, 2007

Blog Strip / Power of Flex

So I wanted to add a list of recent blog posts to the home page of my website. I searched for about an hour for some javascript snippet to do a nice ajax style grab of my feed and display it. An entire hour I searched before I realized I'm a flex developer. I spent the next 15 minutes throwing together a quick prototype, and another 45 minutes tweaking it. In less time than I spent searching for a premade solution, I wrote my own.

Source, example, etc can be found here:
http://www.rogue-development.com/blogStrip.xml

I am constantly amazed at how easy Flex makes some things.

Labels: ,

Friday, April 27, 2007

Using Flex Builder 2 with Perforce

Here are some directions I made to get a developer set up with the perforce plugin in Flex Builder. It then shows how to do a couple common tasks. I usually submit through the p4win client, so that's the work-flow I show, even though you can do it directly from Flex Builder if you wish.

It assumes:

1) You have Flex Builder 2 and Perforce installed and working.
2) The project you have is from perforce, and you've already opened it in Flex Builder

Enjoy:
http://rogue-development.com/SetUpPerforce.swf

Warning: Movie is amateur hour stuff. It's only my second time using wink, and I didn't want to spend a lot of time on it.

I've also gone through some work to figure out how to share a Flex project among multiple developers. I'll make another post at some point explaining that. Unfortunately, I probably wont be able to post that movie since it does show some proprietary company information.

Labels: , ,

Tuesday, March 13, 2007

SWC Refreshing

So I got the ant build for the library working with my code-behind thing from yesterday. Unfortunately it has the same results as building the library in a separate project. It seems Flex Builder won't refresh it's SWC's until you either open the config dialog or restart.

Has anyone else found a way to compile a SWC, and have those results immediately apparent in the design-mode view of an app?

Labels: , , ,

Monday, March 12, 2007

Code in front

I'm new to the whole Flex world. One of my first questions was to figure out how to link the components that I laid out in the mxml editor with the classes I created to control those components.

I googled around and found the way most people advocated to do it is with code behind. It consists of creating a base class in actionscript, then extending that class with mxml. This immediately struck me as an odd way of doing it, but I pushed on. It works pretty well.

But I've had this nagging feeling that it's doing things backwards. I have a component that I want to extend the functionality of through code. Why am I doing that inheritance in the opposite direction?

Trolltech makes a great development framework that I'm far more familiar with. They have a .ui format that generates c++ code in exactly the same way that flex has an .mxml format that generates actionscript code. But I think the Trolltech guys got it right just a little more than the Adobe guys. Their .ui generates the base class for you to extend.

So I put my "nagging feeling" and my experience with QT together and have come up with what I will call "Code in Front".

Code in Front Step 1
Create your component in mxml. Here's my example:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="Hello World" id="mLabel"/>
<mx:Button label="Goodbye" id="mButton"/>
</mx:VBox>


Save this file as "TestComponentBase.mxml". This way, a class named "TestComponentBase" will automatically be generated during compile time. We're going to extend that class in the next step, hence the term: "code in front"

Code in Front Step 2
Create your actionscript class by extending the mxml class. Mine looks something like this:
package
{
import mx.events.FlexEvent;
import flash.events.MouseEvent;

public class TestComponent extends TestComponentBase
{
public function TestComponent()
{
super();
addEventListener(FlexEvent.CREATION_COMPLETE, init);
}

protected function init(event:FlexEvent) : void
{
mButton.addEventListener(MouseEvent.CLICK, onButtonClick );
}

protected function onButtonClick(event:MouseEvent) : void
{
mLabel.text = "Goodbye World!!!!";
}

}
}


Code behind step 3:
Use your new component in your application.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" xmlns:ns1="*" width="423" height="400">
<ns1:TestComponent />
<ns1:TestComponent />
<ns1:TestComponent />
</mx:Application>

And that's all there is to it. Easy, elegant, makes sense to me.

But there is one huge downfall to this. When using flex builder in design mode to lay out your controls in your app, this is all you see for the above mxml:



That's losing way too much of the functionality of the tools for most people and I agree. Luckily, there is a way to fix this. First, create a new project based on a Flex Library. Then, inside that project create a new directory from the flex ide. Call that directory "src" and click the "advanced" button. Link that directory to your source directory from your "main" project. You should see something like this:



From this newly created project, right click your component class (TestComponent.as in my example) and select "Include class in library". Assuming "Build Automatically" is checked off in your project menu, you will shortly get a brand new .swc inside your bin directory.

Now, go back to your "main" project. Right click the project, go to properties. Select "Flex build path" on the left. Select the "Library path" tab on the right. Click the "add swc" button and add that swc we just made in the above step.




Now, go back to your application mxml, go into design mode, click the refresh button, and bamn:



You can now edit the actionscript all you like without problems.

But now a new problem arises. If you edit the mxml base component, and then go back to your application mxml, those custom components are just blank boxes again. Relaunching flex builder fixes this. Going to the properties of your project (not changing anything) and clicking "ok" also fixes it. But that's a big inconvenience and is something worth investigating to fix.

I'm pretty sure I can replace the whole second-project thing with an ant build task and make that automatically build by installing ant into flex builder. That will most likely fix the problem.

For the record, I'm not claiming to have come up with some novel way of doing things. This idea is so obvious that it's very likely that someone somewhere else has had it before.

Labels: , ,