XML Accordion Tree
Simple XML based multilevel accordion tree
/**
* Copyright meix ( http://wonderfl.net/user/meix )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/hnoY
*/
/*****************************************************************************************/
/* ACCORDION TREE CLASS
/*****************************************************************************************/
package{
import flash.display.*;
import flash.text.*;
import flash.events.*;
import flash.geom.*;
import gs.TweenLite;
import gs.easing.Expo;
import gs.plugins.TweenPlugin;
import gs.plugins.TintPlugin;
import idv.cjcat.signals.NativeSignal;
public class AccordionTree extends Sprite {
public static var ITEM_WIDTH:int = 260;
public static var ITEM_HEIGHT:int = 25;
private var menuSprite:Sprite;
private var contractMask:Shape;
private var expandMask:Shape;
private var enterFrameSignal:NativeSignal;
private var data:XML = new XML("<content><item>TreeItem 1</item><item>TreeItem 2</item><item>TreeItem 3<item>TreeItem 3-1</item><item>TreeItem 3-2</item><item>TreeItem 3-3</item><item>TreeItem 3-4</item><item>TreeItem 3-5</item><item>TreeItem 3-6</item><item>TreeItem 3-7</item><item>TreeItem 3-8</item><item>TreeItem 3-9</item><item>TreeItem 3-10</item><item>TreeItem 3-11</item></item><item>TreeItem 4</item><item>TreeItem 5<item>TreeItem 5-1<item>TreeItem 5-1-1</item><item>TreeItem 5-1-2</item><item>TreeItem 5-1-3<item>TreeItem 5-1-3-1</item></item><item>TreeItem 5-1-4<item>TreeItem 5-1-4-1</item><item>TreeItem 5-1-4-2</item><item>TreeItem 5-1-4-3<item>TreeItem 5-1-4-3-1</item><item>TreeItem 5-1-4-3-2</item></item><item>TreeItem 5-1-4-4</item></item><item>TreeItem 5-1-5</item></item><item>TreeItem 5-2</item></item><item>TreeItem 6</item><item>TreeItem 7</item><item>TreeItem 8<item>TreeItem 8-1</item><item>TreeItem 8-2</item><item>TreeItem 8-3</item><item>TreeItem 8-4</item></item><item>TreeItem 9</item></content>");
private var items:Vector.<Object> = new Vector.<Object>();
private var itemsCopy:Vector.<Object>;
private var tweenDuration:Number = .5;
private var tweenEase:Function = Expo.easeOut;
private var selectedIndex:int = -1;
public function AccordionTree(){
TweenPlugin.activate([TintPlugin]);
enterFrameSignal = new NativeSignal(this, Event.ENTER_FRAME);
menuSprite = new Sprite();
menuSprite.x = menuSprite.y = 10;
addChild(menuSprite);
contractMask = getMask();
expandMask = getMask();
parse(data);
initChildAndParentIndices();
addChildren();
contractAll();
}
private function getMask():Shape {
var s:Shape = new Shape();
s.graphics.beginFill(0xFF0000, 0);
s.graphics.drawRect(0, 0, 10, 10);
s.graphics.endFill();
return s;
}
private function parse(node:XML, level:int = -1):void{
var item:TreeItem = new TreeItem(node, level);
item.width = ITEM_WIDTH; item.height = ITEM_HEIGHT;
item.clickSignal.add(onItemClick);
items.push( { sprite:item, level:level, tweenY:0, parentIndex:-1, childIndices:[], visible:Boolean } );
node.item.(parse(valueOf(), level + 1));
}
private function addChildren():void {
for each (var o:Object in items) {
menuSprite.addChild(o.sprite);
}
}
private function initChildAndParentIndices():void {
var levelsObject:Object = { };
var lastLevel:int = -1;
for (var i:int = 0; i < items.length; i++) {
var o:Object = items[i];
if (lastLevel < o.level){
o.parentIndex = i - 1;
levelsObject["l" + o.level] = i;
}else
o.parentIndex = levelsObject["l" + o.level] - 1;
if(o.parentIndex){
var parentSprite:TreeItem = items[o.parentIndex].sprite as TreeItem;
if (parentSprite.hasChildren) items[o.parentIndex].childIndices.push(i);
}
lastLevel = o.level;
}
}
private function draw():void {
var iy:int = 0;
for (var i:int = 0; i < items.length; i++) {
var o:Object = items[i];
o.tweenY = iy;
var item:TreeItem = o.sprite;
if (o.visible) {
item.visible = true;
TweenLite.to(item, tweenDuration, { alpha:1, y:iy, ease:tweenEase } ); // Tween in visible items
iy += ITEM_HEIGHT;
}else {
TweenLite.to(item, tweenDuration, { alpha:1, y:iy, ease:tweenEase, onComplete:hideItemSprite, onCompleteParams:[i] } ); // Set visible to false after tween out
}
}
tweenMasks();
TweenLite.delayedCall(tweenDuration, afterTween);
}
private function afterTween():void {
for each (var o:Object in items) o.sprite.mask = null;
if(menuSprite.contains(contractMask)) menuSprite.removeChild(contractMask);
if(menuSprite.contains(expandMask)) menuSprite.removeChild(expandMask);
}
private function tweenMasks():void {
contractMask.width = expandMask.width = ITEM_WIDTH;
menuSprite.addChild(contractMask); contractMask.y = 0; contractMask.height = 1;
menuSprite.addChild(expandMask); expandMask.y = 0; expandMask.height = 1;
var expandItems:Array = [];
var contractItems:Array = [];
if (itemsCopy) {
for (var i:int = 0; i < items.length; i++) {
if (items[i].visible != itemsCopy[i].visible) {
if (items[i].visible) {
expandItems.push(items[i]);
items[i].sprite.mask = expandMask; // Mask expanding items
}else {
contractItems.push(items[i]);
items[i].sprite.mask = contractMask; // Mask contracting items
}
}
}
if (expandItems.length > 0) { // Tween expand mask
expandMask.y = expandItems[0].sprite.y;
TweenLite.to(expandMask, tweenDuration, { y:expandItems[0].tweenY, height:expandItems.length * ITEM_HEIGHT, ease:tweenEase } );
}
if(contractItems.length>0){ // Tween contract mask
contractMask.y = contractItems[0].sprite.y;
contractMask.height = contractItems.length * ITEM_HEIGHT;
TweenLite.to(contractMask, tweenDuration, { y:contractItems[0].tweenY, height:0, ease:tweenEase } );
}
}
}
private function hideItemSprite(index:int):void {
items[index].sprite.visible = false;
}
private function expandIndex(index:int):void {
var o:Object = items[index];
contractLevel(o.level); // Contract other nodes at the same level
if (o.level > 0) expandIndex(o.parentIndex); // Expand parent nodes
for (var i:int = 0; i < o.childIndices.length; i++)
items[o.childIndices[i]].visible = true; // Show children on node
invalidate();
}
private function contractIndex(index:int):void {
var o:Object = items[index];
for (var i:int = 0; i < o.childIndices.length; i++) {
items[o.childIndices[i]].visible = false;
contractIndex(o.childIndices[i]); // Contract all children
}
invalidate();
}
private function contractLevel(level:int):void {
for (var i:int = 0; i < items.length; i++) {
if (items[i].level == level)
contractIndex(i);
}
invalidate();
}
private function contractAll():void {
for each (var o:Object in items) {
o.visible = o.level == 0 ? true : false;
}
invalidate();
}
private function deselectAll():void {
for each (var o:Object in items) {
o.sprite.selected = false;
}
selectedIndex = -1;
invalidate();
}
private function selectIndex(index:int):void {
deselectAll();
items[index].sprite.selected = true;
selectedIndex = index;
expandIndex(index);
}
private function selectFirstLeaf(index:int):void {
if (items[index].childIndices.length > 0) {
var o:Object = items[index];
var childIndex:int = o.childIndices[0];
items[childIndex].childIndices.length > 0 ? selectFirstLeaf(childIndex) : selectIndex(childIndex);
}else {
selectIndex(index);
}
}
private function copyItems():void {
itemsCopy = new Vector.<Object>();
for each (var o:Object in items) {
itemsCopy.push({item:o.sprite, level:o.level, childIndices:o.childIndices, visible:o.visible } );
}
}
private function invalidate():void{
enterFrameSignal.addOnce(onInvalidate);
}
/*****************************************************************************************/
/* EVENT HANDLERS
/*****************************************************************************************/
private function onInvalidate(e:Event):void{
enterFrameSignal.remove(onInvalidate);
draw();
}
private function onItemClick(e:MouseEvent):void {
copyItems(); // Copy current items props
deselectAll();
for (var i:int = 0; i < items.length; i++) {
if (items[i].sprite == e.currentTarget) {
if (items[i].childIndices.length > 0) {
selectFirstLeaf(i); // Branch - open and select first leaf
}else
selectIndex(i); // Leaf - set selected
}
}
}
}
}
/*****************************************************************************************/
/* TREE ITEM CLASS
/*****************************************************************************************/
import flash.display.*;
import flash.events.*;
import flash.text.*;
import gs.TweenLite;
import gs.easing.Expo;
import gs.plugins.TweenPlugin;
import gs.plugins.TintPlugin;
import idv.cjcat.signals.NativeSignal;
class TreeItem extends Sprite {
private var _width:int = 250;
private var _height:int = 25;
private var _margin:int = 10;
private var _indent:int = 25;
private var _data:XML;
private var _level:int;
private var _selected:Boolean;
private var _bgColor:uint = 0xCCCCCC;
private var _frameColor:uint = 0xFFFFFF;
private var _normalColor:uint = 0x666666;
private var _overColor:uint = 0x888888;
private var _selectedColor:uint = 0xCC0000;
private var _selectedOverColor:uint = 0xFF0000;
public var clickSignal:NativeSignal;
private var enterFrameSignal:NativeSignal;
private var tf:TextField;
private var bg:Shape;
private var frame:Shape;
public function TreeItem(data:XML, level:int):void {
mouseChildren = false; mouseEnabled = true; buttonMode = true; useHandCursor = true;
enterFrameSignal = new NativeSignal(this, Event.ENTER_FRAME);
clickSignal = new NativeSignal(this, MouseEvent.CLICK); clickSignal.add(onClick);
new NativeSignal(this, MouseEvent.MOUSE_OVER).add(onMouseOver);
new NativeSignal(this, MouseEvent.MOUSE_OUT).add(onMouseOut);
frame = new Shape(); addChild(frame);
bg = new Shape(); addChild(bg);
tf = new TextField();
tf.x = _margin + level * _indent;
tf.autoSize = TextFieldAutoSize.LEFT;
tf.selectable = false;
tf.defaultTextFormat = new TextFormat("Arial", 14, _normalColor, true);
addChild(tf);
this.data = data;
}
public function draw():void {
tf.text = _data && _data.text() ? _data.text() : "---";
if (hasChildren) tf.text = "+ " + tf.text;
tf.y = height / 2 - tf.height / 2;
var tfm:TextFormat = tf.defaultTextFormat;
tfm.color = selected ? _selectedColor : _normalColor;
tf.setTextFormat(tfm);
frame.graphics.clear();
frame.graphics.beginFill(_frameColor, 1);
frame.graphics.drawRect(0, 0, width, height);
frame.graphics.endFill();
bg.graphics.clear();
bg.graphics.beginFill(_bgColor, 1);
bg.graphics.drawRoundRect(1, 1, width-2, height-2, 8);
bg.graphics.endFill();
}
private function invalidate():void{
enterFrameSignal.addOnce(onInvalidate);
}
/*****************************************************************************************/
/* EVENT HANDLERS
/*****************************************************************************************/
private function onInvalidate(e:Event):void{
enterFrameSignal.remove(onInvalidate);
draw();
}
private function onClick(e:MouseEvent):void{
new TweenLite(tf, .2, {tint:null});
}
private function onMouseOver(e:MouseEvent):void{
new TweenLite(tf, .2, {tint:selected ? _selectedOverColor : _overColor});
}
private function onMouseOut(e:MouseEvent):void{
new TweenLite(tf, .2, {tint:null});
}
/*****************************************************************************************/
/* GETTERS/SETTERS
/*****************************************************************************************/
override public function set width(value:Number):void {
_width = value;
invalidate();
}
override public function get width():Number { return _width; }
override public function set height(value:Number):void {
_height = value;
invalidate();
}
override public function get height():Number { return _height; }
public function set data(value:XML):void {
_data = value;
invalidate();
}
public function get data():XML { return _data; }
public function set selected(value:Boolean):void {
_selected = value;
invalidate();
}
public function get selected():Boolean { return _selected; }
public function get hasChildren():Boolean {
return data.item.length() > 0;
}
}