In AS3/Flex, how can I get from flat data to hierarchical data? - apache-flex

I have some data that gets pulled out of a database and mapped to an arraycollection. This data has a field called parentid, and I would like to map the data into a new arraycollection with hierarchical information to then feed to an advanced data grid.
I think I'm basically trying to take the parent object, add a new property/field/variable of type ArrayCollection called children and then remove the child object from the original list and clone it into the children array? Any help would be greatly appreciated, and I apologize ahead of time for this code:
private function PutChildrenWithParents(accountData : ArrayCollection) : ArrayCollection{
var pos_inner:int = 0;
var pos_outer:int = 0;
while(pos_outer < accountData.length){
if (accountData[pos_outer].ParentId != null){
pos_inner = 0;
while(pos_inner < accountData.length){
if (accountData[pos_inner].Id == accountData[pos_outer].ParentId){
accountData.addItemAt(
accountData[pos_inner] + {children:new ArrayCollection(accountData[pos_outer])},
pos_inner
);
accountData.removeItemAt(pos_outer);
accountData.removeItemAt(pos_inner+1);
}
pos_inner++;
}
}
pos_outer++;
}
return accountData;
}

I had a similar problem with a hierarchical task set which was slightly different as it has many root elements, this is what i did, seems good to me:
public static function convertFlatTasks(tasks:Array):Array
{
var rootItems:Array = [];
var task:TaskData;
// hashify tasks on id and clear all pre existing children
var taskIdHash:Array = [];
for each (task in tasks){
taskIdHash[task.id] = task;
task.children = [];
task.originalChildren = [];
}
// loop through all tasks and push items into their parent
for each (task in tasks){
var parent:TaskData = taskIdHash[task.parentId];
// if no parent then root element, i.e push into the return Array
if (parent == null){
rootItems.push(task);
}
// if has parent push into children and originalChildren
else {
parent.children.push(task);
parent.originalChildren.push(task);
}
}
return rootItems;
}

Try this:
AccountData:
public class AccountData
{
public var Id:int;
public var ParentId:int;
public var children:/*AccountData*/Array;
public function AccountData(id:int, parentId:int)
{
children = [];
this.Id = id;
this.ParentId = parentId;
}
}
Code:
private function PutChildrenWithParents(accountData:ArrayCollection):AccountData
{
// dummy data for testing
//var arr:/*AccountData*/Array = [new AccountData(2, 1),
// new AccountData(1, 0), // root
// new AccountData(4, 2),
// new AccountData(3, 1)
// ];
var arr:/*AccountData*/Array = accountData.source;
var dict:Object = { };
var i:int;
// generate a lookup dictionary
for (i = 0; i < arr.length; i++)
{
dict[arr[i].Id] = arr[i];
}
// root element
dict[0] = new AccountData(0, 0);
// generate the tree
for (i = 0; i < arr.length; i++)
{
dict[arr[i].ParentId].children.push(arr[i]);
}
return dict[0];
}
dict[0] holds now your root element.
Maybe it's doesn't have the best possible performance but it does what you want.
PS: This code supposes that there are no invalid ParentId's.

Here's what I ended up doing, apparently you can dynamically add new properties to an object with
object['new_prop'] = whatever
From there, I used a recursive function to iterate through any children so you could have n levels of the hierarchy and if it found anything it would pass up through the chain by reference until the original function found it and acted on it.
private function PutChildrenWithParents(accountData : ArrayCollection) : ArrayCollection{
var pos_inner:int = 0;
var pos_outer:int = 0;
var result:Object = new Object();
while(pos_outer < accountData.length){
if (accountData[pos_outer].ParentId != null){
pos_inner = 0;
while(pos_inner < accountData.length){
result = CheckForParent(accountData[pos_inner],
accountData[pos_outer].ParentId);
if ( result != null ){
if(result.hasOwnProperty('children') == false){
result['children'] = new ArrayCollection();
}
result.children.addItem(accountData[pos_outer]);
accountData.removeItemAt(pos_outer);
pos_inner--;
}
pos_inner++;
}
}
pos_outer++;
}
return accountData;
}
private function CheckForParent(suspectedParent:Object, parentId:String) : Object{
var parentObj:Object;
var counter:int = 0;
if ( suspectedParent.hasOwnProperty('children') == true ){
while (counter < suspectedParent.children.length){
parentObj = CheckForParent(suspectedParent.children[counter], parentId);
if (parentObj != null){
return parentObj;
}
counter++;
}
}
if ( suspectedParent.Id == parentId ){
return suspectedParent;
}
return null;
}

