blob: c489847cf7148626e2fad0d360c3aa804ab5edec [file]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package
{
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.binding.utils.BindingUtils;
import mx.collections.ArrayCollection;
import mx.collections.ICollectionView;
import mx.controls.Tree;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DragEvent;
import mx.events.TreeEvent;
use namespace mx_internal;
/**
* This class will expand a tree whenever the data provider changes.
* The tree expansion performed in one of two ways.
*
* The first is by setting the nested levels. Setting the nested levels to 1 for example
* will show the children of the root node.
*
* Anothee approach is by setting the last parent class which indicates the class when encountered
* will stop the expansion of a branch. By setting expandLastParent to true, that node is also expanded.
*
* In theory this should be separated into two classes once some neamingful names for the classes are found.
*/
public class TreeExpander
{
/**
* The last class to expand
*/
public var lastParent:Class;
/**
* Whether or not the last parent should be expanded. Default is false.
*/
public var expandLastParent:Boolean = false;
/**
* The number of tree levels to open, default is 100. Ignored if LastParent is set.
*/
public var nestedLevels:int = 100;
/**
* Automatically expand the parent branches of an item when that item is selected.
* Default is true.
*/
public var expandSelectedItem:Boolean = true;
private var selectedDomainObjects:Array;
private var timerToExpand:Timer = new Timer(700, 1);
private var nodeToExpand:Object;
/**
* The tree to perform the expansions.
*/
public function set tree(tree:Tree):void
{
_tree = tree;
BindingUtils.bindSetter(expandFromRoot, tree, "dataProvider");
// BindingUtils.bindSetter(expandAncestors, this, "selectedDomainObjects");
tree.addEventListener(TreeEvent.ITEM_OPEN, nodeExpandedHandler);
tree.addEventListener(DragEvent.DRAG_OVER, dragOverHandler);
timerToExpand.addEventListener(TimerEvent.TIMER, timerToExpandHandler);
}
public function get tree():Tree
{
return _tree;
}
private var _tree:Tree;
/**
* Expand the whole tree.
*/
public function expandFromRoot(object:Object=null):void
{
if (tree==null || tree.dataProvider==null || nestedLevels < 1)
return;
var rootNode:Object = getRootNode();
if (rootNode != null)
{
expandNode(rootNode);
if (tree.dataDescriptor.hasChildren(rootNode))
{
var children:ICollectionView = tree.dataDescriptor.getChildren(rootNode);
children.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
}
}
}
/**
* Expand starting at a specific node
*/
private function expandNode(node:Object):void
{
if (lastParent)
expandToLastParent(node);
else
expandToLevel(node);
}
/**
* Returns the root data provider node of the tree.
*/
private function getRootNode():Object
{
var rootNode:Object;
if (tree.dataProvider is ArrayCollection)
{
var dataProviderArray:ArrayCollection = tree.dataProvider as ArrayCollection;
if (dataProviderArray.length > 0)
{
rootNode = dataProviderArray.getItemAt(0);
}
}
else
{
rootNode = tree.dataProvider;
}
return rootNode;
}
/**
* Expand until the nested level is reached
*/
private function expandToLevel(node:Object, curLevel:int=0):void
{
if (curLevel<nestedLevels)
{
tree.expandItem(node, true);
if (curLevel<nestedLevels && tree.dataDescriptor.hasChildren(node))
{
for each (var node:Object in tree.dataDescriptor.getChildren(node))
{
expandToLevel(node, curLevel+1);
}
}
}
}
/**
* Expand until we have reached a node having a specific class
*/
private function expandToLastParent(node:Object):void
{
if (!isLastParent(node) || expandLastParent)
{
tree.expandItem(node, true);
if (!isLastParent(node) && tree.dataDescriptor.hasChildren(node))
{
for each (var node:Object in tree.dataDescriptor.getChildren(node))
{
expandToLastParent(node);
}
}
}
}
/**
* Returns true if the node is of class lastParent
*/
private function isLastParent(node:Object):Boolean
{
return lastParent ? node is lastParent : false;
}
[Bindable]
public function set selectedItems(items:Array):void
{
selectedDomainObjects = items;
trace("1 --- Selected Items Length = " + items.length);
if (items != null && items.length > 0)
{
expandAncestors(items[0]);
}
trace("2 --- Selected Items Length = " + items.length);
try {
tree.selectedItems = items;
}
catch(e:Error) {
trace("ERROR" + e.message);
}
trace("3 --- Selected Items Length = " + items.length);
}
public function get selectedItems():Array
{
trace("TreeExpander " + tree.selectedItems.length);
return tree.selectedItems;
}
/**
* Expand the ancestors of a node.
*/
private function expandAncestors(item:Object):void
{
if (tree.dataProvider!=null && expandSelectedItem && item!=null && !tree.isItemVisible(item))
{
var ancestors:Array = new Array();
getAncestors(item, getRootNode(), ancestors);
for each (var ancestor:Object in ancestors)
{
tree.expandItem(ancestor, true);
}
}
}
/**
* Recursively finds the ancestors of a node.
*/
private function getAncestors(item:Object, parent:Object, ancestors:Array):void
{
var children:ICollectionView = tree.dataDescriptor.getChildren(parent);
for each (var child:Object in children)
{
if (child === item)
{
ancestors.push(parent);
return;
}
else if (tree.dataDescriptor.hasChildren(child))
{
getAncestors(item, child, ancestors)
if (ancestors.length > 0)
{
ancestors.push(parent);
return;
}
}
}
}
private function collectionChangeHandler(event:CollectionEvent):void
{
var items:Array = findAddedItems(event);
for each (var item:Object in items)
{
expandNode(item);
}
}
private function findAddedItems(event:CollectionEvent):Array
{
var addedItems:Array;
if (event.kind == CollectionEventKind.ADD)
{
addedItems = event.items;
}
else if (event.kind == CollectionEventKind.UPDATE)
{
addedItems = new Array();
for each (var item:Object in event.items)
{
if (item.hasOwnProperty("origin") && item.origin is CollectionEvent)
{
var originalEvent:CollectionEvent = CollectionEvent(item.origin);
if (originalEvent.kind == CollectionEventKind.ADD)
{
addedItems = addedItems.concat(originalEvent.items);
}
}
}
}
return addedItems;
}
private function nodeExpandedHandler(event:TreeEvent):void
{
if (expandSelectedItem)
{
var firstItem:Object = selectedDomainObjects.length==0 ? null : selectedDomainObjects[0];
if (firstItem != null && tree.isItemVisible(firstItem))
{
selectedItems = selectedDomainObjects;
}
}
}
private function dragOverHandler(event:DragEvent):void
{
var collapsedBranch:Object = getCollapsedBranch();
if (nodeToExpand != collapsedBranch)
{
timerToExpand.stop();
}
if (collapsedBranch != null)
{
nodeToExpand = collapsedBranch;
timerToExpand.start();
}
}
private function getCollapsedBranch():Object
{
if (tree != null && tree._dropData != null)
{
if (tree._dropData.parent == null)
{
return getRootNode();
}
else
{
var children:ICollectionView = tree._dataDescriptor.getChildren(tree._dropData.parent);
if (children is ArrayCollection && ArrayCollection(children).length>tree._dropData.index)
{
var child:Object = ArrayCollection(children).getItemAt(tree._dropData.index);
if (tree._dataDescriptor.isBranch(child) && !tree.isItemOpen(child))
{
return child;
}
}
}
}
return null;
}
private function timerToExpandHandler(event:TimerEvent):void
{
if (nodeToExpand != null)
{
expandNode(nodeToExpand);
}
}
}
}