add Dynamic children to an array or arraycollection - apache-flex

I'm a bit new to flex and cannot get my head around this problem. can someone help. thanks in advance.
I have a string list path.
path 1 - "one/two/three"
path 2 - "one/two/four"
path 3 - "five/six"
i need an advanced datagrid to show a tree structure like so
one/
...two/
........three/
............four
five/
.......six
but i want to achieve this dynamicall with arrays, objects, or arraycollection (as applicable)
I need to loop through each string path using string methods which isnt a problem but how do i create "DYNAMIC" (depth) children? please help as i'm about to pull my hair out.

You can try something like:
var paths:Array = ['one/two/three','one/two/four','five/six'];
var pathsCollection:ArrayCollection = new ArrayCollection();
for(var i:int = 0 ; i < paths.length ; i++){
var folderArr:Array = paths[i].split('/');
var folderNum:int = folderArr.length;
var folderLabel:String = '';
for(var j:int = 0 ; j < folderNum; j++){
trace(folderLabel+folderArr[j]);
pathsCollection.addItem({label:folderLabel+folderArr[j],level:j,path:folderArr});
folderLabel += '...';
}
}
and as sharvey says, do have a look at recursion.

Recursion is the way to go when you are operating on objects which contain an unknown number of elements. Try this sample application:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="{init();}"
layout="vertical"
verticalAlign="middle">
<mx:Script>
<![CDATA[
import mx.utils.ObjectUtil;
import mx.collections.HierarchicalData;
private var paths:Array = ['one/two/three','one/two/four','five/six'];
private static const DELIMITER:String = "/";
private function init():void {
var test:Array = buildHierarchy(paths);
dg_test.dataProvider = new HierarchicalData(test);
trace(ObjectUtil.toString(test));
}
private function buildHierarchy(arr:Array):Array {
var ret:Array = new Array();
var o:Object = new Object();
/* Loop over the paths array */
for (var i:int = 0; i < arr.length; i++) {
/* Split the string block according to the delimiter */
var parts:Array = String(arr[i]).split(DELIMITER);
if (parts.length) {
/* Make a new object with a label equal to the first string element */
o = new Object();
o.label = parts[0];
/* Remove the first item in the string list */
parts.splice(0, 1);
/* Important - If the string has remaining members, call this
function again with the remaining members. Assign this to
the 'children' property of the newly created object */
if (parts.length > 0)
o.children = buildHierarchy([parts.join(DELIMITER)]);
/* Add the object to the new array */
ret.push(o);
}
}
return ret;
}
]]>
</mx:Script>
<mx:AdvancedDataGrid id="dg_test" height="200" width="400">
<mx:columns>
<mx:AdvancedDataGridColumn id="col_label" dataField="label"/>
</mx:columns>
</mx:AdvancedDataGrid>
This function will call itself once for every element contained in the 'string/string/string' block. The key to getting this structure displayed in the ADG is to set the adg.dataProvider = new HierarchicalData(myArray);
Hope this works! I can't get the code formatted 100% but you should get the idea. Don't forget to add the closing application tag.

Related

How to use Charts in Flash Builder 4.5

