Nested SWF communication

Posted in Justin Shacklette on April 27th, 2009 by admin

The ability to split up a large application in smaller, more manageable chunks is one of the pillars of enterprise development. The flash platform is littered with examples: AS3 is OO language, MXML components, SWCs, RSLs, SWFLoader, ExternalInterface, etc. In this post, I’m going to focus on using nested SWFs via SWFLoader. There a few other good posts out there, plus the Adobe docs, but I’ll try to tie everything together in one place.

My main interest in the nested SWFs approach is for performance reasons, both the reduced initial download size and the ability to defer component creation can make a big difference in the real world. And secondarily for testing reasons, because it’s just awesome to see a big enterprise app wired up via hudson with FlexMonkey and Fluint tests.

None of this would be very interesting without the ability to communicated between SWFs. Thankfully, there are a few good options when talking between Flash 9 SWFs, because they run together inside Flash Player in AVM2. Talking between Flash 9 or newer and Flash 8 or older is still possible, but because of the AVM1/AVM2 split you need to use LocalConnection which I’m not going to get into.

Child Application

How does SWF to SWF communication work? First, let’s start with a simple child app:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
 
    <mx:Script>
        <![CDATA[
            [Bindable] public var myProp:String = 'foo';
 
            private function btnClick():void {
                dispatchEvent(new Event('child'));
            }
            public function myFunc(color:uint):void {
                setStyle('backgroundColor', color);
            }
            public function myFuncReturn(i:int, j:int):int {
                return i + j;
            }
        ]]>
    </mx:Script>
 
    <mx:Label text="{'myProp = ' + myProp}" />
    <mx:Button label="dispatch event" click="btnClick()" />
</mx:Application>

The child app exposes one public property myProp, two public functions myFunc and myFuncReturn, and a custom event.

Main Application

The main app exercises all four options in the child SWF. But in an effort to mimic reality, I’m imposing the additional constraint that while the child’s API is known, the actual MXML is unavailable.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" verticalGap="12">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.events.FlexEvent;
            import mx.managers.SystemManager;
 
            private var childApp:Application;
 
            private function init():void {
                SystemManager(loader.content).addEventListener(
                        FlexEvent.APPLICATION_COMPLETE, childReady);
              }
 
              private function childReady(e:FlexEvent):void {
                  childApp = e.target.application as Application;
                  childApp.addEventListener('child', childEventHandler);
              }
              private function childEventHandler(e:Event):void {
                  Alert.show('got child event');
              }
 
              private function setChildProp():void {
                  if (childApp.hasOwnProperty('myProp')) {
                      childApp['myProp'] = 'bar';
                  }
            }
            private function callChildFunc():void {
                if (childApp.hasOwnProperty('myFunc') &&
                        childApp['myFunc'] is Function) {
                    childApp['myFunc'](0xFF00FF);
                }
            }
            private function callChildFuncReturn():void {
                if (childApp.hasOwnProperty('myFuncReturn') &&
                        childApp['myFuncReturn'] is Function) {
                    var val:int = childApp['myFuncReturn'](13, 4);
                    Alert.show('13 + 4 = ' + val);
                }
            }
        ]]>
    </mx:Script>
 
    <mx:Panel title="Child">
        <mx:SWFLoader id="loader" source="test_child.swf" complete="init()" />
    </mx:Panel>
 
    <mx:Button label="set child property" click="setChildProp()" />
    <mx:Button label="call child func" click="callChildFunc()" />
    <mx:Button label="call child func return" click="callChildFuncReturn()" />
</mx:Application>

In the main app, the child SWF is loaded via the SWFLoader MXML tag, which calls the init() function on completion. Then we wait for the child SWF to finish loading before casting it to an Application and wiring up a handler for our custom event. The other three child-to-parent communication options are exercised by the three button click callback functions. In each callback, we first check to see if the child property exists, and in the case of the two function calls, if it can be cast to a Function. In the Adobe docs, because the child MXML code is in the same folder as the parent MXML, they just use a simple cast prior to invoking functions:

FlexApp(loadedSM.application).setVarOne("Updated varOne!");