Related

How to get format of current selection?

Is there a way to get the format of the current selection? Here is what I have so far:
var currentFormat:TextLayoutFormat;
var selectionStart:int;
var selectionEnd:int;
var operationState:SelectionState;
var editManager:IEditManager;
if (richEditableText.textFlow && richEditableText.textFlow.interactionManager is IEditManager) {
editManager = IEditManager(richEditableText.textFlow.interactionManager);
selectionStart = Math.min(richEditableText.selectionActivePosition, richEditableText.selectionAnchorPosition);
selectionEnd = Math.max(richEditableText.selectionActivePosition, richEditableText.selectionAnchorPosition);
if (operationState == null) {
operationState = new SelectionState(richEditableText.textFlow, selectionStart, selectionEnd);
}
// this does not work
currentFormat = editManager.getCommonCharacterFormat(operationState);
}
I couldn't find anything so here is what seems to work:
/**
* Get format of element range
**/
public static function getElementRangeFormat(elementRange:ElementRange):TextLayoutFormat {
var leaf:FlowLeafElement = elementRange.firstLeaf;
var attr:TextLayoutFormat = new TextLayoutFormat(leaf.computedFormat);
for (;;)
{
if (leaf == elementRange.lastLeaf)
break;
leaf = leaf.getNextLeaf();
attr.concatInheritOnly(leaf.computedFormat);
}
return Property.extractInCategory(TextLayoutFormat, TextLayoutFormat.description, attr, Category.CHARACTER, false) as TextLayoutFormat;
}
Inspired by ElementRange.getCommonCharacterFormat().

Remove duplicate values from arraycollection in flex4

This is my arraycollection
o = JSON.parse(event.result.toString());
jsonarray = new ArrayCollection(o as Array);
in this array i have a duplicate values of product name, so i wants to remove duplicacy.\
my code is here,its not working please let me know, i am a flex beginner. thanx in advance.
function removeDuplicates(item:Object):Boolean
{
var returnValue:Boolean = false;
if (!myObject.hasOwnProperty(item.ProductName))
{
myObject[item.ProductName] = item;
returnValue = true;
}
prodArray.push(myObject);
return returnValue;
}
Call the filterCollection method given below and in that use the filterfunction to remove duplicates
private var tempObj:Object = {};
private function filterCollection():void {
// assign the filter function
jsonarray.filterFunction = removeDuplicates;
//refresh the collection
jsonarray.refresh();
}
private function removeDuplicates(item:Object):Boolean {
return (tempObj.hasOwnProperty(item.ProductName) ? false : tempObj[item.ProductName] = item && true);
}

Doubt in action script for Flex: getting unique elements from an ArrayCollection