Im new to Flex and Actionscript (2 weeks).
I'm trying to use a Chart whose DataProvider is an ArrayCollection that contains Objects
'Person' for example. Person's properties are Age and Salary.
My chart will be an ColumnChart and the xField=Age and the yField would be the AVG(or SUM) Salary for Persons with the same age.
There is any easy way to do that?
Using the way I've just learned, each Person will own one Column in the chart.
My mxml code:
< mx :ColumnChart id="myChart" height="100%" color="0x323232"
showDataTips="true" dataProvider="{GraphArray}">
< mx:series>
< mx:ColumnSeries xField="age" yField="salary"/>
< /mx:series>
< /mx:ColumnChart>
As far as I know, the standard charts will only display exactly what you give them, so you'll need to create a new data provider that contains the average/sum of the data for each age group.
To make a derived provider that stays up to date with changes to GraphArray, you can either add listeners to GraphArray or, if the GraphArray is only set and not modified, you can get away with breaking the variable out into getters and setters, something like this (untested) code:
If you had
[Bindable]
public var GraphArray:ArrayCollection;
Replace it with
private var _GraphArray:ArrayCollection;
[Bindable]
private var DerivedGraphArray:ArrayCollection;
[Bindable]
public function get GraphArray():ArrayCollection
{
return _GraphArray;
}
public function set GraphArray(value:ArrayCollection):void
{
var ageBucketedArray:Array = new Array(121);
var ageBucketedCountsArray:Array = new Array(121);
//Compute salary sums
for each (var o:Object in value)
{
if (ageBucketedArray[o["age"]])
{
ageBucketedArray[o["age"]] += o["salary"];
ageBucketedCountsArray[o["age"]] += 1;
}
else
{
ageBucketedArray[o["age"]] = o["salary"];
ageBucketedCountsArray[o["age"]] = 1;
}
}
//Compute averages
for (var age:int = 0; age < ageBucketedArray.length; age++)
{
if (ageBucketedArray[age])
{
ageBucketedArray[age] /= ageBucketedCountsArray[age];
}
else
{
ageBucketedArray[age] = 0;
}
}
_GraphArray.removeAll();
_GraphArray.addAll(value);
DerivedGraphArray = new ArrayCollection(ageBucketedArray);
}

Flex 3: Can anybody see why this dictionary isn't working?

So, in my main mxml, i have a variable defined as such:
[Bindable] public var studentsListDict:Dictionary = new Dictionary;
I also have the following imported:
import flash.utils.Dictionary;
I then have an HTTPService that imports an XML file:
<mx:HTTPService id="studentsHttp" url="students.xml" resultFormat="e4x" makeObjectsBindable="true" result="createStudentsCollection(event)" />
The createStudentsCollection function is as follows:
private function createStudentsCollection(e:ResultEvent):void
{
var xmlList:XMLList = XML(e.result).student;
var dupString:String = "|";
var tempArray:Array = new Array;
studentsListDict = new Dictionary;
for (var i:int = 0; i < xmlList.length(); i++)
{
if (dupString.indexOf(String("|" + xmlList[i].name) + "|") == -1)
{
tempArray = new Array;
tempArray[0] = xmlList[i].name.#id;
tempArray[1] = xmlList[i].name;
tempArray[2] = xmlList[i].year;
tempArray[3] = xmlList[i].track;
studentsListAC.addItem(tempArray);
studentsListDict[tempArray[0]] = tempArray;
dupString += "|" + xmlList[i].name + "|";
getLen(studentsListDict);
}
}
}
Then, to ensure the items were correctly put into the dictionary, i have the following function:
public static function getLen(d:Dictionary):int
{
var i:int = 0;
for (var key:Object in d)
{
Alert.show(String(key + "\n" + d[key]));
i++;
}
return i;
}
This creates pop up alerts that show that everything was loaded correctly into the dictionary.
Later on, in a child, I call a function that tries to use the dictionary, and i get a return of "undefined".
Here's the function that searches based on key, and returns a value from the array within:
public function getStudentName(sID:Number):String
{
return studentsListDict[sID][1];
}
Unfortunately, the getStudentName function simply returns undefined every time.
If anybody can see something I'm missing, it'd be greatly appreciated.
Thanks,
Brds
EDIT
It wasn't working b/c you can't have numbers as keys in a dictionary. Simply casting them to a string during the declaration and look up seems to work just fine.
Here is some documentation on dictionary keys..
It looks like you're code is setting it as a string and then accessing it as a number. I suspect that is the root of your problem You can try something like this:
public function getStudentName(sID:Number):String
{
return studentsListDict[sID.toString()][1];
}
It is actually perfectly acceptable to use numbers as Keys to a Dictionary. The Dictionary apparently turns the number and a string value of that number to the same key. Here is a sample:
<?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" creationComplete="application1_creationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
public var dict : Dictionary;
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
dict = new Dictionary();
dict["0"] = "hi";
dict["4"] = "hola";
dict["17"] = "bye";
dict["32"] = "adios";
dict[32] = "adios 2";
dict[3.2] = "adios 3";
dict[50] = "Audio ";
dict["50"] = "Audio 2";
trace(dict["0"]);
trace(dict["4"]);
trace(dict["17"]);
trace(dict["32"]);
trace(dict[32]);
trace(dict[3.2]);
trace(dict[50]);
trace(dict["50"]);
}
]]>
</fx:Script>
</s:Application>
I think it's because getStudentName is using a Number as a key, while createStudentCollection is using a string. In this case, because the keys are numbers/strings, you can simply use an Object: var studentsListDict:Object = {}; — it will automatically coerce all the keys to strings.
Also, as an asside: new Dictionary(); more standard, and it's better form in ActionScript to use [] (eg, var foo:Array = []) than new Array(). That way you can put stuff in the array at the same time:
var tempArray:Array = [
xmlList[i].name.#id,
xmlList[i].name,
…
];

