Check boxes just on tree leaf nodes in Flex - apache-flex

I'm having difficulty getting checkboxes on only the leaf nodes of a tree.
Before anyone links it, I've seen http://www.sephiroth.it/file_detail.php?id=151# and this isn't exactly what I need. I don't want a 3-state checkbox system including both branch and leaf.
I understand applying the checkbox item renderer to a data grid but not on a tree.
I'm using Flex Builder 3

Let's say that we want to put the Checkbox in one of the columns of AdvancedDataGrid. I like using HierarchicalData or HierarchicalCollectionView as my datagrid's dataProvider:
// TestGrid
<mx:AdvancedDataGrid id="myADG">
<mx:columns>
<AdvancedDataGridColumn id="col1" />
<AdvancedDataGridColumn id="col2" itemRenderer="LeafCheckbox" />
</mx:columns>
</mx:AdvancedDataGrid>
// LeafCheckBox.mxml
<mx:Box
creationComplete="init(event)"
implements="IDropInListItemRenderer">
<mx:Script>
<![CDATA[
// Internal variable for the property value.
private var _listData:BaseListData;
// Make the listData property bindable.
[Bindable("dataChange")]
// Define the getter method.
public function get listData():BaseListData
{
return _listData;
}
// Define the setter method,
public function set listData(value:BaseListData):void
{
_listData = value;
}
private function init(event:Event):void {
var dg:AdvancedDataGrid = this.listData.owner.parent as AdvancedDataGrid;
if (!dg.dataProvider.hasChildren(dg.selectedItem))
this.addChild(new CheckBox());
}
]]>
</mx:Script>
</mx:Box>
That should be most of it. Let me know, thanks!

Related

ItemRender data change

I have a List with an ItemRenderer. When I click a button, then a property of the ArrayCollection I bound to the list is changed.
When I click the button, then it does change the property, but the list doesn't change.
How do I solve this.
Here's my code
<fx:Script>
<![CDATA[
[Bindable]
public var controllers:ControllerCollection = new ControllerCollection();
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
//these methods don't work unfortunatly
controllers.itemUpdated(controllers[0]);
controllers.refresh();
}
]]>
</fx:Script>
<mx:List id="listControllers" dataProvider="{controllers}">
<mx:itemRenderer>
<fx:Component>
<solutionItems:displaySolutionItem visible="{data.meetsRequirements}" />
</fx:Component>
</mx:itemRenderer>
</mx:List>
<mx:Button label="test" click="hideTheFirstItem(event)" />
(ControllerCollection extends ArrayCollection)
Thanks!
Vincent
Two ways:
collection.refresh()
collection.itemUpdated()
Of course, ControllerCollection is not a standard Flex Collection class; so I am just assuming that it implements the ICollectionView interface.
Update:
I do notice that your code is set to modify the first element of the ArrayCollection
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
//these methods don't work unfortunatly
controllers.itemUpdated(controllers[0]);
controllers.refresh();
}
I wanted to be sure to specify that the first element of the collection may not be the first element currently visible in the view. I wonder if that is causing you issues.
Without seeing your item renderer, I need to make some assumptions.
First, I will assume that your item renderer is using data binding to the meetsRequirements property. If that is the case, then the meetsRequirements property needs to notify when that property changes. If you add the [Bindable] tag to that property or the Controller class, then the meetsRequirements property will notify the itemRenderer to update that field based on your data binding.
If my assumptions are wrong, we need to see the code to give you any further thoughts.
First, don't try to create new collections if you don't need to.
I believe your problem lies with this statement: (controllers[0] as Controller).meetsRequirements = false; which should fail on compile because a collection item cannot be retrieved using the square bracket annotation. You need to use getItemAt(index:int) function.
Furthermore, you wouldn't want to set visible to false to an item renderer if you want to 'remove' it because then you'd have an empty spot. What you want to do is filter it out:
<s:Application creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] public var data:ArrayCollection = new ArrayCollection();
private function onCreationComplete():void
{
// TODO: need to add data here
// Adding filter function
data.filterFunction = filterItems;
}
private function filterItems(item:Object):Boolean
{
return item.meetsRequirements;
}
private function hideFirstItem():void
{
if(data.length > 0)
{
Controller(data.getItemAt(0)).meetsRequirements = false;
}
data.refresh();
}
]]>
</fx:Script>
<mx:List id="listControllers" dataProvider="{data}" />
<mx:Button label="test" click="hideFirstItem()" />
</s:Application>
This should do it. Untested though.
Try this:
<fx:Script>
<![CDATA[
[Bindable(Event="refreshMyList")]
public var controllers:ControllerCollection = new ControllerCollection();
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
dispatchEvent(new Event("refreshMyList"));
}
]]>
</fx:Script>