Which is great for them because they control both the parent and the child SWFs, but doesn’t really work when you have some 3rd party SWF you need to use or Creative lobs some crazy flash animation grenade into your cube at 5:15pm with a nice note to send over a working solution before you go home for the evening. Yep, that never happens.

There is another point that I often forget about MXML: instantiation via tags creates a public instance of the component. So for example, if we added id attributes to the child label and child button, then they would be directly accessible in the parent via childApp['childLabel'].text or childApp['childButton'].addEventListener(...). I find my sense of API cleanliness is deeply offend by this manner of communication, so I tend to stick with one of the four options I described above (probably why I always forget).

Here is the final product:

 »  Main App

Digging Deeper: If you really want to maximize client-side performance, you might find yourself loading and unloading child SWFs. In such a situation, it is important to be a good citizen and make sure your child SWFs clean up after themselves.

Files

Open Video Player in AIR

Posted in Justin Shacklette on April 19th, 2009 by admin

Recently, I’ve been doing quite a bit of video work in Flex and AIR. My main gripe (that I’ll try to rectify below) is that there sure is a lot of info around the web about streaming, FMS, and friends, but almost none about playing local video. Even the Adobe Media Player, which is a really nice AIR app, is all about streaming content from the web.

And like those before me, I’ve come to the same conclusion about video in Flash: it’s pretty cool, but I sure wish it was better. Enter the Open Video Player as the answer to some of NetStream’s woes. Alas, all of the OVP docs and samples are, once again, all about streaming, Akamai, bandwidth, etc.

The elephant in the room for an AIR video player: What if I’m offline?

The Easy Way: VideoDisplay

I gonna get started with the easiest possible local video player, which in Flex means using VideoDisplay. Here’s a simple AIR app that allows you to open a FLV file and play it:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
        width="700" height="500">
 
    <mx:Script>
        <![CDATA[
            [Bindable] private var filename:String = '';
 
            private function openVideo():void {
                if (vd.playing) {
                    vd.pause();
                    playPause.label = 'PLAY';
                }
 
                var docs:File = File.documentsDirectory;
                docs.browseForOpen('Select a Video', [new FileFilter("Video", "*.flv")]);
                docs.addEventListener(Event.SELECT, openFileHandler);
            }
 
            private function openFileHandler(e:Event):void {
                var f:File = e.target as File;
                filename = f.name;
 
                vd.source = 'file://' + f.nativePath;
                vd.play();
            }
 
            private function stopClick():void {
                vd.stop();
                playPause.label = 'PLAY';
            }
 
            private function toggleClick():void {
                if (vd.playing) {
                	vd.pause();
                	playPause.label = 'PLAY';
                } else {
                	vd.play();
                	playPause.label = 'PAUSE';
                } 
            }
        ]]>
    </mx:Script>
 
    <mx:ApplicationControlBar dock="true">
        <mx:Button label="Open Video" click="openVideo()" />
        <mx:Label text="{filename}" fontSize="14" fontWeight="bold" />
    </mx:ApplicationControlBar>
 
    <mx:Canvas width="100%" height="100%" backgroundColor="#000000" backgroundAlpha="0.1">
    	<mx:VideoDisplay id="vd" autoPlay="false" />
    </mx:Canvas>
 
    <mx:HBox>
        <mx:Button id="playPause" label="PAUSE" width="100" click="toggleClick()" />
        <mx:Button label="STOP" width="100" click="stopClick()" />
    </mx:HBox>
</mx:WindowedApplication>

The only real magic is the vd.source = 'file://' + f.nativePath line. Apparently, source wants a URL even for a local file, so just prepend file:// and everything works fine.

Getting Fancy with NetStream

