Adobe has been talking lately about the next update to Flex and Flash Builder, version 4.6. There's a lot of cool stuff planned, but some of the things that interest me most are the new UI components. You can read a good article on them here, What's new in Flex 4.6 SDK and Flash Builder 4.6. I thought it might be interesting to update one of my own application, the Death Clock mobile app, to use some of these new components.
When I built the Death Clock application, one of the first things I ran into was handling the UI for picking gender and the month of your birth. Dropdowns just don't work well on a mobile device. I ended up using a hybrid approach that used states and groups of radio buttons. You can read more about this in my original blog post from ... wow... exactly one year ago. Man - I'd love to say I planned that but it was completely random! Anyway - be sure to look at that post for an example of the UI.
For my new application, I decided to get rid of this work around and make use of the new ToggleSwitch and DateSpinner classes. I thought the ToggleSwitch would be a good replacement for the gender selection and the date spinner would - obviously - replace the date drop downs. Here's what the Flex code looks like for the original version, again, with the work around:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="Death Clock" initialize="init()" xmlns:mx="library://ns.adobe.com/flex/mx" backgroundColor="0x000000">
<fx:Declarations>
<mx:NumberValidator id="dayValidator" source="{dayBorn}" property="text" allowNegative="false" minValue="1" maxValue="31"
invalidCharError="Day Born must be a number." integerError="Day Born must be a number." negativeError="Day Born must be a positive number."
lowerThanMinError="Day Born must be over 0." exceedsMaxError="Day Born must be lower than 31."/> <mx:NumberValidator id="yearValidator" source="{yearBorn}" property="text" allowNegative="false" minValue="1900" maxValue="2020"
invalidCharError="Year Born must be a number." integerError="Year Born must be a number." negativeError="Year Born must be a positive number."
lowerThanMinError="Year Born must be over 1900." exceedsMaxError="Year Born must be lower than 2020." /> <s:RadioButtonGroup id="monthRBG" change="setMonthLabel()"/>
<s:RadioButtonGroup id="genderRBG" change="setGenderLabel()"/>
</fx:Declarations> <fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx"; #errorDiv {
font-weight: bold;
color: red;
}
</fx:Style> <fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.ValidationResultEvent; [Bindable] private var months:ArrayCollection;
[Bindable] private var days:ArrayCollection;
[Bindable] private var years:ArrayCollection; private var selectedMonth:int;
private var selectedGender:int; private function setGenderLabel():void {
if(genderRBG.selection) {
genderButton.label = genderRBG.selection.label;
selectedGender = int(genderRBG.selection.value);
}
toggleGenderState();
} private function setMonthLabel():void {
if(monthRBG.selection) {
monthButton.label = monthRBG.selection.label;
selectedMonth = int(monthRBG.selection.value);
}
toggleMonthState();
} private function toggleGenderState():void {
if(this.currentState == 'normal') this.currentState='selectedGender';
else this.currentState='normal';
} private function toggleMonthState():void {
if(this.currentState == 'normal') this.currentState='selectedMonth';
else this.currentState='normal';
} private function init():void {
this.currentState = 'normal'; } private function doClock():void { var validation:ValidationResultEvent;
validation = dayValidator.validate();
if(validation.type == "invalid") {
errorDiv.text = validation.message;
return;
}
validation = yearValidator.validate();
if(validation.type == "invalid") {
errorDiv.text = validation.message;
return;
} errorDiv.text = ''; var bDay:Date = new Date();
bDay.fullYear = int(yearBorn.text); bDay.month = selectedMonth;
bDay.date = int(dayBorn.text); var now:Date = new Date(); //Life expectancy will be 75.6 for men, 80.8 for women (http://en.wikipedia.org/wiki/List_of_countries_by_life_expectancy)
//for the .6 and .8 we just guestimate based on 60 and 80% of 365
//date math idea from: http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/
var deathDate:Date = new Date(bDay.time);
if(selectedGender == 0) {
deathDate["fullYear"] += 72;
deathDate["date"] += 219;
} else {
deathDate["fullYear"] += 78;
deathDate["date"] += 292;
} //are you dead already?
if(deathDate.getTime() < now.getTime()) {
errorDiv.text = 'Sorry, but you are already dead. Have a nice day.';
return;
} var timeLeft:Number = Math.round((deathDate.time - now.time)/1000);
trace('death is '+deathDate.toString()+ ' v='+deathDate.time);
trace('Now is ' +now.toString()+ ' v='+now.time);
trace('diff is '+timeLeft);
//trace(bDay.toString()+'\n'+deathDate.toString()+'\n'+timeLeft.toString()); navigator.pushView(Counter,{deathDate:deathDate,timeLeft:timeLeft}); }
]]>
</fx:Script> <s:states>
<s:State name="normal"/>
<s:State name="selectedMonth"/>
<s:State name="selectedGender"/>
</s:states> <s:layout>
<s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" />
</s:layout> <!--
The idea of button groups to handle drop downs is from Dirk Eismann (DEismann@herrlich-ramuschkat.de)
-->
<s:HGroup width="100%" height.selectedGender="100%">
<s:Label text="Gender: " width="50%" />
<s:Button id="genderButton" label="Male" click="toggleGenderState()" includeIn="normal,selectedMonth" />
<s:Scroller includeIn="selectedGender" height="100%" width="100%">
<s:Group>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<s:RadioButton label="Male" value="0" selected="true" groupName="genderRBG"/>
<s:RadioButton label="Female" value="1" groupName="genderRBG"/>
</s:Group>
</s:Scroller> </s:HGroup> <s:HGroup width="100%" height.selectedMonth="100%">
<s:Label text="Month Born: " width="50%" />
<s:Button id="monthButton" label="January" click="toggleMonthState()" includeIn="normal,selectedGender" />
<s:Scroller includeIn="selectedMonth" height="100%" width="100%">
<s:Group >
<s:layout>
<s:VerticalLayout/>
</s:layout>
<s:RadioButton label="January" value="0" selected="true" groupName="monthRBG"/>
<s:RadioButton label="February" value="1" groupName="monthRBG"/>
<s:RadioButton label="March" value="2" groupName="monthRBG"/>
<s:RadioButton label="April" value="3" groupName="monthRBG"/>
<s:RadioButton label="May" value="4" groupName="monthRBG"/>
<s:RadioButton label="June" value="5" groupName="monthRBG"/>
<s:RadioButton label="July" value="6" groupName="monthRBG"/>
<s:RadioButton label="August" value="7" groupName="monthRBG"/>
<s:RadioButton label="September" value="8" groupName="monthRBG"/>
<s:RadioButton label="October" value="9" groupName="monthRBG"/>
<s:RadioButton label="November" value="10" groupName="monthRBG"/>
<s:RadioButton label="December" value="11" groupName="monthRBG"/>
</s:Group>
</s:Scroller> </s:HGroup> <s:HGroup width="100%">
<s:Label text="Day Born: " width="50%" />
<s:TextInput id="dayBorn" width="50%" text="1" />
</s:HGroup> <s:HGroup width="100%">
<s:Label text="Year Born: " width="50%" />
<s:TextInput id="yearBorn" width="50%" text="1973" />
</s:HGroup> <s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" /> <s:Label id="errorDiv" width="100%" />
</s:View>
That's right at 190 lines. Not bad for my first Flex mobile application. Now let's look at the new version.
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="Death Clock" backgroundColor="0x000000" xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Declarations> </fx:Declarations> <fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx"; #errorDiv {
font-weight: bold;
color: red;
}
</fx:Style> <fx:Script>
<![CDATA[
import mx.collections.ArrayCollection; private function doClock():void { var bDay:Date = dob.selectedDate; var now:Date = new Date(); //Life expectancy will be 75.6 for men, 80.8 for women (http://en.wikipedia.org/wiki/List_of_countries_by_life_expectancy)
//for the .6 and .8 we just guestimate based on 60 and 80% of 365
//date math idea from: http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/
var deathDate:Date = new Date(bDay.time);
if(!selectedGender.selected) {
deathDate["fullYear"] += 72;
deathDate["date"] += 219;
} else {
deathDate["fullYear"] += 78;
deathDate["date"] += 292;
}
deathDate["fullYear"] += 78;
deathDate["date"] += 292; //are you dead already?
if(deathDate.getTime() < now.getTime()) {
errorDiv.text = 'Sorry, but you are already dead. Have a nice day.';
return;
} var timeLeft:Number = Math.round((deathDate.time - now.time)/1000);
trace('death is '+deathDate.toString()+ ' v='+deathDate.time);
trace('Now is ' +now.toString()+ ' v='+now.time);
trace('diff is '+timeLeft);
//trace(bDay.toString()+'\n'+deathDate.toString()+'\n'+timeLeft.toString()); navigator.pushView(Counter,{deathDate:deathDate,timeLeft:timeLeft});
}
]]>
</fx:Script> <s:layout>
<s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" />
</s:layout> <s:HGroup width="100%">
<s:Label text="Gender: " width="30%" />
<s:ToggleSwitch id="selectedGender" skinClass="skins.GenderToggleSkin" width="70%" />
</s:HGroup> <s:HGroup width="100%">
<s:Label text="Birth Date:" width="30%" />
<s:DateSpinner id="dob" width="70%" />
</s:HGroup> <s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" /> <s:Label id="errorDiv" width="100%" /> </s:View>
78 lines. Bit nicer, right? Here's a screen shot showing the new UI controls.
I just need to change that "Begin" button to a skull or something and it will be perfect.