Compiler warns me that binding will not work but why I run the application it does work!

The Flex application below generates the compiler warning: Data binding will not be able to detect assignments to 'dp'. This seems correct since the variable 'dp' is not a bindable property (there is no [Bindable] metadata tag). I have added a button which appends items to the back of 'dp' when it is clicked. Although the compiler warns me that I will not see changes to 'dp', the list shows the new item every time the button is clicked!
I do not understand why I can see new items appear in the list. Can someone explain why this still works although 'dp' is not bindable?
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" minWidth="955" minHeight="600">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
private var arrayData:Array = [
{name:"banana", cat:"fruit", cost:0.99},
{name:"bread", cat:"bakery", cost:1.99},
{name:"orange", cat:"fruit", cost:0.52},
{name:"donut", cat:"bakery", cost:0.33},
{name:"apple", cat:"fruit", cost:1.05}];
private var dp:ArrayCollection = new ArrayCollection(arrayData);
private function onButtonClick(event:MouseEvent):void
{
var obj:Object = new Object();
obj.name="test";
obj.cat="testcat";
obj.cost=666;
dp.addItem(obj);
}
]]>
</mx:Script>
<mx:HorizontalList dataProvider="{dp}" labelField="name" columnWidth="100" width="80%" height="50"/>
<mx:Button label="Click me" click="onButtonClick(event)" />
The compiler is correct in it's warning.
The compiler is warning you that assignments that change the value of dp from the initial ArrayCollection you specified to another ArrayCollection won't be detected.
However, if you leave the value of dp alone, and only change the contents of it, then your <HorizontalList /> will continue to work.
This may seem trivial, but it's an important distinction, and one that can lead to some very confusing bugs further down the road in your application.
Assignments to the variable dp will not be detected. However, changes to the ArrayCollections list will, because they dispatch a CollectionChangeEvent.
eg:
private var dp:ArrayCollection = new ArrayCollection();
private function test():void
{
// Here, we don't change the value of dp directly,
// instead we just modify it's list.
// The DataGroup will show the strings One,Two
dp.addItem("One")
dp.addItem("Two")
// Here, we change the actual value of dp, by assigning a
// new ArrayCollection to it.
// This change would not be detected, and the list would continue to show
// the contents of the previous value.
// Additionally, the label will show the string "Length: 2",
// even though the length is clearly now 3.
dp = new ArrayCollection();
dp.addItem("Tahi");
dp.addItem("Rua");
dp.addItem("Toru");
}
<s:DataGroup dataProvider="{dp}" />
<s:Label text="Length: {dp.length}" />
Try to use:
[Bindable("__NoChangeEvent__")]
private var dp:ArrayCollection = new ArrayCollection(arrayData);
What about adding elements in list see the code of ListBase:
public function set dataProvider(value:Object):void
{
if (collection)
{
collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
}
if (value is Array)
{
collection = new ArrayCollection(value as Array);
}
else if (value is ICollectionView)
{
collection = ICollectionView(value);
}
else if (value is IList)
{
collection = new ListCollectionView(IList(value));
}
else if (value is XMLList)
{
collection = new XMLListCollection(value as XMLList);
}
else if (value is XML)
{
var xl:XMLList = new XMLList();
xl += value;
collection = new XMLListCollection(xl);
}
else
{
// convert it to an array containing this one item
var tmp:Array = [];
if (value != null)
tmp.push(value);
collection = new ArrayCollection(tmp);
}
// get an iterator for the displaying rows. The CollectionView's
// main iterator is left unchanged so folks can use old DataSelector
// methods if they want to
iterator = collection.createCursor();
collectionIterator = collection.createCursor(); //IViewCursor(collection);
// trace("ListBase added change listener");
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);
clearSelectionData();
var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.RESET;
collectionChangeHandler(event);
dispatchEvent(event);
itemsNeedMeasurement = true;
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
So take a look at line:
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);