As far as I can tell, no one actually uses VideoDisplay at the core of a video player component in a production system. The many benefits of NetStream are not really clear to me, but it is the darling of production systems. Thankfully, despite its name, NetStream is perfectly happy playing local video. Rebuilding the above example yields:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
        width="700" height="500"
        applicationComplete="complete()">
 
    <mx:Script>
        <![CDATA[
            [Bindable] private var filename:String = '';
            private var nc:NetConnection;
            private var ns:NetStream;
            private var vid:Video;
            private var t:Timer;
 
            private function complete():void {
                nc = new NetConnection();
                nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
                nc.addEventListener(NetStatusEvent.NET_STATUS, connStatusHandler);
                nc.connect(null);
 
                ns = new NetStream(nc); 
                ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
                ns.addEventListener(NetStatusEvent.NET_STATUS, streamStatusHandler);
                ns.client = { onMetaData:streamMetadataHandler };
 
                t = new Timer(300);
                t.addEventListener(TimerEvent.TIMER, streamProgressHandler);
 
                vid = new Video();
                uic.addChild(vid);
            }
 
            private function openVideo():void {
                if (ns) {
                    ns.pause();
                    playPause.label = 'PLAY';
                }
 
                var docs:File = File.documentsDirectory;
                docs.addEventListener(Event.SELECT, openFileHandler);
                docs.browseForOpen('Select a Video', [new FileFilter("Video", "*.flv")]);
            }
 
            private function openFileHandler(e:Event):void {
                var f:File = e.target as File;
                filename = f.name;
 
                ns.play('file://' + f.nativePath);
                playPause.label = 'PAUSE';
 
                vid.attachNetStream(ns);
 
                t.start();
            }
 
            private function errorHandler(e:AsyncErrorEvent):void {
                trace('ERROR: ' + e.text);
            }
 
            private function connStatusHandler(e:NetStatusEvent):void {
                trace('CONN_STATUS: ' + e.info.code);
            }
 
            private function streamStatusHandler(e:NetStatusEvent):void {
                trace('STREAM_STATUS: ' + e.info.code);
            }
 
            private function streamMetadataHandler(info:Object):void {
                for (var key:String in info) {
                    trace("STREAM_METADATA: " + key + "=" + info[key]);
                }
                uic.width = info.width;
                uic.height = info.height;
 
                vid.width = info.width;
                vid.height = info.height;
                vid.x = 0;
                vid.y = 0;
 
                len.text = parseFloat(info.duration).toFixed(1);    
            }
 
            private function streamProgressHandler(e:TimerEvent):void {
                   timer.text = ns.time.toFixed(1);
            }
 
            private function stopClick():void {
                ns.pause();
                ns.seek(0);
                playPause.label = 'PLAY';
            }
 
            private function toggleClick():void {
                ns.togglePause();
                playPause.label = (playPause.label == 'PAUSE' ? 'PLAY' : 'PAUSE');
            }
        ]]>
    </mx:Script>
 
    <mx:ApplicationControlBar dock="true">
        <mx:Button label="Open Video" click="openVideo()" />
        <mx:Label text="{filename}" fontSize="14" fontWeight="bold" />
        <mx:Label id="timer" fontSize="14" fontWeight="bold" />
        <mx:Label id="len" fontSize="14" fontWeight="bold" />
    </mx:ApplicationControlBar>
 
    <mx:Canvas width="100%" height="100%" backgroundColor="#000000" backgroundAlpha="0.1">
        <mx:UIComponent id="uic" width="100" height="100" />
    </mx:Canvas>
 
    <mx:HBox>
        <mx:Button id="playPause" label="PAUSE" width="100" click="toggleClick()" />
        <mx:Button label="STOP" width="100" click="stopClick()" />
    </mx:HBox>
</mx:WindowedApplication>

NetStream is a wonder of engineering. Basic usage is understandable, just feed in null to the NetConnection to switch to progressive download mode, and use the same file:// trick above to play a local video. But after that, things get weird fast. First, the metadata handling to totally backwards: you have to create a dynamic object with a onMetaData property that maps to a callback function. My guess would be that there is some legacy bullshit going on that breaks addEventListener. Second, you can’t tell if a video is playing or not. Third, the lack of progress event means you need to use a timer to track playhead time. Add it up – LAME.

Reaching for the Grail: Open Video Player

