View Specialist: Shared Date Formatter August 25, 2008
Posted by Allen Manning in : Flex, Refactoring, Unit Testing , 1 comment so farOur Views enjoy collaborating amongst shared specialist objects that have a clear and delineated purpose. These utility objects help reduce View complexity, code duplication, and are easier to trust.
Specialized utility objects are easier to trust because they are easier to certify. We can compare it to the certification schemes of other trades- for the most part, the simpler and more focused a particular trade or skill the easier it is to certify.
For example, for most of us a PADI certification would suffice as a good verification of being able to Scuba Dive. Whereas if someone claims that they can build a house, we would require much more verification and validation that they can really do what they claim- examples of the houses they have built, extended references, and a good project plan.
The more focused the skill, the easier it is to certify.
Let’s look at an example of this- imagine a scenario where we needed to apply a single date format across multiple screens in multiple localized applications. Rather than sprinkle duplicate code throughout our View objects, let’s use a date formatting specialist object.
We create an object that formats dates in the standard localized date mask. This object can be used for all date formatting inside of the application. If the application is localized, the date mask can be updated in the resource bundle to apply a different locale-specific date formatting.
StandardDateFormatter.as
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 | package com.allenmanning.exampleflexapp.formatters { import mx.controls.dataGridClasses.DataGridColumn; import mx.formatters.DateFormatter; import mx.resources.IResourceManager; import mx.resources.ResourceManager; /** * Object responsible for taking dates and formatting them into strings. This object will try * and format all dates into a standard date mask. * * @author amanning * @author mgregory */ [ResourceBundle("StandardDateFormatter")] public class StandardDateFormatter { //------------------------------------------------------------------------------------------ // Member variables //------------------------------------------------------------------------------------------ /** Labels for this class */ private static var _resourceManager:IResourceManager = ResourceManager.getInstance(); /** Flex Date formatter which will actually do the date formatting */ private static var _formatter:DateFormatter = new DateFormatter(); //------------------------------------------------------------------------------------------ // Methods //------------------------------------------------------------------------------------------ /** * Given a date, return a string formatted with the standard date mask * * @param date date to format * @return formatted string representation of date */ public static function format(date:Date):String { var result:String = ""; if(date != null) { _formatter.formatString = _resourceManager.getString( "StandardDateFormatter", "standardDateMask"); result = _formatter.format(date); } return result; } } } |
We commonly use date formatting in List controls, so rather than have multiple label function wrappers spread throughout our View code, we add another responsibility to our StandardDateFormatter- it can also become a shared Label Function.
Updated StandardDateFormatter.as
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 | package com.allenmanning.exampleflexapp.formatters { import mx.controls.dataGridClasses.DataGridColumn; import mx.formatters.DateFormatter; import mx.resources.IResourceManager; import mx.resources.ResourceManager; /** * Object responsible for taking dates and formatting them into strings. This object will try * and format all dates into a standard date mask. * * @author amanning * @author mgregory */ [ResourceBundle("StandardDateFormatter")] public class StandardDateFormatter { //------------------------------------------------------------------------------------------ // Member variables //------------------------------------------------------------------------------------------ /** Labels for this class */ private static var _resourceManager:IResourceManager = ResourceManager.getInstance(); /** Flex Date formatter which will actually do the date formatting */ private static var _formatter:DateFormatter = new DateFormatter(); //------------------------------------------------------------------------------------------ // Methods //------------------------------------------------------------------------------------------ /** * Given a date, return a string formatted with the standard date mask * * @param date date to format * @return formatted string representation of date */ public static function format(date:Date):String { var result:String = ""; if(date != null) { _formatter.formatString = _resourceManager.getString( "StandardDateFormatter", "standardDateMask"); result = _formatter.format(date); } return result; } /** * A convenience date formatting label function for list controls * * @see mx.controls.dataGridClasses.DataGridColumn * @param date date to format * @return formatted string representation of date */ public static function labelFunction(item:Object, column:DataGridColumn):String { var result:String = ""; if(item != null && column.dataField != null) { if (item[column.dataField] is Date) { result = format(item[column.dataField]); } else { var date:Date = new Date(item[column.dataField]); result = format(date); } } return result; } } } |
Because the object is so focused it is fairly straight-forward to Unit Test. Many thanks to Melissa Gregory of Brightcove Seattle for the improvements she has made to this object and its corresponding test.
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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | package com.allenmanning.exampleflexapp.formatters { import flexunit.framework.TestCase; import flexunit.framework.TestSuite; import mx.controls.dataGridClasses.DataGridColumn; /** * Responsible for testing the object which formats dates in a consistent way * * @author amanning * @author mgregory */ public class StandardDateFormatterTest extends TestCase { //------------------------------------------------------------------------------------------ // Constructors //------------------------------------------------------------------------------------------ /** * Class Constructor * * @param methodName the test to add to this test suite * @see flexunit.framework.TestCase */ public function StandardDateFormatterTest( methodName:String = null ) { super( methodName ); } //------------------------------------------------------------------------------------------ // Methods //------------------------------------------------------------------------------------------ /** * Returns testing suite, which can be used by a FlexUnit test runner * * @return the testing suite for this class */ public static function suite():TestSuite { return new TestSuite(StandardDateFormatterTest); } /** * Tests basic date formatting */ public function testDateFormat():void { var dateString:String = 'Mar 26 2008, 4:52PM'; var date:Date = new Date(2008,2,26,16,52,0,0); assertEquals( 'formatting must match', dateString, StandardDateFormatter.format(date)); } /** * Tests formatting an invalid date */ public function testFormatInvalid():void { var dateString:String = ''; var date:Date = null; assertEquals( 'formatting must match', dateString, StandardDateFormatter.format(date)); } /** * Tests the date label function */ public function testLabelFunctionDateInput():void { var dateString:String = 'Mar 26 2008, 4:52PM'; var date:Date = new Date(2008,2,26,16,52,0,0); var dateString2:String = 'Mar 6 2010, 4:53PM'; var date2:Date = new Date(2010,2,6,16,53,0,0); var dummyRow:Object = {updated:date,created:date2}; var mockColumn:DataGridColumn = new DataGridColumn("updated"); assertEquals( 'updated column formatting must match', dateString, StandardDateFormatter.labelFunction(dummyRow,mockColumn)); mockColumn = new DataGridColumn("created"); assertEquals( 'created column formatting must match', dateString2, StandardDateFormatter.labelFunction(dummyRow,mockColumn)); } /** * Tests the date label function when datafield is not a date */ public function testLabelFunctionTimestampInput():void { var dateString:String = 'Jul 23 2008, 8:56PM'; var timestamp:Number = 1216846571711; var date:Date = new Date(timestamp); var millisOffset:Number = date.timezoneOffset * 60 * 1000; // convert the timestamp for comparison purposes // (otherwise this test will fail in other timezones) var timestampOffset:Number = timestamp + millisOffset; var dummyRow:Object = {uploadTimestampMillis:timestampOffset}; var mockColumn:DataGridColumn = new DataGridColumn("uploadTimestampMillis"); assertEquals( 'column formatting must match', dateString, StandardDateFormatter.labelFunction(dummyRow,mockColumn)); } } } |
This is a simple, but powerful example of how highly focused specialist classes can collaborate with our Views to reduce complexity and boilerplate code. Distributing the View ‘workload’ amoung a focused team of object specialists is good approach in assembling Flex views.