Populate comboBox in flex 4 using remote Object

I have a remote object returned and I am trying to populate it into combobox.
<s:RemoteObject id="ro" result="result(event)" destination="echoServiceDestination">
private var statesData:ArrayCollection;
private function result(e:ResultEvent):void{
statesData = e.result as ArrayCollection;
}
How can I turn this collection into something like {label:"Red", data:"#FF0000"} so that I can populate into combobox
The remote object is party and I can't seem to able to cast it as below
var party:Party = new Party;
for(var i:int = 0 ; i < statesData.length; i++)
{
party = statesData.getItemAt(i);
}
Thanks for the help.
You need to implement a label function. Set the returning data directly as dataprovider to your combobox:
<mx:ComboBox id="comboBox"
dataProvider="{statesData}"
labelFunction="labelFunc" />
This will be your label function:
private function labelFunc(item:Object):String {
return item.label; // Or whatever parameter you want to display
}
]]>
</mx:Script>

How to access the array elements

var count:uint = 0;
var textInputs:Array /* of TextInputs */ = [];
for(var i:String in columnsData){
textInputs[count] = new TextInput();
addChild(textInputs[count]);
count++;
}
here how can i access the first, second of Array of textinputs in the form of string or any thing to further proceed
I am not sure if I understood what your question is, but you're not setting the value for text inputs:
I prefer it the following way:
//make the array an instance variable instead of local var
//so that it can be accessed from other functions if required.
public var textInputs:Array = [];
for(var str:String in columnsData)
{
var ti:TextInput = new TextInput();
ti.text = str;
addChild(ti);
textInputs.push(ti);
}
You can access the values using textInputs[0].text etc.
if(textInput != null){
for(var i:Number = 0; i < textInputs.length; i++)
trace(textInputs[i].text);
}
(getChildAt(0) as TextInput).text //first
(getChildAt(1) as TextInput).text //second
If your sprite contains something other than TextInput at indices 0 & 1 you'll get null reference exception.
<?xml version="1.0"?>
<!-- dpcontrols\UseIList.mxml -->
<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"
initialize="initData();">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.collections.*;
// The data provider is an Array of Strings
public var myArray:Array = ["AZ", "MA", "MZ", "MN", "MO", "MS"];
// Declare an ArrayList that represents the Array.
[Bindable]
public var myAL:ArrayList;
//Initialize the ArrayList.
public function initData():void {
myAL = new ArrayList(myArray);
}
// The function to change the collection, and therefore
// the Array.
public function changeCollection():void {
// Get the original collection length.
var oldLength:int=myAL.length;
// Remove the invalid first item, AZ.
var removedItem:String=String(myAL.removeItemAt(0));
// Add ME as the second item. (ILists used 0-based indexing.)
myAL.addItemAt("ME", 1);
// Add MT at the end of the Array and collection.
myAL.addItem("MT");
// Change the third item from MZ to MI.
myAL.setItemAt("MI", 2);
// Get the updated collection length.
var newLength:int=myAL.length;
// Get the index of the item with the value ME.
var addedItemIndex:int=myAL.getItemIndex("ME");
// Get the fifth item in the collection.
var index4Item:String=String(myAL.getItemAt(4));
// Display the information in the TextArea control.
ta1.text="Start Length: " + oldLength + ". New Length: " +
newLength;
ta1.text+=".\nRemoved " + removedItem;
ta1.text+=".\nAdded ME at index " + addedItemIndex;
ta1.text+=".\nThe item at index 4 is " + index4Item + ".";
// Show that the base Array has been changed.
ta1.text+="\nThe base Array is: " + myArray.join();
}
]]>
</fx:Script>
<s:ComboBox id="myCB" dataProvider="{myAL}"/>
<s:TextArea id="ta1" height="75" width="300"/>
<s:Button label="rearrange list" click="changeCollection();"/>
</s:Application>

Resources