I have an ArrayCollection as mentioned below.
private var initDG:ArrayCollection = new ArrayCollection([
{fact: "Order #2314", appName: "AA"},
{fact: "Order #2315", appName: "BB"}
{fact: "Order #2316", appName: "BB"}
...
{fact: "Order #2320", appName: "CC"}
{fact: "Order #2321", appName: "CC"}
]);
I want to populate a ComboBox with UNIQUE VALUES of "appName" field from the ArrayCollection initDG.
<mx:ComboBox id="appCombo" dataProvider="{initDG}" labelField="appName"/>
One method I could think is to loop through the Array objects and for each object check and push unique appName entries into another Array. Is there any better solution available?
That sounds good to me:
var unique:Object = {};
var value:String;
var array:Array = initDG.toArray();
var result:Array = [];
var i:int = 0;
var n:int = array.length;
for (i; i < n; i++)
{
value = array[i].appName;
if (!unique[value])
{
unique[value] = true;
result.push(value);
}
}
return new ArrayCollection(result);
You can used this class for finding unique arraycollection:
tempArray=_uniqueArray.applyUnqiueKey1(_normalsearchdata.toArray());
"uniqueArray" this is package name and _normalsearchdata is ArrayCollection;
package{
import mx.collections.ArrayCollection;
public class applyUniqueKey{
private var tempArray:Array;
private var tempIndex = 0;
public var temp:String;
public function applyUnqiueKey1(myArray)
{
tempArray = new Array();
tempIndex = 0;
myArray.sort();
tempArray[0] = myArray[0];
tempIndex++;
for(var i=1; i<myArray.length; i++) {
if(myArray[i] != myArray[i-1]) {
tempArray[tempIndex] = myArray[i];
tempIndex++;
}
}
var temp=String(tempArray.join());
return new ArrayCollection(tempArray);
}
}
}
Alas, there is no unique() method in ActionScript's Array, but you can approximate it like this:
var names:Array = initDG.toArray().map(
function (e:Object, i:Number, a:Array):String {
return e.appName;
}
);
var uniqueNames:Array = names.filter(
function (name:String, i:Number, a:Array):Boolean {
// Only returns true for the first instance.
return names.indexOf(name) == i;
}
);
Note this happens to work because you are filtering strings, which are compared by value. This wouldn't be effective if you needed to filter arbitrary objects.

Cannot add an entity that already exists