According to their website, it’s Open Video Player to the rescue. And I’ll have to agree that it fixes many of NetStream issues. Swaping in OvpNetStream yields:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
        width="700" height="500"
        applicationComplete="complete()">
 
    <mx:Script>
        <![CDATA[
            import org.openvideoplayer.net.OvpConnection;
            import org.openvideoplayer.net.OvpNetStream;
            import org.openvideoplayer.events.OvpEvent;
 
            [Bindable] private var filename:String = '';
            private var nc:OvpConnection;
            private var ns:OvpNetStream;
            private var vid:Video;
 
            private function complete():void {
                nc = new OvpConnection();
                nc.addEventListener(OvpEvent.ERROR, errorHandler);
                nc.addEventListener(NetStatusEvent.NET_STATUS, connStatusHandler);
                nc.connect(null);
 
                ns = new OvpNetStream(nc);
                ns.addEventListener(OvpEvent.ERROR, errorHandler);
                ns.addEventListener(NetStatusEvent.NET_STATUS, streamStatusHandler);
                ns.addEventListener(OvpEvent.NETSTREAM_METADATA, streamMetadataHandler);
                ns.addEventListener(OvpEvent.PROGRESS, streamProgressHandler);
 
                vid = new Video();
                uic.addChild(vid);
            }
 
            private function openVideo():void {
                if (ns) {
                    ns.pause();
                    playPause.label = 'PLAY';
                }
 
                var docs:File = File.documentsDirectory;
                docs.addEventListener(Event.SELECT, openFileHandler);
                docs.browseForOpen('Select a Video', [new FileFilter("Video", "*.flv")]);
            }
 
            private function openFileHandler(e:Event):void {
                var f:File = e.target as File;
                filename = f.name;
 
                ns.play('file://' + f.nativePath);
                playPause.label = 'PAUSE';
 
                vid.attachNetStream(ns);
            }
 
            private function errorHandler(e:OvpEvent):void {
                trace('ERROR: ' + e.data.errorNumber + ': ' + e.data.errorDescription);
            }
 
            private function connStatusHandler(e:NetStatusEvent):void {
                trace('CONN_STATUS: ' + e.info.code);
            }
 
            private function streamStatusHandler(e:NetStatusEvent):void {
                trace('STREAM_STATUS: ' + e.info.code);
            }
 
            private function streamMetadataHandler(e:OvpEvent):void {
                for (var key:String in e.data) {
                    trace("STREAM_METADATA: " + key + "=" + e.data[key]);
                }
                uic.width = e.data.width;
                uic.height = e.data.height;
 
                vid.width = e.data.width;
                vid.height = e.data.height;
                vid.x = 0;
                vid.y = 0;
 
                len.text = nc.streamLengthAsTimeCode(e.data.duration);
            }
 
            private function streamProgressHandler(e:OvpEvent):void {
                   timer.text = ns.timeAsTimeCode;
            }
 
            private function stopClick():void {
                ns.pause();
                ns.seek(0);
                playPause.label = 'PLAY';
            }
 
            private function toggleClick():void {
                ns.togglePause();
                playPause.label = (playPause.label == 'PAUSE' ? 'PLAY' : 'PAUSE');
            }
        ]]>
    </mx:Script>
 
    <mx:ApplicationControlBar dock="true">
        <mx:Button label="Open Video" click="openVideo()" />
        <mx:Label text="{filename}" fontSize="14" fontWeight="bold" />
        <mx:Label id="timer" fontSize="14" fontWeight="bold" />
        <mx:Label id="len" fontSize="14" fontWeight="bold" />
    </mx:ApplicationControlBar>
 
    <mx:Canvas width="100%" height="100%" backgroundColor="#000000" backgroundAlpha="0.1">
        <mx:UIComponent id="uic" width="100" height="100" />
    </mx:Canvas>
 
    <mx:HBox>
        <mx:Button id="playPause" label="PAUSE" width="100" click="toggleClick()" />
        <mx:Button label="STOP" width="100" click="stopClick()" />
    </mx:HBox>
</mx:WindowedApplication>

It’s not a revolution because OvpNetStream just extends NetStream, but we instantly get standard events thrown for progress ticks and metadata, and also some pretty time formatting as a bonus. The OVP also does about a hundred other things related to streaming, streaming servers, and Akamai. That’s about all I know about video right now, so before I can post again I’ll need to learn something new…

Files

Just tarballs (it’s an AIR app after all):

Library Services

