I am trying to use a FormItem as an ItemRenderer within a DataGroup - the purpose being to dynamically create a Form based upon an XMLlist as a data provider. The first part is a success in that the form items are successfully rendered, however my problem is that despite used the FormLayout in the DataGroup, my FormItem Labels are not aligning up with eachother.
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="955" minHeight="600"
>
<fx:Declarations>
<fx:XMLList id="_formModel" >
<control description="Weeks 1 Form Item" id="087B4626-B95E-731F-ACC7-80ED7373E083" label="One" />
<control description="Weeks 2 Form Item" id="AC546361-D9C5-E9F3-5F90-80EFA52EF54D" label="Second" />
<control description="Weeks 3 Form Item" id="9EE2AAA4-B9C3-68C0-D9B5-80F0374F940E" label="Three" />
<control description="Weeks 4 Form Item" id="3CCEABD6-12F9-E511-0C77-80F0A86902D5" label="The Fourth" />
</fx:XMLList>
<s:XMLListCollection id="_controls" source="{_formModel}"/>
</fx:Declarations>
<s:Scroller width="100%" height="100%">
<s:DataGroup width="100%" height="100%" dataProvider="{_controls}" >
<s:layout>
<s:FormLayout/>
</s:layout>
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer>
<s:FormItem label="{data.@label}" width="100%" >
<s:TextInput width="100%" text="{data.@description}"/>
</s:FormItem>
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:DataGroup>
</s:Scroller>
</s:Application>
Can anyone tell me how to align the form item labels whilst still using them within a Data Group as the purpose of the exercise is to create a form Dynamically based upon data fed into a DataGroup.
Thanks in advance.
Interesting question: I had some fun solving it and it turned out to be easier then I anticipated.
The issue is that FormItem
s must be direct children of the container that has the FormLayout
, however in this case the FormItem
is a child of ItemRenderer
which in turn is a child of the DataGroup
.
To fix the issue, you can create a subclass of FormItem
that also implements the IItemRenderer
interface. That way the DataGroup
will know what to do with the items, and the FormItem
s will be direct children of the FormLayout
.
First create the custom item renderer:
public class FormItemRenderer extends FormItem implements IItemRenderer {
private var _data:Object;
[Bindable("dataChange")]
public function get data():Object { return _data; }
public function set data(value:Object):void {
_data = value;
label = value ? value.@label : null;
if (hasEventListener(FlexEvent.DATA_CHANGE))
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
private var _itemIndex:int;
public function get itemIndex():int { return _itemIndex; }
public function set itemIndex(value:int):void {
if (value == _itemIndex) return;
_itemIndex = value;
invalidateDisplayList();
}
private var _selected:Boolean = false;
public function get selected():Boolean { return _selected; }
public function set selected(value:Boolean):void {
if (value != _selected) {
_selected = value;
invalidateDisplayList();
}
}
public function get dragging():Boolean { return false; }
public function set dragging(value:Boolean):void { }
public function get showsCaret():Boolean { return false; }
public function set showsCaret(value:Boolean):void { }
}
I only implemented the bare minimum to get this to work; you can add what you need later.
Also note the second line of the data
setter where we pass on the label
attribute of the XML node to the FormItem
's label
property. That's not a very generic way to do it, but I guess you can figure out a solution for that later, if need be.
Now use the custom renderer:
<s:DataGroup dataProvider="{_controls}">
<s:layout>
<s:FormLayout/>
</s:layout>
<s:itemRenderer>
<fx:Component>
<r:FormItemRenderer>
<s:TextInput width="100%" text="{data.@description}"/>
</r:FormItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:DataGroup>
You can use this solution with a DataGroup
out of the box, but if you want to use it with a List
(or any other component extending ListBase
) there's one additional thing to consider:
FormLayout
doesn't support layout virtualization, which is turned on by default for Lists (wouldn't make sense for normal use cases anyway). So you'll have to explicitly set useVirtualLayout
to false
to avoid NullPointerExceptions.
<s:List dataProvider="{_controls}" useVirtualLayout="false">
...
</s:List/>