Flex 4 . Passing data from Main App to custom component

I havent found quite what I want elsewhere or at least am not accomplished enough to adapt
I'm trying to create a project that shows radio stations with logo, listen button etc
The datasource is an xml file with child tags like 'name','image' and 'listen' for each parent, 'station'.
I access this data via the main application with creationComplete="radioService.send()"
I can then use it on the main app via dataProvider="{radioService.lastResult.stations.station}
However, I want to use the results to populate variables in the custom component in both the components themselves
hard coded examples
mx:Image source="Images/BBC5.gif" id="bbc5Logo" "/>
mx:LinkButton id="bbc5Listen" click="bbc5Listen_clickHandler(event)" />
and in the clickhandler
protected function bbc5Listen_clickHandler(event:MouseEvent):void
{
var url:String = "http://www.bbc.co.uk/iplayer/console/bbc_radio_five_live";
var request:URLRequest = new URLRequest(url);
}
Any help, much appreciated
I done something similar before though I am not sure of what look and feel you wish your app to have but it would be possible to create a Datagrid and bind the datasource to that grid.
Then you can add fields to the datasource like ( img field and use the link in the XML by setting the xpath towards that field) , it would also be possible to create a button and place that in the row of the datagrid for listen.
Obviously this is a slightly different implementation but the same principle applys if you have created a custom component then make sure it makes use of DataSource and DataSource fields which will aquire the data from the right part of the XML. Then it would be possible to map that DataSource field to a String for example.
Hope this helps
You can actually use Custom Events, where you can pass values from your main app to your custom component.
http://www.anujgakhar.com/2009/03/04/flex-passing-data-with-custom-events/
Another approach could be to create Value Objects from your XML and bind your components to these objects, this way you would decouple your components from the XML , which can be handy if at some point in the future, you don't want to use XML, but JSON for instance.
Each Value Object would have the properties defined in your question , i.e , imageSource , id , url etc... and each component would be bound to one Value Object
Edit
Have a look at this video training, it covers data retrieval from an XML , ArrayCollections of ValueObjets etc...
http://www.adobe.com/devnet/flex/videotraining.html
there are two ways to accomplish this:
you should wrap your data structure
from xml source to a [Bindable] actionscript
object (not mandatory, but you can
handle it easier), and bind the array of these data instances (let's say RadioChannel.class) as the dataProvider of your grid. Inside your itemRenderer implementation you can access the current row's RadioChannel instance via the data getter/setter:
RadioChannel.as:
[Bindable]
public class RadioChannel {
private var _name: String;
private var _imageUrl: String;
private var _listenUrl: String;
public function RadioChannel()
{
}
public function get name():String {return _name;}
public function set name(value:String):void {_name = value;}
public function get imageUrl():String {return _imageUrl;}
public function set imageUrl(value:String):void {_imageUrl = value;}
public function get listenUrl():String {return _listenUrl;}
public function set listenUrl(value:String):void {_listenUrl = value;}
public function toString():String
{
return 'RadioChannel ' + name + ' - image: ' + imageUrl +
' | listen at ' + listenUrl;
}
}
RadioList.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:DataGrid xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx" dataProvider="{radioChannels}">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var _radioChannels:ArrayCollection = new ArrayCollection();
[Bindable]
private function get radioChannels():ArrayCollection {return _radioChannels;}
private function set radioChannels(value:ArrayCollection):void {_radioChannels = value;}
]]>
</fx:Script>
<mx:columns>
<mx:DataGridColumn>
<mx:itemRenderer>
<fx:Component>
<mx:Image source="{data.imageUrl}">
<fx:Script>
<![CDATA[
public override function set data(value:Object):void
{
super.data = value;
trace('current instance: ' + RadioChannel(data).toString());
}
]]>
</fx:Script>
</mx:Image>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn dataField="name" />
<mx:DataGridColumn>
<mx:itemRenderer>
<fx:Component>
<mx:HBox>
<fx:Script>
<![CDATA[
protected function onLinkClicked(event:MouseEvent):void
{
var currentChannel:RadioChannel = RadioChannel(data);
if (data)
{
var request:URLRequest = new URLRequest(currentChannel.listenUrl);
[...]
}
}
]]>
</fx:Script>
<mx:Button click="onLinkClicked(event)" label="Listen" />
</mx:HBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
The other way is to build up custom
events that deal with an extra
parameter: the url of the channel.
These custom events should cascade back to the list component, where they should be handled.
Since for the second solution you would still need the first's binding as well, i'd choose the first one, and handle the click event locally.
hope this helped.
I think this may be useful, to pass the value from mainApp to custom component you can follow this method.Since you have the value in mainApp,in the custom component you can access the value by using parentDocument object.parentDocument.rootAppVar