Posted in Jerry Andrews on April 19th, 2009 by admin
My emotional favorite: GuruLib.com. Two young people, tremendous technical skill and creativity. Not the richest site, but it's terribly easy to use, and the enthusiasm they have for their product is palpable.  I love the clean design.  I wish there were a few more controls over layout and content, and that their search engine had all the features of LibraryThing, but wow, it just feels good to use.  It has two features I absolutely love (and I seldom love things about software): 1. I can download my library data, so it's not captive, and 2. it goes to Amazon every night and produces a value estimate, which I then squirrel away for my insurance company.  I also really like their integration into other social network software... so much so that I broke a personal rule and authorized their application on my FaceBook page. Nice work, folks.

My technical favorite: LibraryThing.com.  Vast power at your fingertips.  Elegant integration with lots of libraries. They should license the webcam barcode scanner from GuruLib--if they had, I might never have gone looking and found GuruLib in the first place.

Not enough time spent yet: GoodReads.com.

A note on the GuruLib webcam tool: this is just an excellent piece of UI design.  It opens a small window with the current image from your webcam, with a blank beneath and a single "add" button.  You hold a book up to your webcam until the ISBN barcode fills the image. When it recognizes a barcode, it beeps, snapshots the frame, draws a scan bar across the barcode showing what it is interpreting, and holds until the image changes significantly (e.g. you move the book away), whereupon it goes back to a live webcam view. Then, if you agree that it got the right barcode value, you can click the add button and it locates your book using its user-defined search source list (Amazon, Library of Congress, and hundreds of public libraries). It is excellent primarily because of the way it manages the "conversation" with you, the user.  It doesn't require you to do anything with your hands except handle books and click the add button.  When I use it, my mouse never moves, so I just punch the mouse button to add.  It provides great feedback: it tells you what part of the image it used, you can evaluate if the image is clean and bright enough to be accurate, and actually compare the interpreted barcode value to the value printed on the barcode itself if that isn't enough.  It doesn't bother you with extra notifications or weird visual tics. I didn't even notice the switch between snapshot and live scan until I started thinking about why I like it.  It does just what you expect it to do quietly, cleanly, and efficiently.  Sweet.

Introduction to FlexMonkey

Posted in FlexMonkey on April 18th, 2009 by admin

The FlexMonkey project was founded by Stu Stern, CEO of Gorilla Logic, in late 2008.  FlexMonkey is an open source testing framework for Flex and AIR applications that provides for the capture, replay and verification of Flex UI functionality. FlexMonkey can record and playback Flex UI interactions, and generates ActionScript-based testing scripts that can easily be included within a continuous integration process. It uses the Flex Automation API and was created by extending Adobe’s sample automation adapter, AutoQuick.

Resources:

Managing the books

Posted in Jerry Andrews on April 15th, 2009 by admin
I have a few thousand books.  About a thousand of them are on shelves in the house.  Over the past few weeks, I've been VERY slowly sorting them on the shelves so I could find what I'm looking for... and last night I finally realized I needed help doing it.

That's what the Dewey Decimal system is for, right?

So then it occurs to me (naturally) that the web must've already solved this problem.  So I went looking.  And boy have they... as long as you're near a computer, don't mind a short wait for each search, and are willing to enter thousands of books by hand--at least partially.

The good news is there's lots of free services which can generate a complete listing for a book, including Dewey decimal number, summary, keywords, and all the bibliographic information you could want, as well as links to purchase sites (no thanks--I already own the book) give a title fragment or ISBN number or really any basic identifying information. For a small fee, you can also buy a barcode scanner (or adapt your webcam, with a bit of ingenuity), to scan the ISBN numbers which became ubiquitous on dust covers sometime in the last decade or two, which covers maybe 50% of my library.

The bad news is that while that helps a lot, I still have to get the data into the computer somehow--and it doesn't really solve my filing problem.

I want a solution which (a) gets me bibliographic data on all my books in searchable form, (b) doesn't cost thousands of dollars, (c) will allow me to find what I've indexed. RFIDs paired with an existing on-line system seem like the natural choice.  And RFID reader can be got for as little as $50 (and as much as $1000 for a hand-held proximity unit).  Tags cost quite a bit--so much that they violate criterion (b), so that solution has to go on the shelf for now. The price is falling dramatically as manufacturers find ways to print them on labels and manufacture them without a silicon backing, though, so I'm hopeful this will be practical in another couple of years.

