In the article I give a (very) brief overview of most of the changes and new features between Flex 3.x and 4.5 and between Flex Builder 3 and Flash Builder 4.5. If I covered everything in depth then the article would be longer than you would want to read and longer than I want to write -- so I stuck with an overview of each change/addition, and then tried to provide links for further reading if a topic is of particular interest or importance to you.
One handy new feature in Flex 4.5 is the BusyIndicator component. This little thing goes by many names -- I usually find myself calling it a "throbber", though I'm not exactly sure why. What is it exactly? Any of these qualify:
All of these were taken from ajaxload.info, a handy site for finding a throbber for your website.
What do all of these have in common? They communicate to the user that your application is doing something but not what percentage complete the task is. It's saying, "I'm here, working on what you asked for, but I don't know how long it will be until I'm done!"
Flex has had a progress bar component for a while now, which even has an "indeterminate" property, which when true makes it not actually indicate progress but rather just show busy-status, like the images above do, but in bar form, sort of like this: (also taken from ajaxload.info)
Prior to Flex 4.5, if you wanted to show busy status but not progress, your options were to use an indeterminate progress bar, find a third party class, like this one that I used in one of my old projects, or roll your own. You can't display an animated gif in a Flex application -- I know because I tried. I'm lazy, don't judge.
Well my friends, now that Flex 4.5 has arrived, you've got a BusyIndicator that couldn't be simpler to use. Here's some sample code that shows how to use it:
What does it look like? I'm glad you asked. Here's a screenshot of the above application running:
There are a few key things to know:
When it's visible, it's animating. When it's hidden (visible="false") or removed from the stage, it's not using any CPU time, which makes it great for Mobile use. However, it is not possible to have it be visible and not animating.
This control does not block -- that is, it does not prevent any other user interaction. It is not modal.
The above code has two buttons and space at the top of the application for the busy indicator to be displayed. When you press the Start button, the busy indicator is displayed, for example indicating that you have just sent a message to the server with BlazeDS and are awaiting the response. When you press the Stop button, the busy indicator is hidden.
This component is not currently skinnable, but it does offer a few customization properties. You can set the bar color using the property symbolColor. It accepts a uint, which means you can pass in 0xFF0000, #FF0000, or red, they all do the same thing.
You have already seen my use of the rotationInterval property in the example above. This is a numeric value, the number of milliseconds between rotations, so a value of 1000 would be 1 second, and your throbber would seem pretty slow. Speaking of slow throbbers, let's have some fun.
Suppose you work for the Department of Anger Inspiration, and since you love your job, you want to create a really frustrating BusyIndicator user experience. What could be more frustrating than an indicator that seems to slow to a crawl and then speeds back up? Is something wrong with my computer? My internet connection? The application? Who knows? But ultimately, nobody is going to like this BusyIndicator except your boss.
I added a timer that keeps in sync with the busy indicator by updating its own delay
The delay starts out at a decent clip of 1/100th of a second, but the delay increases by 20% with every tick of the timer.
When the delay exceeds 3.5 seconds, it is reset to 1/100th of a second. This gives the illusion that things are slowing to a crawl, but then suddenly pick up for a moment, before slowing to a crawl again, over and over.
I happen to work at an awesome place where we get the week between Christmas and New Year's Eve off as an unofficial holiday (the university shuts down, so staff such as myself can't work), so I spent last week hanging out at home with my extended family and getting in some extra quality time with my wife and son.
Speaking of which, I've got a baby on the way -- due any day now. Ok, technically due 7 days from now. And during the course of that break, it occurred to me that it was kind of tedious to keep track of my wife's contractions the last time we went through this (with my first son, in 2008) - was the last contraction 90 seconds? 60? Was it 5 minutes apart? 7?
I could surely write an app to take care of tracking contraction start times, durations, and frequencies all with a single button. I checked the Android Market and while there are a few apps out there for this, I really didn't like the UI of any of them and knew my idea was better. So I opened up Flash Builder Burrito and used my existing Flex skills and AIR for Android to throw together this app to help us decide when it will be time to go to the hospital.
(HUGE DISCLAIMER: I am not a doctor, this is just some software I wrote for fun. Always consult a physician when concerning things like your health and having a freaking baby... don't be an idiot!)
All told it only took a few hours to get the UI designed and work out the (basic!) algorithms for averaging durations and frequencies.
The screen shot on the left shows the screen during a rest-time (between contractions) and after 2 are complete; the right screen shot shows the screen during timing of a 3rd contraction.
I had decided to average the 5 most recent contractions mostly arbitrarily because it sounded good at the time. I don't know if that's a good measurement or not, but it's there, and it would be easy enough for me to change if I had a reason.
I've only tested it on my Droid X, so I'm not sure how it looks or works on smaller-screened Android phones, like the Droid 2, but it works like a charm for me. My wife, after a good laugh, has seemed to warm up to the idea and we've even been practicing using it when she has Braxton Hicks contractions in the evenings.
I'm contemplating putting it up on the Android market. What do you currently-expectant and experienced mothers and fathers out there think? Is it useful?
I had some information ready to be posted here when I submitted my entry into Ray's Best of CF9 contest, but he covers it pretty well in his review post; and he was kind enough to host the video on his S3 account. I've also posted the video on my Vimeo account, so here it is again for reference:
You can download the source code here. Unfortunately, I haven't found any more time to continue working on this; and I have to admit that as cool as the CFaaS feature is, I'm not terribly motivated to go further with this mail client because I know it will eventually get canned. Why write a mail client when you use Outlook every day?
So it was a fun way to play with Flex 4, the latest Swiz build at the time, AIR, and CFaaS; but I don't think I'll bother continuing to mess with it.
Sometimes I find myself sending a query object from ColdFusion to Flex, for example, to bind to a DataGrid, and then I want to edit that query object and return it to ColdFusion to be saved in the database. Unfortunately, Queries are only a data type in ColdFusion. When sent to Flex, they get mapped to an ArrayCollection, and when you send that ArrayCollection back to ColdFusion, it isn't automatically converted back to a Query object (though lord knows that would be nice!).
It's certainly possible to just treat that ArrayCollection as an array of structures -- because that's what it is -- and loop over it to extract the data you want. Sure. Be my guest. But ColdFusion has so many great functions designed to make working with Queries easier. Wouldn't it be great if you could just convert that ArrayCollection back to a Query?
That's why I created this UDF, and I intend to submit it to CFLib once I know it's solid. I wanted to put this out to the community though to make sure it covers all of the necessary data types and doesn't have any errors. Can you see anything I've missed?
Below is the code for the function as well as a unit test. To see how it handles bad/unexpected data, you can do something wacky like change one of the values inside the structs to be a struct instead of the simple value it is now.
<cffunction name="arrayCollectionToQuery">
<cfargument name="arrayColl" type="Array" required="true" />
<cfset var qResult = '' />
<cfset var columnList = structKeyList(arrayColl[1]) />
<cfset var typeList = ''/>
<cfset var numericType = ''/>
<cfset var k = '' />
<cfset var i = 0 />
<cfloop collection="#arrayColl[1]#" item="k">
<cfif isNumeric(arrayColl[1][k])>
<!--- decimal or integer? --->
<cfset numericType = "integer">
<cfloop from="1" to="#arrayLen(arrayColl)#" index="i">
<cfif arrayColl[i][k] - fix(arrayColl[i][k]) gt 0>
<cfset numericType = "decimal" />
<cfbreak />
</cfif>
</cfloop>
<cfset typeList = listAppend(typeList, numericType) />
<cfelseif isSimpleValue(arrayColl[1][k])>
<cfset typeList = listAppend(typeList, 'varchar') />
<cfelseif isBoolean(arrayColl[1][k])>
<cfset typeList = listAppend(typeList, 'bit') />
<cfelseif isDate(arrayColl[1][k])>
<cfset typeList = listAppend(typeList, 'date') />
<cfelse>
<cfthrow message="Invalid ArrayCollection"
detail="All keys in your array collection must be of one of the following types: Numeric (Int or Float), String, Boolean, Date. The following key contains data that is not one of these types: `#k#`" />
</cfif>
</cfloop>
<cfset qResult = queryNew(columnList, typeList) />
<cfloop from="1" to="#arrayLen(arrayColl)#" index="i">
<cfset queryAddRow(qResult) />
<cfloop collection="#arrayColl[i]#" item="k">
<cfif not isNumeric(arrayColl[i][k]) and not isSimpleValue(arrayColl[i][k]) and not isBoolean(arrayColl[i][k]) and not isDate(arrayColl[i][k])>
<cfthrow message="Invalid ArrayCollection"
detail="All keys in your array collection must be of one of the following types: Numeric (Int or Float), String, Boolean, Date. The following key contains data that is not one of these types: `#k#`" />
</cfif>
<cfset querySetCell(qResult,k,arrayColl[i][k]) />
</cfloop>
</cfloop>
<cfreturn qResult />
</cffunction>
<cfset testData = [
{Num=2, String='fubar!', Bool=true, Date=CreateDate(2009,05,2)},
{Num=4.8, String='bufar!', Bool=false, Date=CreateDate(2009,06,1)},
{Num=6, String='futbol!', Bool=true, Date=CreateDate(2009,07,12)},
{Num=8, String='string!', Bool=false, Date=CreateDate(2009,08,9)},
{Num=10, String='data type!', Bool=true, Date=CreateDate(2009,09,18)},
{Num=12, String='yes', Bool=false, Date=CreateDate(2009,10,6)}
] />
<cfdump var="#testData#" label="data in">
<cfdump var="#arrayCollectionToQuery(testdata)#" label="data out">