How do I create a bidirectional data binding in Flex 3?

I need to bind a property to an edit control and have the control write its value back to the same property. The problem is, I'm setting the source value before the control is created:
<mx:Panel>
<mx:Script>
<![CDATA[
[Bindable] public var editedDocument: XML;
]]>
</mx:Script>
<mx:TextInput id="docLabel" text="{editedDocument.#label}"/>
<mx:Binding source="docLabel.text" destination="editedDocument.#label"/>
</mx:Panel>
I call this like so:
var xmlDoc: XML = <document label="some label" />;
var myPanel: MyPanel = new MyPanel();
myPanel.editedDocument = xmlDoc;
parent.addChild(myPanel);
What happens is this:
the docLabel text field ends up blank (equal to "")
the xmlDoc's #label attribute is set to ""
What I want is this:
the docLabel text field should contain "some label"
the xmlDoc's #label attribute should change only when the docLabel's text property changes.
How do I accomplish this, using Flex 3?
Edit
I have also tried this:
<mx:Panel>
<mx:Script>
<![CDATA[
[Bindable] public var editedDocument: XML;
]]>
</mx:Script>
<mx:TextInput id="docLabel"/>
<mx:Binding source="editedDocument.#label" destination="docLabel.text"/>
<mx:Binding source="docLabel.text" destination="editedDocument.#label"/>
</mx:Panel>
The result is the same.
You can try using BindingUtils to programmatically create the binding after the class has been created:
http://life.neophi.com/danielr/2007/03/programmatic_bindings.html
There are many variations of this that I've used to tackle similar problems. If you can't figure it out from the link post a comment and I'll dig through my source code and see what I can find.
private function init():void
{
var xmlDoc: XML = <document label="some label" />;
var myPanel: MyPanel = new MyPanel();
myPanel.editedDocument = xmlDoc;
parent.addChild(myPanel);
BindingUtils.bindProperty(docLabel, "text", editedDocument, "label");
//or maybe it should be one of these, I have not done binding to an XML attribute before
BindingUtils.bindProperty(docLabel, "text", editedDocument, "#label");
BindingUtils.bindProperty(docLabel, "text", editedDocument, "{#label}");
}
Take a look at Two-way data binding.
Take a look at the part of the text:
In Flex 3, if you want to set
two-way binding using the
mx:Binding
tag you need to set it twice:
mx:Binding source="a.property" destination="b.property"/>
mx:Binding source="b.property" destination="a.property"/>
which becomes:
mx:Binding source="a.property" destination="b.property" twoWay="true"/>
In Flex 3 you would be better of doing something like this. Also not sure you can bind directly to XML?
Instead do something like this:
[Bindable] public var tmpString: String;
public var onChange():void {
tmpString = docLabel.text;
//set the XML string, etc.
}
]]>
</mx:Script>
<mx:TextInput id="docLabel" text="{tmpString}" change="onChange()" />
I think bidirectional data binding is a new feature in Flex 4.
This is straight from Adboe http://opensource.adobe.com/wiki/display/flexsdk/Two-way+Data+Binding, and it's Flex 3 too!
I created custom controls that programmatically create two-way bindings when given a provider object that has a suitable property whose name matches the control's id. Here's an example for a TextInput:
public class BoundTextInput extends TextInput
{
// some code omitted for brevity:
// Create getter / setter pair, call invalidateProperties()
// and set internal flag for efficiency
// create bindings in commitProperties:
override protected function commitProperties():void
{
if (this._fProviderChanged) {
this._fProviderChanged = false;
if (null != this._provider && this._provider.hasOwnProperty(this.id) && this._provider[this.id] is String) {
// this is the core bit
BindingUtils.bindProperty(this, "text", this._provider, this.id);
BindingUtils.bindProperty(this._provider, this.id, this, "text");
}
}
// Normally, you call the overridden method first,
// but we want to see the values initialized by the new
// binding right away, so we first create the bindings
// and then commit all inherited properties
super.commitProperties();
}
}
This is an example of how I use it inside one of my other components (a popup dialog). The data property is set to an instance of the appropriate model class, which is always a dumb, [Bindable] container.
<?xml version="1.0" encoding="utf-8"?>
<PopUp xmlns="com.econemon.suite.gui.components.*" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Form width="100%" height="100%" >
<mx:FormHeading label="Server-URL" />
<mx:FormItem label="URL" >
<!--
If it is available, and of type String, data.urlServer
is bound to the text property of this TextInput
-->
<BoundTextInput id="urlServer" provider="{this.data}"/>
</mx:FormItem>
<mx:FormItem>
<mx:Button label="OK" click="this.submit(event)" />
</mx:FormItem>
</mx:Form>
</PopUp>