For now, I'm experimenting with http://www.librarything.com (my favorite so far), http://www.goodreads.com, and http://www.worldcat.org as tools for getting me enough data for good filing.  And I'm still looking for a real solution to the problem.

Enterprise Reuse of Flex Components and Classes

Posted in Glen Whitbeck on April 8th, 2009 by admin

In a simple Flex (SWF) application, all of the necessary components and classes are defined in a single Flex project. In the enterprise, however, you may want to refactor some of the components and classes into a separate library so that these common components and classes can be reused across multiple Flex applications (or project teams). The most common way to do this is to create a separate Flex project that will be used to create *.SWC file. Each Flex application will then be configured to link to this SWC. A SWC file is basically an archive of external components and classes, similar to a *.jar file in Java land.

There are various reasons that an enterprise might choose to decouple the common components and classes and put them in a separate, reusable SWC file:

  • * Reuse across Flex projects
  • * Reuse across Enterprise project teams
  • * Reuse across Enterprise applications
  • * Influence/enforce/decouple enterprise look-and-feel
  • * Improve application performance
    • – Startup time
    • – file size
    • – network bandwidth usage

Once the SWC file is created, you must decide how your SWF code will link to the necessary classes residing in the SWC. At a high level, Flex provides two linking options: Static linking and Dynamic linking.

STATIC LINKING (“Merged Into Code”):
When you use static linking, the compiler includes all referenced classes and their dependencies in the application SWF file. The end result is a larger SWF file. Obviously, a larger SWF file takes longer to download from the server. On the other hand, since all of the necessary code is contained in the SWF, it will load and run quickly. Keep in mind that the browser can cache the SWF file. So, if a given user will be accessing the same SWF file multiple times, the initially increased download time might be a reasonable trade-off for faster startup times.

DYNAMIC LINKING (“External” (or Runtime Shared Library “RSL”)):
When you use dynamic linking, the refactored components and classes are not included in (they are not compiled into) the application SWF file. Instead, the SWC file remains separate from the SWF. The result is a smaller SWF file size for the main application. The downside is that the SWC must be loaded at run time. In addition, the application loads the entire library (as opposed to static linking that can limit the linking to only the items that the SWF actually uses). This can result in slower startup times and greater memory usage.

Once again, however, client-side caching can play a role. The external SWC file can be cached and reused by multiple SWF applications. Each individual SWF will be smaller due to the exclusion of the common components and classes. If the enterprise has multiple SWF applications that will use the same SWC file, the cumulative size of the downloads might be lessened by using dynamic linking.

The enterprise will need to consider the tradeoffs of each option with respect to its situation and goals, in order to determine the best course of action. Components that will be reused across the enterprise will likely result in the creation of enterprise guidance and best practices. Components and classes whose reuse is limited to smaller divisions may need the flexibility to make the decision that is best for their particular sitation.

Flex and Spring Integration – Dzone Refcard and Interview

Posted in Jon Rose on April 8th, 2009 by jonr

This week Dzone posted a Refcard James Ward and I wrote on the new Spring BlazeDS Integration project (Download the Refcard here).  To go along with publishing the Refcard, Dzone’s

More Degrafa skins

Posted in Justin Shacklette on April 6th, 2009 by admin

Before I dig too deep trying to create a Degrafa Skin Explorer (the idea) as an evolution of the Degrafa Button Explorer, I thought it would be a good idea to spend some more time with some basic Degrafa skinning. I managed to create a few button skins previously, but this time around I wanted something more involved.

ButtonBar

First, I built out a pair of ButtonBar skins (view source enabled):

Flash is required. Get it here!
ToggleButtonBar

Next, I augmented the my basic ButtonBar skins with the various required selected skins (selectedUpSkin, selectedDownSkin, etc.) to create a pair of ToggleButtonBar skins (view source enabled):

Flash is required. Get it here!
iPhone Theme

