I’ve been working on an Adobe AIR application that supports the administration of some XML content for a client. The source XML has a namespace assigned to it. And thus begins some more Flex/ActionScript shenanigans. I’ve searched the net for solutions to this and not found anything useful, so I hope this will help others.
For this post I’m simply trying to provide a viewer/editor for a list of authors. Here’s some sample XML:
<authors xmlns="http://www.example.com">
<author>
<honorific>Mr.</honorific>
<lname>McGoo</lname>
<fname>Jim</fname>
<mname>S.</mname>
<suffix>Esq.</suffix>
</author>
<author>
<honorific>Ms.</honorific>
<lname>McGoo</lname>
<fname>Jane</fname>
<mname>M.</mname>
<suffix></suffix>
</author>
<author>
<honorific>Mr.</honorific>
<lname>McGoo</lname>
<fname>Buddy</fname>
<mname>L.</mname>
<suffix>Jr</suffix>
</author>
</authors>
In my project I have an XMLListCollection with this XML content in it called authors. What I want to do is have one row per author and one column per author name element. Following is a rough example of where I started:
<AuthorGrid id="dg" width="100%" height="100%"
allowMultipleSelection="false" editable="true"
dataProvider="{authors}">
<columns>
<mx:DataGridColumn id="honorific" headerText="Title" dataField="honorific" />
<mx:DataGridColumn id="fname" headerText="First Name" dataField="fname" />
<mx:DataGridColumn id="mname" headerText="Middle Name" dataField="mname" />
<mx:DataGridColumn id="lname" headerText="Last Name" dataField="lname" />
<mx:DataGridColumn id="suffix" headerText="Suffix" dataField="suffix" />
</columns>
</AuthorGrid>
Unfortunately this does not work because ActionScript is namespace aware, but lots of Flex controls are not and we defined a non-standard default namespace on our root element. This namespace was then applied to each descendant as would be expected. I’ve found some samples on the net that define a separate labelFunction for each column. While this may be useful in some cases, for me it was a big waste, what I ended up doing was adding a labelFunction to the whole grid. This function simply adds a namespace to the dataField already defined in the grid above:
protected namespace ns = "http://www.example.com";
protected function labelFunction(item:Object, column:DataGridColumn):String {
use namespace ns;
return item[column.dataField].toString();
}
OK, so now I have a grid that shows my authors, unfortunately I want the grid to be editable, notice the editable=”true” in the grid definition. While the grid goes into edit mode any values typed into cells do not get back into the source XML. The best solution I’ve found for this is to subclass DataGrid, there are a couple of methods that handle marshaling information into and out-of edit mode for cells, we need to override these methods and make sure they are aware of our namespace:
package
{
import mx.controls.DataGrid;
public class AuthorGrid extends DataGrid
{
protected namespace ns = "http://www.example.com";
public function AuthorGrid()
{
super();
}
//default implementations of these two methods, intended for subclassing
//not checking if it really is a complex value here as the performance hit of doing this here is negligible
//compared with every display
override protected function getCurrentDataValue( data:Object, property:String ):String
{
use namespace ns;
// use namespace does not reach into a call to super so copied parents code
if ( !isComplexColumn( property ) )
return data[ property ];
var complexFieldNameComponents:Array = property.split( "." );
var obj:Object = deriveComplexFieldReference( data, complexFieldNameComponents );
return String( obj );
}
//Passing all of these parameters as it basically allows everything you would need to subclass for all sorts of fun implementations
override protected function setNewValue( data:Object, property:String, value:Object, columnIndex:int ):Boolean
{
use namespace ns;
// use namespace does not reach into a call to super so copied parents code
if ( !isComplexColumn( property ) )
{
data[ property ] = value;
}
else
{
var complexFieldNameComponents:Array = property.split( "." );
var lastProp:String = complexFieldNameComponents.pop();
var parent:Object = deriveComplexFieldReference( data, complexFieldNameComponents );
parent[ lastProp ] = value;
}
//The value they typed in is always converted to a string, but is the value actually a string in the dataprovider?
//unknown as it is cast by datagridcolumn before datagrid ever gets to know...
//control if this really causes an update in subclass
return true;
}
}
}
One last thing I wanted was for the honorific/title column to provide a list of possible values rather than the default TextInput when edit mode is invoked (The schema for my document requires one of a fixed set of values to be used.) The solution here is to update the definition for the honorific/title column as follows:
<mx:DataGridColumn id="honorific" headerText="Title" dataField="honorific" >
<mx:itemEditor>
<mx:Component>
<mx:ComboBox alpha="100" creationComplete="init()">
<mx:Script>
<![CDATA[
public function init():void {
var ns:Namespace = new Namespace("", "http://www.example.com");
var nm:XML = data as XML;
var ho:XML = nm.ns::honorific[0];
this.selectedItem = ho.toString();
}
]]>
</mx:Script>
<mx:String>Prof.</mx:String>
<mx:String>Dr.</mx:String>
<mx:String>Mr.</mx:String>
<mx:String>Mrs.</mx:String>
<mx:String>Ms.</mx:String>
<mx:String>Miss</mx:String>
</mx:ComboBox>
</mx:Component>
</mx:itemEditor>
</mx:DataGridColumn>
Well that sums up what it took to create an editable DataGrid bound to namespace qualified XML for our project. Not all that difficult in the end but it took quite a bit of digging to get here.