Flex DataGrid with ComboBox itemRenderer

I'm going spare trying to figure out the "correct" way to embed a ComboBox inside a Flex (3.4) DataGrid. By Rights (e.g. according to this page http://blog.flexmonkeypatches.com/2008/02/18/simple-datagrid-combobox-as-item-editor-example/) it should be easy, but I can't for the life of me make this work.
The difference I have to the example linked above is that my display value (what the user sees) is different to the id value I want to select on and store in my data provider.
So what I have is:
<mx:DataGridColumn headerText="Type" width="200" dataField="TransactionTypeID" editorDataField="value" textAlign="center" editable="true" rendererIsEditor="true">
<mx:itemRenderer>
<mx:Component>
<mx:ComboBox dataProvider="{parentDocument.transactionTypesData}"/>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
Where transactionTypesData has both 'data' and 'label' fields (as per what the ComboBox - why on earth it doesn't provide both a labelField and idField I'll never know).
Anyway, the above MXML code doesn't work in two ways:
The combo box does not show up with any selected item.
After selecting an item, it does not store back that selected item to the datastore.
So, has anyone got a similar situation working?
While Jeff's answer is a partial answer for one approach for this (see http://flex.gunua.com/?p=119 for a complete example of this being used to good effect), it isn't as general as I wanted.
Thankfully, I finally found some great help on Experts Exchange (the answers by hobbit72) describes how to create a custom component that works in a grid as a ItemRenderer.
I've extended that code to also support using the combo box as an ItemEditor as well. The full component is as follows:
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox
xmlns:mx="http://www.adobe.com/2006/mxml"
dataChange="setSelected()"
change="onSelectionChange(event)"
focusEnabled="true">
<mx:Script>
<![CDATA[
import mx.events.DataGridEvent;
import mx.events.ListEvent;
import mx.controls.dataGridClasses.DataGridListData;
private var _ownerData:Object;
private var _lookupField:String = "value";
// When using this component as an itemEditor rather than an itemRenderer
// then set ' editorDataField="selectedItemKey"' on the column to
// ensure that changes to the ComboBox are propogated.
[Bindable] public var selectedItemKey:Object;
public function set lookupField (value:String) : void {
if(value) {
_lookupField = value;
setSelected();
}
}
override public function set data (value:Object) : void {
if(value) {
_ownerData = value;
setSelected();
}
}
override public function get data() : Object {
return _ownerData;
}
private function setSelected() : void {
if (dataProvider && _ownerData) {
var col:DataGridListData = DataGridListData(listData);
for each (var dp:Object in dataProvider) {
if (dp[_lookupField] == _ownerData[col.dataField]) {
selectedItem = dp;
selectedItemKey = _ownerData[col.dataField];
return;
}
}
}
selectedItem = null;
}
private function onSelectionChange (e:ListEvent) : void {
if (selectedItem && _ownerData) {
var col:DataGridListData = DataGridListData(listData);
_ownerData[col.dataField] = selectedItem[_lookupField];
selectedItemKey = selectedItem[_lookupField];
}
}
]]>
</mx:Script>
</mx:ComboBox>
Using this component is straight forward. As an ItemRenderer:
<mx:DataGridColumn headerText="Child" dataField="PersonID" editable="false" textAlign="center">
<mx:itemRenderer>
<mx:Component>
<fx:GridComboBox dataProvider="{parentDocument.childrenData}" labelField="Name" lookupField="PersonID" change="dispatchEvent(new mx.events.DataGridEvent(mx.events.DataGridEvent.ITEM_FOCUS_OUT, true, true))"/>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
Using this component is straight forward. And as an ItemEditor:
<mx:DataGridColumn labelFunction="lookupChildName" headerText="Child" dataField="PersonID" editable="true" editorDataField="selectedItemKey">
<mx:itemEditor>
<mx:Component>
<fx:GridComboBox dataProvider="{parentDocument.childrenData}" labelField="Name" lookupField="PersonID" change="dispatchEvent(new mx.events.DataGridEvent(mx.events.DataGridEvent.ITEM_FOCUS_OUT, true, true))"/>
</mx:Component>
</mx:itemEditor>
</mx:DataGridColumn>
Note that when using it as an ItemEditor, a custom labelFunction (that looks up the Name from the PersonID in my case) must be used, otherwise you only see the key in the grid when the field isn't being edited (not a problem if your keys/values are the same).
Note that in my case, I wanted the item focus out event to propogate up to provide immediate feedback to the user (my DataGrid has itemFocusOut="handleChange()"), hence the change event creating an ITEM_FOCUS_OUT event.
Note that there are probably simpler ways to have a ComboBox as an ItemEditor when you don't mind the ComboBox only shown when the user clicks on the cell to edit. The approach I wanted was a generic way to show a combo box in a DataGrid for all rows, and being editable and with decent event propogation.
The easiest way to add itemRenderers to DataGrids is to make a custom MXML component. In your case make a canvas, HBox, or VBox as the custom component and add the combobox as a child.Set the dataProvider on the dataGrid itself and assign the itemRenderer to the column, and then override the set data function of the itemRenderer to access all data from the given data provider for that instance as seen below:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void{
trace(value.data);
trace(value.name);
}
]]>
</mx:Script>
<mx:ComboBox width="100%" height="100%" id="myComboBox"/>
</mx:HBox>
This method will be called for each instance of the itemRenderer
In my case I used a spark datagrid where one of the columns has an ItemRenderer that utilises a DropDownListBox. My problem was that when my item list change, the DropDownLists doesn't get updated with the new dataProvider. To solve this, I had to pass the dataProvider for the DropDownListBox as part of the data (of the ItemRenderer), and then by overriding the setter of the data to just assign the DropDownlListBox's dataProvider. Probably a bit of overhead, but if someone have a better solution, please let me know:
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
override public function set data(v : Object) : void {
super.data = v;
if (v == null)
return;
dropDown.dataProvider = data.dataProvider;
}
]]>
</fx:Script>
<s:DropDownList id="dropDown" width="100%" height="100%" dataProvider="{data.dataProvider}" labelField="name"/>

