Wednesday, December 30, 2009

Delete is broken in Hierarchical Data

I have a AdvancedDataGrid where I am deleting the children. When all the children are gone the folder icon does not change. If I walk the collection and then force the children = null, then the folder changes to a sheet (leaf) icon, as you would expect by looking at the HierachicalCollection class. However when I go to add a new leaf to the summary folder the folder reappears and the down arrow opens indicating that it's got a child, but the child does not render.

Repro using this is swf.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application layout="horizontal"
viewSourceURL="srcview/index.html"
xmlns:mx="http://www.adobe.com/2006/mxml%22>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.collections.IHierarchicalCollectionView;
import mx.collections.ICollectionView;
import mx.collections.HierarchicalData;
[Bindable] public var tasks:HierarchicalData = new HierarchicalData([{id: "T1", name: "Task #1", startTime: "1/14/2008 8:0:0", endTime: "1/28/2008 17:0:0"}, {id: "T2", name: "Task #2", startTime: "1/29/2008 8:0:0", endTime: "2/5/2008 17:0:0"},
{id: "T3", name: "Summary Task #1", startTime: "2/5/2008 8:0:0", endTime: "2/17/2008 17:0:0", children: [{id: "T4", name: "Task #3", startTime: "2/5/2008 8:0:0", endTime: "2/12/2008 17:0:0"},
{id: "T5", name: "Task #4", startTime: "2/13/2008 8:0:0", endTime: "2/17/2008 17:0:0"}]},
{id: "T6", name: "Milestone #1", milestone: "true", startTime: "2/17/2008 17:0:0", endTime: "2/17/2008 17:0:0"}]);

private function onDelete():void {
var task:Object = adGrid.selectedItem;
if(task != null) {
var _taskCollection:IHierarchicalCollectionView = IHierarchicalCollectionView(adGrid.dataProvider);
var parentTask:Object = _taskCollection.getParentItem(task);
_taskCollection.removeChild(parentTask, task);
//trace(parentTask.id + " has children = " + tasks.hasChildren(parentTask));
//this.cleanTaskCollection(tasks.source as Array);
_taskCollection.itemUpdated(parentTask);
//_taskCollection.refresh();
} else {
mx.controls.Alert.show("Please select a node");
}
}

private function cleanTaskCollection(collection:Array):void {
for each(var task:Object in collection) {
if(task.children && task.children.length == 0) {
// attempt to force item render to not show folders
task.children = null;
} else {
// recurse
cleanTaskCollection(task.children);
}
}
}
private var count:int = 0;

private function onAdd():void {
var task:Object = adGrid.selectedItem;
if(task != null) {
var child:Object = new Object;
child.id = count++;
child.name = "new node " + count;
if(task.children == null) {
task.children = new Array();
}
var _taskCollection:IHierarchicalCollectionView = IHierarchicalCollectionView(adGrid.dataProvider);
_taskCollection.addChild(task, child);
} else {
mx.controls.Alert.show("Please select a node");
}
}
]]>
</mx:Script>
<mx:AdvancedDataGrid dataProvider="{tasks}"
designViewDataType="tree"
id="adGrid">
</mx:AdvancedDataGrid>
<mx:Button click="onDelete()"
label="delete">
</mx:Button>
<mx:Button click="onAdd()"
label="add">
</mx:Button>
</mx:Application>

Monday, December 28, 2009

Finding circular dependencies in Flex/AS3

I was looking for cycles in a directed graph in a task chart implementation I was doing for a client. My implementation of the Tarjan algorithm is hard coded for a linkage of task objects linked by constraints. I’d be interested to find out if anyone else has dug into this algorithm and gotten it to work.

package model.task {
import ilog.gantt.TaskChart;


public class Tarjan {
private var index:int = 0;
private var stack:Array = new Array();
private var SCC:Array = new Array();
public var taskChart:ilog.gantt.TaskChart;

/**
* N.B. Don't forget to reset the *.index = -1 after checking
*
* DJH 12/24/2009 This will only work for task nodes connected by constraints. It does not traverse
* the entire task hierachical data
*
* I don't fully understand but all nodes are treated as loops. Look through the SCC
* array for child arrays which with more than 1 node and those the the cycles.
*
*/
public function tarjan(task:Object /* node */ /*, adjacencyList:Array*/ ):Array {
trace("tarjan():: push task " + task.id);
task.index = index;
task.lowLink = index;
index++;
stack.push(task);
var constraints:Array = taskChart.getFromConstraints(task);
// Collect the successors
if(constraints) {
for each(var c:Object in constraints) {
var toTask:Object = taskChart.getToTask(c);
if(toTask == null) {
// DJH 12/9/2009, fix the constraint does not chain to a successor, so ignore
continue;
}
if(toTask.index == -1) {
tarjan(toTask /*, adjacencyList*/ );
task.lowLink = Math.min(task.lowLink, toTask.lowLink);
} else if(stack.indexOf(toTask) >= 0) {
task.lowLink = Math.min(task.lowLink, toTask.index);
}
}
}
if(task.lowLink == task.index) {
var arr:Array = new Array();
do {
toTask = stack.pop();
arr.push(toTask);
} while(toTask != task)
SCC.push(arr);
}
return SCC;
}
}
}