Sep/093
Introducing Compaction
I've been very quiet on the blogging front for the past few weeks. Although I would like to tell you that I have been relaxing on the beach somewhere, sipping cocktails and working a tan, it would be a lie. I have actually been spending my spare time creating a new open source project that I would like to introduce today.
The project I have been working on is called compaction, a small flex library designed to take some of the hard work out of creating CRUD based applications.
Some of the notable benefits of using the library include:
- Automatically detect user changes.
- Automatically undo user changes.
- Validate at the form level.
- Convention over configuration data binding.
- Disable buttons when validation fails.
- Disable buttons until changes have been made.
Although providing you with a lot of useful stuff, it has been designed to be as unobtrusive as possible. You can use compaction on a single form just like this:
<!-- The edit model does the heavy lifting --> <model:EditModel id="model" /> <mx:Form id="form" width="100%"> <mx:FormItem label="Name"> <mx:TextInput id="nameInput" /> </mx:FormItem> <mx:FormItem direction="horizontal"> <mx:Button id="saveButton" label="Save" /> <mx:Button id="cancelButton" label="Cancel"/> </mx:FormItem> </mx:Form> <!-- The binder wires the form components into the model --> <binder:FormBinder source="{model}" target="{form}" />
The edit model represents the state (editing, changed, valid, saving etc) of the editing process while the behaviour is encapsulated by a number of actions accessible from the model.
- model.edit (edit an object)
- model.cancel (undo user changes)
- model.save (persist user changes)
These actions are automatically enabled/disabled based on the state of the model. They also display useful tool tips when they are disabled to reduce confusion.
The form binder uses a convention over configuration approach to wire the form components into the model. I wont go into the conventions in this post but bindings on input fields are two way with user changes commited on "change" by default.
That concludes my very quick introduction to compaction, one of my main goals at this stage is to create a small sample application. I'd rather not reinvent the wheel here so if you know of an existing project that might benefit please let me know!
Sep/095
Flex Validation – Future Directions
I'm currently working on creating a small flex library with the goal of simplifying the task of implementing common user interface logic. One of the areas that is of particular interest to me is validation. Although the built in flex validators are useful they lack some important functionality needed to use them in any non trivial application.
In particular, the flex validators are quite narrowly focused on the validation of individual components like the text input or date chooser. While that's great it limits our ability to easily validate an entire form or re-use our validators outside mxml.
In the Java world when you want to validate an object you implement a single method that executes the desired validation routines and collects the results.
public class PersonValidator implements Validator { public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); Person p = (Person) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old"); } } }
I think this is a great approach for three reasons:
- You can use your validators everywhere, not just in the UI
- Your validation routines are all grouped together in a single spot
- You can easily unit test your validation
So naturally I'm interested in bringing something like this to the Flex world. Using a fluent builder pattern I think I am getting to a position where the validation process is becoming fairly straightforward e.g.
public class CustomerValidator implements Validator { public var blackList:ArrayCollection = new ArrayCollection(["a@a.com"]); public function validate(builder:ValidationBuilder, item:Object): void { var customer:Customer = Customer(item); builder.string(customer.name, "name").notEmpty().minLength(3); builder.number(customer.age, "age").between(0, 110); builder.string(customer.email, "email").validEmail().notIn(blackList); } }
When writing a validator most of the heavy lifting is done by interacting with the builder:
public interface ValidationBuilder { function get routines(): ValidationRoutines; function get messages(): ValidationMessages; function addError(error:String, key:String=null); function hasErrors(): Boolean; function isNull(value:Object, key:String=null): ValidationBuilder; function isNotNull(value:Object, key:String=null): ValidationBuilder; function string(value:String, key:String=null) : StringValidationBuilder; function number(value:Number, key:String=null) : NumberValidationBuilder; function date(value:Date, key:String=null) : DateValidationBuilder; }
You can easily use the the results of your validators in mxml using syntax similar to this...
<v:ValidationListener source="{validator}" target="{nameText}" key="name"/> <v:ValidationListener source="{validator}" target="{ageText}" key="age"/> <v:ValidationListener source="{validator}" target="{emailText}" key="email"/>
You'll notice that several things are missing from this small overview including how you trigger validation and what happens with the results. The short story is that validation is just one component in the overall functionality required when editing objects.
I'm working on simple solutions to the overall problem but today I wanted to discuss how the validation side of things is shaping up.
Aug/090
Flex Snippet – Automatic Dirty Checking
In order to prepare for an upcoming tutorial on flex unit testing I need to present a couple of simple concepts that I can build upon to create some useful example code.
The first of these is the concept of automatic dirty checking. You can use dirty checking to determine if any properties on given object have changed since you last looked at it. This technique is quite common in applications where users are editing mission critical data. It can be used to prevent users from forgetting to save something they have changed. An example is probably the easiest way to explain it:
public function close(): void { if(importantDataWasChanged) { var saveFirst:Boolean = askUserIfTheyWantToSaveBeforeClosing(); if(saveFirst) { saveImportantInfo(); } } // perform close }
There are various ways you can do this but I want to present an approach that is both easy to use and more importantly, easy to test. The first rule of making something easy to test, create an interface:
ChangeDetector.as
1 2 3 4 5 6 7 8 9 | package com.compact { /** * Simple interface for observing changes in a given object. */ public interface ChangeDetector { function watch(object:Object):void; function get changed():Boolean; } } |
Basically we want to use this interface to watch a single object at a time and use the changed property to query if that object has been modified since we began watching. We can achieve this by 'cloning' the object when we call watch and then comparing the clone to the current version of the object.
CloningChangeDetector.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.compact { import mx.utils.ObjectUtil; /** * Uses a clone and compare technique to determine if a given object * has changed/is dirty. */ public class CloningChangeDetector implements ChangeDetector { private var _before:Object; private var _now:Object; public function watch(object:Object):void { _before = ObjectUtil.copy(object); _now = object; } public function get changed():Boolean { return ObjectUtil.compare(_before, ObjectUtil.copy(_now)) != 0; } } } |
And to prove it all works:
CloningChangeDetectorTest.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 | package com.compact { import flexunit.framework.TestCase; public class CloningChangeDetectorTest extends TestCase { private var _detector:ChangeDetector; override public function setUp():void { _detector = new CloningChangeDetector(); } public function testChangedReturnsFalseByDefault():void { assertEquals(false, _detector.changed); } public function testChangedReturnsFalseIfNothingChanged():void { var _watched:Object = new Object(); _watched.name = "fred" _detector.watch(_watched); assertEquals(false, _detector.changed); } public function testChangedReturnsTrueIfNameChanged():void { var _watched:Object = new Object(); _detector.watch(_watched); _watched.name = "fred" assertEquals(true, _detector.changed); } public function testChangedReturnsFalseIfNameChangeReversed():void { var _watched:Object = new Object(); _watched.name = "fred" _detector.watch(_watched); _watched.name = "bob" _watched.name = "fred" assertEquals(false, _detector.changed); } public function testDetectsNoChangeOnTypedObjects():void { var _watched:Customer = new Customer(); _detector.watch(_watched); assertEquals(false, _detector.changed); } public function testDetectsChangesOnTypedObjects():void { var _watched:Customer = new Customer(); _detector.watch(_watched); _watched.name = "fred" assertEquals(true, _detector.changed); } } } class Customer { public var name:String; } |
Voila, your good to go!
Aug/094
Unit Testing Flex – More MXML
This is a continuation of my previous post. Today I am going to show you three more simple techniques you can use to unit test your mxml:
- Button Clicks
- List Selection
- DataGrid Rendering
Button Clicks
ClickForm.mxml
1 2 3 4 5 6 7 8 9 10 | <?xml version="1.0" encoding="utf-8"?> <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:compact="com.compact.*"> <compact:ClickModel id="model" /> <mx:Button id="button" label="Increment" click="model.incrementClickCount()"/> </mx:HBox> |
ClickModel.as
1 2 3 4 5 6 7 8 | package com.compact { public class ClickModel { public var clickCount:int = 0; public function incrementClickCount(): void { clickCount++; } } } |
ClickTest.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.compact { import flash.events.MouseEvent; import flexunit.framework.TestCase; public class ClickTest extends TestCase { private var _form:ClickForm; override public function setUp():void { _form = new ClickForm(); _form.initialize(); } public function testClickingButtonIncrementsClickCount(): void { _form.button.dispatchEvent(new MouseEvent(MouseEvent.CLICK)); assertEquals(1, _form.model.clickCount); } } } |
List Selection
SelectionForm.mxml
1 2 3 4 5 6 7 8 9 10 | <?xml version="1.0" encoding="utf-8"?> <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:compact="com.compact.*"> <compact:SelectionModel id="model"/> <mx:List id="listView" dataProvider="{model.list}" change="model.selected = listView.selectedItem"/> </mx:HBox> |
SelectionModel.as
1 2 3 4 5 6 7 8 | package com.compact { import mx.collections.ArrayCollection; [Bindable] public class SelectionModel { public var list:ArrayCollection = new ArrayCollection(); public var selected:Object; } } |
SelectionTest.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.compact { import flexunit.framework.TestCase; import mx.events.ListEvent; public class SelectionTest extends TestCase { private var _form:SelectionForm; override public function setUp():void { _form = new SelectionForm(); _form.initialize(); } public function testListViewSelectionUpdatesModel(): void { var itemOne:Object = new Object(); var itemTwo:Object = new Object(); _form.model.list.addItem(itemOne); _form.model.list.addItem(itemTwo); _form.listView.selectedItem = itemTwo; _form.listView.dispatchEvent(new ListEvent(ListEvent.CHANGE)); assertEquals(itemTwo, _form.model.selected); } } } |
DataGrid Rendering
DataGridForm.mxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?xml version="1.0" encoding="utf-8"?> <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:compact="com.compact.*"> <compact:DataGridModel id="model" /> <mx:DataGrid id="customerGrid" dataProvider="{model.customers}"> <mx:columns> <mx:DataGridColumn id="nameCol" dataField="name"/> <mx:DataGridColumn id="birthCol" dataField="birth" labelFunction="model.formatBirth"/> </mx:columns> </mx:DataGrid> </mx:HBox> |
DataGridModel.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.compact { import mx.collections.ArrayCollection; import mx.controls.dataGridClasses.DataGridColumn; import mx.formatters.DateFormatter; [Bindable] public class DataGridModel { [ArrayElementType("com.compact.Customer")] public var customers:ArrayCollection = new ArrayCollection(); private var _format:DateFormatter = new DateFormatter(); public function formatBirth(item:Object, col:DataGridColumn): String _format.formatString = "MMMM/YYYY"; return _format.format(item.birth); } } } |
Customer.as
1 2 3 4 5 6 7 | package com.compact { [Bindable] public class Customer { public var name:String public var birth:Date; } } |
DataGridTest.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.compact { import flexunit.framework.TestCase; public class DataGridTest extends TestCase { private var _form:DataGridForm; override public function setUp():void { _form = new DataGridForm(); _form.initialize(); // Initializes the datagrid columns. _form.customerGrid.validateProperties(); } public function testNameIsRenderedAsIs(): void { var customer:Customer = new Customer(); customer.name = "foo"; assertEquals("foo", _form.nameCol.itemToLabel(customer)); } public function testBirthIsRenderedUsingDateFormat(): void { var customer:Customer = new Customer(); customer.birth = new Date(2000, 0, 1); assertEquals("January/2000", _form.birthCol.itemToLabel(customer)); } } } |
Do you find this useful? Does it need more explanation? I'm not really sure what people are looking for so I have left this post as lightweight as possible.
As requested my next post will start looking at some slightly more complicated tests and how we can use mock objects to simplify them.
Aug/090
Unit Testing Flex – MXML
As promised previously this is my first attempt at sharing some of my accumulated knowledge on the topic of unit testing in Flex.
If you need a general introduction to unit testing in flex I would recommend reading this blog.
In this article I'll show you some simple techniques you can use to test your mxml:
- Data Binding
- Validators
CustomerEditForm.mxml
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="utf-8"?> <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" model="com.compact.*"> <model:CustomerEditModel id="model" /> <mx:Form width="100%"> <mx:FormItem label="Name"> <mx:TextInput id="nameText" text="{model.name}"/> </mx:FormItem> <mx:FormItem label="Age"> <mx:TextInput id="ageText" text="{model.age}"/> </mx:FormItem> </mx:Form> </mx:VBox> |
This form is quite straight forward containing two text fields that display values obtained from the presentation model, listed below.
CustomerEditModel.as
1 2 3 4 5 6 7 | package com.compact { [Bindable] public class CustomerEditModel { public var name:String; public var age:int; } } |
The first thing we want to do is ensure that our text fields are being automatically populated from the values in our model. We can check this using the following Flex Unit test:
CustomerEditFormTest.as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.compact { import flexunit.framework.TestCase; public class CustomerEditFormTest extends TestCase { private var _form:CustomerEditForm; override public function setUp():void { _form = new CustomerEditForm(); _form.initialize(); } public function testChangingModelNameUpdatesNameText(): void { _form.model.name = "fred"; assertEquals("fred", _form.nameText.text); } public function testChangingModelAgeUpdatesAgeText(): void { _form.model.age = 25; assertEquals("25", _form.ageText.text); } } } |
In this test we create an instance variable to hold a reference to the form we are testing. The setUp method is invoked before every test method. This ensures that we have a clean form for each test and prevents tests interfering which each other. An important thing to note is that we need to initialize the form manually before we can access any of its components. Once we have all that out of the way the tests themselves are pretty simple, we set a value on the model and make sure the text fields on the form reflect that value.
Now lets say we we want to add validation to our age field so that our users know that they should only enter numbers. We can do this by creating a standard flex NumberValidator and binding it to our age field. I'm going to mix things up a bit and write my test before I go changing the form.
CustomerEditFormTest.as
18 19 20 21 22 23 24 25 26 | public function testAgeTextValidWhenTextContainsOnlyNumbers(): void { _form.ageText.text = "25"; assertEquals("", _form.nameText.errorString); } public function testAgeTextInvalidWhenTextContainsLetters(): void { var expected:String = "The input contains invalid characters."; _form.ageText.text = "twenty five"; assertEquals(expected, _form.ageText.errorString); } |
In these tests I am expecting that the errorString property of the age text field to be changed by the results of the validation process. If you look at the documentation for errorString you will see this description.
The text that will be displayed by a component's error tip when a component is monitored by a Validator and validation fails.
Basically it works like this: If the value of the errorString is empty then the component is valid, on the other hand if the errorString is not empty we know that it has been marked invalid by a Validator.
As they stand these tests fail, but we can fix that by adding the following line to our form:
CustomerEditForm.mxml
12 | <mx:NumberValidator source="{ageText}" property="text" /> |
And now let us take a moment to marvel at the result our hard work...

Congratulations, you are now unit testing with Flex! Although I had planned on covering more in this article I don't want to risk turning it into a small novel. I appreciate your attention and I hope that you found this small exercise useful. Thats all for now.