Code:
public ActionResult Create(Group group)
{
if (ModelState.IsValid)
{
group.int_CreatedBy = 1;
group.dtm_CreatedDate = DateTime.Now;
var Groups = Request["Groups"];
int GroupId = 0;
GroupFeature GroupFeature=new GroupFeature();
foreach (var GroupIdd in Groups)
{
// GroupId = int.Parse(GroupIdd.ToString());
}
var Features = Request["Features"];
int FeatureId = 0;
int t = 0;
int ids=0;
string[] Feature = Features.Split(',').ToArray();
//foreach (var FeatureIdd in Features)
for(int i=0; i<Feature.Length; i++)
{
if (int.TryParse(Feature[i].ToString(), out ids))
{
GroupFeature.int_GroupId = 35;
GroupFeature.int_FeaturesId = ids;
if (ids != 0)
{
GroupFeatureRepository.Add(GroupFeature);
GroupFeatureRepository.Save();
}
}
}
return RedirectToAction("Details", new { id = group.int_GroupId });
}
return View();
}
I am getting an error here Cannot add an entity that already exists. at this line GroupFeatureRepository.Add(GroupFeature);
GroupFeatureRepository.Save();
This line:
GroupFeature GroupFeature=new GroupFeature();
needs to be inside your for loop, like this:
for(int i=0; i<Feature.Length; i++)
{
if (int.TryParse(Feature[i].ToString(), out ids))
{
GroupFeature GroupFeature=new GroupFeature();
You need to add a new GroupFeature each time(e.g. one not in that collection, which your re-used object already is after the first loop). You can't re-use the same GroupFeature object like that for adding, but moving it inside the loop so you generate a distinct GroupFeature each time will resolve this.

Flex: Updating a Tree control

I've a tree control with checkboxes that uses the control from http://www.sephiroth.it/file_detail.php?id=151#
Somehow I can't get the control to update when I change the dataProvider (i.e. by clicking a checkbox) the only way I can get it to update is to use the scrollbar. How do I force the update? I've tried all possible methods I can figure out? (see update below)
Also how can I reset the Tree (collpasing all nodes, scroll to the top in a large tree)?
package offerta.monkeywrench.components
{
import offerta.monkeywrench.components.componentClasses.TreeCheckBoxItemRenderer;
import mx.collections.ArrayCollection;
import mx.events.TreeEvent;
public class WatchTree extends TreeCheckBox
{
public var idProperty:String;
public var watchFactory:Function;
private var _wSet:Boolean = false;
/* clientId: */
private var _clientId:String;
[Bindable]
public function get clientId():String
{
return _clientId;
}
public function set clientId(value:String):void
{
this._clientId = value;
}
/* //clientId */
/* watching: */
private var _watching:ArrayCollection;
[Bindable]
public function set watching(value:ArrayCollection):void
{
this._watching = value;
}
public function get watching():ArrayCollection
{
return this._watching;
}
/* //watching */
override public function initialize() :void
{
super.initialize();
addEventListener("itemCheck", onItemCheck, false, 0, true);
}
private function isWatching(id:String):Boolean
{
for each(var w:Object in this._watching)
{
if(w[this.idProperty]==id) return true;
}
return false;
}
private function onItemCheck(event:TreeEvent):void
{
var item:Object = event.item as Object;
var currentValue:uint = (event.itemRenderer as TreeCheckBoxItemRenderer).checkBox.checkState;
if(item.children==null)
{
currentValue==2 ? addWatch(item.Id) : removeWatch(item.Id);
}
else
{
for each(var x:Object in item.children)
{
currentValue==2 ? addWatch(x.Id) : removeWatch(x.Id);
}
}
updateParents(item, currentValue);
updateChilds(item, currentValue);
this.dataProvider.refresh();
super.invalidateProperties();
super.invalidateDisplayList();
super.updateDisplayList(this.unscaledWidth, this.unscaledHeight);
}
private function updateParents(item:Object, value:uint):void
{
var checkValue:String = (value == ( 1 << 1 | 2 << 1 ) ? "2" : value == ( 1 << 1 ) ? "1" : "0");
var parentNode:Object = item.parent;
if(parentNode)
{
for each(var x:Object in parentNode.children)
{
if(x.checked != checkValue)
{
checkValue = "2"
}
}
parentNode.checked = checkValue;
updateParents(parentNode, value);
}
}
private function updateChilds(item:Object, value:uint):void
{
var middle:Boolean = (value&2<<1)==(2<<1);
if(item.children!=null && item.children.length>0&&!middle)
{
for each(var x:Object in item.children)
{
x.checked = value == (1<<1|2<<1) ? "2" : value==(1<<1) ? "1" : "0";
updateChilds(x, value);
}
}
}
private function addWatch(id:String):void
{
if(isWatching(id)) return;
this._watching.addItem(this.watchFactory(id, this.clientId));
}
private function removeWatch(id:String):void
{
for(var i:int=0, n:int=this._watching.length; i<n; ++i)
{
if(this._watching[i][this.idProperty]==id)
{
this._watching.removeItemAt(i);
return;
}
}
}
public function update(__watching:ArrayCollection, __clientId:String):void
{
clientId = __clientId;
watching = __watching;
if(this.dataProvider!=null)
{
var ws:ArrayCollection = ArrayCollection(this.dataProvider);
for each(var group:Object in ws)
{
var count:int = 0;
for each(var child:Object in group.children)
{
if(isWatching(child.Id))
{
child.checked = "1";
count++;
}
}
group.checked = (count==0 ? "0" : (count==group.children.length ? "1" : "2"));
}
this._wSet = false;
var dp:ArrayCollection = ArrayCollection(this.dataProvider);
dp.refresh();
super.invalidateProperties();
super.invalidateDisplayList();
super.updateDisplayList(this.unscaledWidth, this.unscaledHeight);
//scroll up the list???
//collapse? (doesn't work)
this.expandItem(null, false);
}
}
}
}
I've found the Tree control a little touchy in Flex. The way I ended up forcing a redraw was to disconnect the dataProvider and reconnect it, then force validation, something a bit like this :
private function forceRedraw(tree:Tree, dataProvider:Object):void
{
var scrollPosition:Number = tree.verticalScrollPosition;
var openItems:Object = tree.openItems;
tree.dataProvider = dataProvider;
tree.openItems = openItems;
tree.validateNow();
tree.verticalScrollPosition = scrollPosition;
}
I guess this incidentally answers the second part of your question since all you'd have to do is null out the openItems collection and set the verticalScrollPosition to 0.
You might have another problem: whenever you check an item the tree scrolls to the top and this is just annoying. To solve this problem you should update the TreeCheckBox.as file this way:
in function checkHandler:
private function checkHandler( event: TreeEvent ): void;
comment the commitProperties(); call.
Now it should work well.
Cheers.
I've had some minor problem with this solution, var scrollPosition:Number = tree.verticalScrollPosition; is constantly 0??

Resources