I have recently had to delve into the world of Adobe Flex and ActionScript and wanted to share some of my trials and tribulations with it. As I’m not a massive fan of Flash or proprietary technologies I had to assume the work had been assigned to me as a punishment for crimes committed in a previous life. In fact, 2 months in, I’m not entirely convinced what embedded Flash has to offer over conventional html and javascript – especially considering the features available in HTML5. “Meh” pretty much sums up my feelings for Flex – perhaps I’ve been doing too much Java.
Having previously been programming JSF I found Flex quite … different. One notable difference being it’s easy approach to the Observer Pattern with Binding
s and another being it’s asynchronous behaviour. As JSF does not have any features comparable to these so this was all unchartered territory for me. In particular, I was experiencing some difficulty with asynchronous calls and the ViewStack
. Basically the application was Single-Sign-On and I didn’t want the home view to show until they had been authenticated. Simple right? All you have to do is a bind a Boolean
which is set by a CairngormEvent
when authentication succeeds. Next problem was that there was some data to show on the home screen. But because every service call is asynchronous the screen would load with a blank area which would only be filled-in once the service call had completed. Well, what’s the point in showing a page unless it’s ready to be shown. I now have 2 conditions that must be true
and ChangeWatcher
s seem to be the only thing that are going to help me.
A capability to synchronise asynchronous events is all I could think of to achieve the desired result. So I created a SynchronisedChangeWatcher
class. WARNING – this is an over-engineered solution so I’m not sure if I would recommend using it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
public class SynchronisedChangeWatcher { // Function to call once all watched properties are true private var _callFunc : Function; // List<{host, property}> private var watchedProperties : ArrayCollection = new ArrayCollection(); // Map<Object{host, property}, ChangeWatcher> private var propertyChangeWatchers : Dictionary = new Dictionary(); // Flag to indicate whether _callFunc has already been called - stops duplicate invocations private var functionCalled : Boolean = false; public class SynchronisedChangeWatcher(func : Function = null) { _callFunc = func; } /** * * @param host the object where the property being watched resides * @param property the name of the property to watch - must be <tt>public</tt> and <tt>[Bindable]</tt>. * @param customCondition an optional function to perform a custom conditional operation. This function MUST return a <tt>Boolean</tt>. As it may be called repeated times * is should contain simple, read-only logic. */ public function watchPropertyWithCustomCondition(host : Object, property : String, customCondition : Function):BindingSynchroniser { if(host == null) { throw new ArgumentError("'host' object cannot be null"); } if(customCondition == null && !(host[property] is Boolean)) { throw new ArgumentError("Property '" + property + "' must be of type boolean"); } if(!ChangeWatcher.canWatch(host, property)) { throw new ArgumentError("Property '" + property + "' must be bindable"); } watchedProperties.addItem({host: host, property: property, customcondition: customCondition}); return this; } /** * * @param host the object where the property being watched resides * @param property the name of the property to watch - must be <tt>public</tt>, <tt>[Bindable]</tt> and <tt>Boolean</tt> . */ public function watchProperty(host : Object, property : String):BindingSynchroniser { return watchPropertyWithCustomCondition(host, property, null); } /** * This starts watching properties or calling a function if all properties are true. */ public function start(arg : PropertyChangeEvent = null):void { if(_callFunc == null) { throw new Error("'callFunc' property cannot be null"); } var allPropertiesTrue : Boolean = true; for(var i : uint; i < watchedProperties.length; i++) { var item : Object = watchedProperties.getItemAt(i); // Either call the item's funtion to perform conditional logic or use item's property directly if((item.customcondition != null && !Boolean(item.customcondition.call())) || (item.customcondition == null && !Boolean(item.host[item.property])) ) { // Property is false so start watching var cw : ChangeWatcher = ChangeWatcher.watch(item.host, item.property, start); propertyChangeWatchers[item] = cw; allPropertiesTrue = false; } else if(propertyChangeWatchers[item] != null) { // Property is true so stop watching ChangeWatcher(propertyChangeWatchers[item]).unwatch(); propertyChangeWatchers[item] = null; } } // Don't call function again if it has already been called - ChangeWatchers that are still running will cause this if(!functionCalled && allPropertiesTrue) { functionCalled = true; unwatchAll(); _callFunc(); } } /** * @param func the function to call once all watched properties are true */ public function set callFunc(func : Function):void { _callFunc = func; } /** * This method ensures any outstanding ChangeWatchers are stopped (this should have already happened in start() method). */ private function unwatchAll():void { for each(var cw : ChangeWatcher in propertyChangeWatchers) { if(cw && cw.isWatching()) { cw.unwatch(); } } } } |
And it’s usage:
1 2 3 4 5 |
var scw : SynchronisedChangeWatcher = new SynchronisedChangeWatcher(); scw.callFunc = function():void{ doStuff(); scw = null; }; scw.watchProperty(_model, "UserLoggedIn"); scw.watchProperty(_model, "_CreationComplete"); scw.start(); |
So doStuff()
will be called when both UserLoggedIn
and _CreationComplete
properties change – Simplz!