jump to navigation

View Specialist: Shared Date Formatter August 25, 2008

Posted by Allen Manning in : Flex, Refactoring, Unit Testing , 1 comment so far

Our 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

?View Code ACTIONSCRIPT
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

?View Code ACTIONSCRIPT
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.

?View Code ACTIONSCRIPT
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.