In Part 1 – Dynamic DataGrid Columns of this Creating an enterprise Flex Grid series we quickly set up a ActionScript based grid that would let us programmatically set an arbitrary number of columns. In this part we will actually hook this grid to a data provider that can accommodate dynamic columns and seamlessly enable all DataGrid features to ‘just work’.
There is no good enterprise application without objects (if possible of course), so before we get start, some housekeeping first.
Step 1) Convert loose XML or Object to strongly typed
[Bindable] private var rows:ArrayCollection =
new ArrayCollection([
new {product: 'Ski', price0: 10, price1: 20... },
new {product: 'Sword', price0: 0, price1: 0, ...},
new {product: 'Beer', price0: 5, price1: 5,...}]);
To use this strongly typed object:
public class ProductPrices
{
public function ProductPrices() {}
public var product:String;
public var price0:Number;
public var price1:Number;
public var price2:Number;
public var price3:Number;
public var price4:Number;
public var price5:Number;
}
Step 2) Convert strong object to use internal Array within each row
We now have type safety on our columns and a somewhat structured data provider object that will represent each of the rows in the grid. What we are missing is the ability to have an arbitrary number of prices across time. Like our DataGridColumn problem this is easily fixed by converting to an array.
public class ProductPrices
{
public function ProductPrices() {}
public var product:String;
public var pricesAccrossTime:Array;
}
Problem – DataGrid only supports a flat dataProvider
Great except now how do we hook a row (ProductPrices) that has an internal Array (of prices) into our grid? This is not supported out of the box otherwise this article wouldn’t exist. i.e. You cannot have DataGridColumn.dataField = [ProductPrices.] “pricesAccrossTime[0]“, it needs to be [ProductPrices.] “price2″
We have 2 possibilities:
- Use a column labelFunction to return the value of pricesAccrossTime[columnIndex]
- Make our change not at the logical View but Model (data class ProductPrices) (think Model View Controller). This change will make our internal dynamic data representation totally transparent to our grid or chart (if we chose to expose this as a graph). This is the preferred solution here but I’d like to discuss why labelFunction is not enough to solve this problem
Why is labelFunction not enough?
Blurb: labelFunctions, sortComparers, groupingFunctions etc are great hooks provided by the Flex framework. While they will let you totally bypass some limitations of your Model they are really not always intended to replace it. Take labelFunction, some people use it to fetch some data (in the View) but really the fetching of data belongs to another layer (Model). Best use of label function would be for formatting reasons and not as an absolute replacement. We’ll talk more about this later.
Solution – labelFunction (kind of) to the rescue
Let’s go ahead and create the labelFunction solution now.
First we will need to create a custom DataGridColumn which will contain some extra information for us so we can easily map a column to an index in our pricesAccrossTime Array.
Add new ActionScript class CustomPriceColumn.as
package
{
import mx.controls.dataGridClasses.DataGridColumn;
public class CustomPriceColumn extends DataGridColumn
{
public function CustomPriceColumn(columnName:String=null)
{
super(columnName);
}
// This will let us track which price to use for each column
// think of this as a columnIndex for our prices
public var priceIndex:int;
}
}
Next we will create our labelFunction that will fetch the correct data at an index, we do this in main application.
/**
* Function that will return the price for each time period
*/
private function getPricesLabel(item:ProductPrices, column:CustomPriceColumn) : String
{
return item.pricesAccrossTime[column.priceIndex].toString();
}
Here is the complete labelFunction Solution, have a look at Source View to find a few other minor changes. We basically added a few helper methods to help us create our dynamic data provider and modified the column creation ever so slightly.
labelFunction Solution (click to run and view code):