Lastly, I dissected the iPhone-esque theme sample from the Degrafa Samples page, and created my own bastardized iPhone theme. I factored out a theme color from each component into a CSS style called mainColor to allow the skin to be tweaked. Check it out (view source enabled):

Flash is required. Get it here!
Conclusions

I learned a couple of things during my skinning exercise, but probably the most important came to me in an amazing “A-Duh” moment: Degrafa is really powerful. This is probably the same as saying MXML is a high-level language with a lot of leverage, but the Degrafa team has done a great job bringing this leverage to bear on skinning and graphics in Flex. At this point, I find it hard to imagine creating a style without it.

Secondly, semantic naming is a must inside a stateful skin. So, I replaced any names like darkFill or lightStroke, with nice semantic names like overFill and selectedStroke. So if I look at a chunk of code like this:

<State name="downSkin">
    <SetProperty target="{rect}" name="fill" value="{overFill}" />
</State>

I can immediately know that my downSkin state is using a fill from my overSkin state, and probably implies the states are the same.

Lastly, always resist the temptation to re-use/re-purpose the CSS style from the underlying component in a skin. For example, the iPhone theme above uses a theme color which I intentionally named mainColor. I could easily have used Button’s aptly-named themeColor style (inherited down from UIComponent). But this would dangerously overload the meaning of themeColor, because while it normally controls the Button’s border color it would now control the body color in my iPhone button skin.

Files

Episode 11: Matt Raible

Posted in Drunk On Software on April 5th, 2009 by admin

In episode 11, we have a fun interview with Matt Raible. Matt is a popular blogger and speaker in the Java community.  This edition captures the desired essence of Drunk On Software, just three geeks chatting over a few beers.

Using Flex to integrate with existing web sites

Posted in Glen Whitbeck on April 2nd, 2009 by admin

In a couple of my previous posts, I discussed the integration of a Flex application into an existing web site. One post (link to post) discussed dynamically controlling the HTML real estate used by an embedded Flex application. The other post (link to post) gave a (hopefully) simplified view of code snippets that can be used to communicate between Flex and JavaScript under various scenarios. These were low-level, “how-to” posts. In this post, I’d like to explore some of the ways that this type of integration might benefit the enterprise.

In this particular situation, I was representing a company that had a new product/service offering. This offering is something that many existing E-Commerce web sites would like to add to their existing web site. Conceptually, this is a pretty common situation. However, integration is often more difficult to implement than it is to conceive.

That was not the case in this situation. Implementing this business offering by embedding a Flex application into an existing web site was embarrassingly easy. The integration instructions given to the E-Commerce site’s developers consisted of about 30 lines of text. This text consisted of the HTML Object/Embed tags and the JavaScript functions that the E-Commerce site’s developers needed to copy and paste into their web page. Beyond copying and pasting this text, all the E-Commerce site’s developers needed to do was to modify the JavaScript return values with the values from their web page.

They did not have to change any code on their server. They did not have to change the flow of their web pages. The Flex application that we provided them handled the external-domain server communication. We did not have to work-out the details of how the customer’s servers would access our servers and our services. We did not have to match technologies or protocols. The E-Commerce web site’s developers did not have to modify their server code to know, understand, and call our services.

This integration did not take months or years. This integration took something on the order of minutes or hours. I suppose you could say it took days, if you include time to arrange phone calls between the developers, etc.

Think about that. A new business offering was implemented in a matter of days.

Obviously, the company (or enterprise project team) providing the services, will have to build the backend for the services. But, they would have to do this anyway. The huge payoff is at integration time. And, if these services are used by many “clients” (either by selling the same offering over and over to many customers … or, within an enterprise, by allowing many project teams and existing applications to leverage your services), the cost-benefit analysis becomes compelling. Moreover, the more you can shrink the integration time, the easier it becomes to close a deal. In corporations, various levels of the hierarchy often have different levels of budgetary decision-making power. The lower the integration cost, the fewer levels of approval you will need to close a deal.

Admittedly, this was a specific type of business offering. It clearly does not replace all other forms of integration. As with any architectural tool (or software design pattern), you still must choose the best solution for a given challenge. There are many places where SOA, RESTful Web Services, XML, etc. will be the better solution. But, when this type of solution fits, it can really payoff!