Why is AdvancedDataGrid not updating when the HierarchicalData dataProvider is updated?

I have an AdvancedDataGrid (ADG) with a HierarchicalData dataProvider:
<mx:AdvancedDataGrid xmlns:mx="http://www.adobe.com/2006/mxml"
dataProvider="{__model.myHierarchicalData}"
displayItemsExpanded="true" sortExpertMode="true" dropEnabled="true"
sortableColumns="false" draggableColumns="false"
resizableColumns="true" textAlign="left" defaultLeafIcon="{null}"
folderOpenIcon="{null}" folderClosedIcon="{null}"/>
When I initially set the HierarchicalData instance in the Model, it is displayed as expected:
function buildHierarchicalData(parentItems:ArrayCollection):void
{
__model.myHierarchicalData = new HierarchicalData();
__model.myHierarchicalData.source = parentItems;
}
parentItems is a Collection of ParentItem valueObjects:
package
{
[Bindable]
public class ParentItem
{
public var children:ArrayCollection;
public var label:String;
}
}
However, when I move child items from one parent to another (via drag-and-drop), the update is not visible, using this code:
function moveChildren(movedChildren:Array /* of ParentItem */):void
{
parentItem.children = new ArrayCollection(movedChildren);
}
For some reason, however, this DOES work:
function moveChildren(movedChildren:Array /* of ParentItem */):void
{
parentItem.children.source = movedChildren;
}
Why do I have to update the source of the ArrayCollection???
Thanks to dirkgently for directing me to the answer. I am now eliminating the need for a HierarchicalData property in my model, and instead setting the Hierarchical dataProvider right in the MXML:
<mx:AdvancedDataGrid xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:dataProvider>
<mx:HierarchicalData source="{__model.parentItems}" />
</mx:dataProvider>
</mx:AdvancedDataGrid>
See this. It is recommended to use a bindable ArrayCollection always when dealing with dataProviders.
Try
IHierarchicalCollectionView(__model.myHierarchicalData).refresh();

Resources