Everything looks great doesn’t it? Seems like we are done. We now have dynamic columns tied to dynamic dataProvider. Well that’s only ½ true. Want to break the grid? Click on a header to sort, CRASH. This is only one way to now crash the grid, it’s mainly caused by the fact that we did not provide an explicit dataField for a column. The Flex DataGrid & more so AdvancedDataGrid is an incredibly powerful control, as is the underlying ArrayCollection>ListCollection infrastructure but the absolute key for all this to behave correctly is dataField.
I cannot stress this enough, unless you are writing your custom grid you must supply a dataField.
#1 rule of Flex
Always, always, always conform to default behaviour of a Flex control. In the DataGrid and Chart the default that controls almost everything is the column level dataField property. I’m not saying you can’t customize and hook into the controls, in fact you should since your logic can be best optimized by you, but be sure to respect the other 90% of the grid features you have not customized, they should still work!
Sure, in this example, the sort crashed because the grid did not know how to sort. You can supply a custom sortCompareFunction and the sort problem would be fixed. But I have to ask you: what else did you not do that might otherwise cause the grid to crash? How a about the Editor, making the columns editable would crash just the same, grouping using the Flex AdvancedDataGrid would also crash and this list goes on.
So let’s fix this default behaviour problem by supply the DataGrid with a dataField property, I know I know, we just had one then decided to remove it to make things ‘dynamic’, that’s true but read on Jedi. So how do we tell the grid this when our new object has an array and not a fixed property called price0, price1 etc?
The magic answer to this is a Flex class Proxy or ObjectProxy to be more specific. ObjectProxy lets the consumers of your typed object think they have a real property like price1 when it doesn’t really exist. For reasons of keeping this post from turning into a book I will let you Google Flex ObjectProxy and learn more about this topic but for now lets continue to the solution to our dynamic columns problem.
Flex ObjectProxy
Lets take a deep look at our ProductPrices class. We need to take public var pricesAccrossTime:Array; and expose it to the outside world as price0, price1 etc.
To accomplish this our ProductPrices cass needs to inherit from ObjectProxy. There are 3 main methods we need to override getProperty, setProperty & hasProperty. Think of these like this:
getProperty - trace(myObject.price0)
// here the getProperty is called to return the value of price0
setProperty - trace(myObject.price0 = 33)
// here the setProperty is called to set price0 to 33
(getProperty is also called here)
hasProperty - is used to ask our custom object
(ProductPrices) if it has some virtual property like price0.
We will add 2 custom methods that will help us wrap this logic
public function getPricesAccrossTimeAt(priceIndex:int) : Number
{
return pricesAccrossTime[priceIndex];
}
public function setPricesAccrossTime(value:Number, priceIndex:int) : void
{
pricesAccrossTime[priceIndex] = value
}
Now here is the magic (it’s not really magic and I hope in a few minutes you can realize this but for now it’s magic
), I have provided a few comments to clear things up but feel free to take all the time you need to absorb this.
ObjectProxy is probably the most powerful feature ActionScript gives us.
Once you master this you will never go back to hard coding dynamic properties, this comes with my own personal guarantee.
Ok, stop here
Breathe
Bathroom break
Coffee/tea/water
Ready?
Go start absorbing:
package
{
import flash.utils.flash_proxy;
import mx.utils.ObjectProxy;
// Need this to use dynamic properties
use namespace flash_proxy;
public class ProductPrices extends ObjectProxy
{
public function ProductPrices() {}
public var product:String;
// keep this public so we are able to access this from our own custom
// code it the View (application that has the DataGrid)
public var pricesAccrossTime:Array;
public function getPricesAccrossTimeAt(priceIndex:int) : Number
{
return pricesAccrossTime[priceIndex];
}
public function setPricesAccrossTimeAt(value:Number, priceIndex:int) : void
{
pricesAccrossTime[priceIndex] = value
}
/**
* Helps us figure out if we are a custom property that we want to explicitly handle
* .i.e. price0 is something we want to intercept but product or uid is not,
* we want Flex to handle this as it normaly would any property
*/
private function isPropertyPricesAccrossTime(propertyName:String) : Boolean
{
return (propertyName != null &&
propertyName.substr(0, priceAccrossTimeKey.length) == priceAccrossTimeKey);
}
// help us strongly track
// our new dataField format is same as before.
// The word 'price' followed by some index, ie.e price2
public static var priceAccrossTimeKey:String = "price";
//--------------------------------------------------------------------------
//
// ObjectProxy methods
//
//--------------------------------------------------------------------------
//////////////////////////////////////
// Object Proxy section
//////////////////////////////////////
override flash_proxy function getProperty(name:*):*
{
var propertyName:String;
if (name is QName) {
propertyName = (name as QName).localName;
}
// Custom hook, this is where we intercept
// the 'get' of something like price2
if(isPropertyPricesAccrossTime(propertyName))
{
return getPricesAccrossTimeAt(
parseInt(propertyName.replace(priceAccrossTimeKey, "")));
}
else
{
super.getProperty(name);
}
}
override flash_proxy function setProperty(name:*, value:*):void {
var propertyName:String;
if (name is QName) {
propertyName = (name as QName).localName;
}
// Custom hook, this is where we intercept
// the 'set' of something like price2
if(isPropertyPricesAccrossTime(propertyName))
{
setPricesAccrossTimeAt(value,
parseInt(propertyName.replace(priceAccrossTimeKey, "")));
}
else
{
super.setProperty(name, value);
}
}
override flash_proxy function hasProperty(name:*):Boolean
{
// Custom hook, this is where we intercept
// the 'has/contains' of something like price2
return (isPropertyPricesAccrossTime(name) || super.hasProperty(name));
}
}
}
Hopefully that was fun and it sunk in. Now the only additional changes we need to make in our Mxml is to re-add dataField property to column.
private function getPriceColumn(priceIndex:int, headerText:String) : DataGridColumn
{
var column:CustomPriceColumn = new CustomPriceColumn();
// we reintroduce this again since we now have an object that can supply us with this value
// our new dataField format is same as before. The word 'price' followed by some index
column.dataField = ProductPrices.priceAccrossTimeKey + name;
// we leave this as its useful meta data
column.priceIndex = priceIndex;
column.headerText = headerText;
//we keep labelFunction since we will want to actually use it for correct purpose later (like add $ symbol to price)
column.labelFunction = getPricesLabel;
return column;
}
Here is the complete solution for Part 2.
View Solution
In the next part of this series we will explore editing of this data and how to correctly capture changes that a user makes.
Until next time, keep your Flex sharp.
Part 3 Capturing changes in a dynamic dataProvider (Coming soon)