Of course you have an API!

The following is a dramatization of actual events.

“I need access to these reports.”

“Well, here they are, in the UI.”

“But I need programmatic access.”

“We don’t have an API yet.”

“Fine, I’ll scrape this… Wait… This is Flex. Wait, let me just run Charles… Flex is talking to the back-end using AMF. So what do you mean you don’t have an API? Of course you do — it is AMF. A little PyAMF script will do the trick.”

“Please don’t show it to anyone!”

P. S. That little script was still running (in “stealth production”) months, if not years, later.

XmlRpc for AS 3.0 HOWTO

Since people ask (in emails, at the short-lived MT version of this blog and on flexcoders@yahoo), here is how I use it.

DISCLAIMER: as I’ve mentioned, I hacked this from the original when I was just starting with Flex 2, so I gutted some code before I knew what the hell I was doing,
but in this state it works for me… 

I use Flex Builder. I have the contents of the zip file right in my Flex Builder project. (NOTE: This was before I read that flex should not be a part of a package name per Adobe’s license; change it, willya?)

Then I choose have a simple facade like so:

package com.qbf.flex.ct {
  // Insert correct imports here, of course...

  public class Utils {
    public static function serverCall(method:String, 
                                          params:Array,    
                                          handler:Function): void {
        SERVER_CALL.call(method, 
                         params, 
                         handler, 
                         DisplayObject(Application.application));
    }

    public static const SERVER_CALL:XmlRpcService = 
   new XmlRpcService("http://localhost:8081/", 
                        Application.application.url.indexOf("http://") == 0? null :
   null :
   // This is explained further below... 
   QbfFakeResponse.SINGLETON); 
}

And from everywhere else in the code I use Utils.serverCall() as
follows:

var params:Array = [{value : resdefDataToServer, type: "struct"}, 
uri, server];
Utils.serverCall("doStuff", params,
        // This callback will be executed when the server 
        // call returns
 function(result:Object):void {
           process(result);
        });

Notice that you only need to specify types for parameters if it’s not a string. If all your parameters are string, you can just pass an array of their values to the serverCall() –a little sugar…

Now about this QbfFakeResponse.SINGLETON business (it’s not necessary, just use null in its place if you don’t need it). The definition ofSERVER_CALL checks whether the application URL starts with http:// — in my case, it means that it’s running “for real”. If it is running in Flex Builder’s debugger, the URL will start with file://,
and so I will use “fake” responses to simulate server calls (this is sometimes useful for debugging, so that I don’t have to run a server). This fake response class is an implementation of com.qbf.flex.util.xmlrpc.FakeResponse interface. All you need to do is implement getResponse() method and return whatever you need by checking the method name and parameters provided.

Silly Flex trick of the day

Since “only top-level components in the application can have context menus“, how would one make a different ContextMenu for every type of item in a tree? In particular, I want tree leaves to have different menu items enabled than the branches; e.g., the leaves should have “Properties” menu enabled, and the “Create new” menu disabled, and vice versa for the branches. Mac Martine mentions using
rollovers, which is cute, but it looks like a more robust way is to use
MOUSE_OVER (yeah, I tried both).

To do this, use the following TreeItemRenderer in the tree in question (obviously, you can choose to add/remove things
from the ContextMenu.customItems rather than enabling/disabling them…):


public class ServerTreeRenderer extends TreeItemRenderer {

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(super.data) {
if (TreeListData(super.listData)) {
leaf:Boolean = !TreeListData(super.listData).hasChildren ;
super.label.addEventListener(
MouseEvent.MOUSE_OVER,
function(evt:MouseEvent):void {
// This will disable all items in my context menu (it's declared somewhere
// else, this is not a TreeItemRenderer method, duh...
disableAll();
if (leaf) {
// propertiesItem is a ContextMenuItem...
propertiesItem.enabled = true;
} else {
contextMenuContainer.createNewItem.enabled = true;
}
[...]

All of this is in anticipation of a promised exegesis on how a
custom ContextMenu works anyway (I have no time for this
at the moment), but works for now…

P.S. I just like the word “exegesis”. The more exegeses, the merrier…