2012/06/17

Time to Eeplace TWaver’s Tree with FastTree

In TWaver Flex edition 2.1 the latest FastTree has been released, the literal meaning of which is Tree “at a higher speed.” It is similar to Tree in function, not multiplexing Flex’s Tree. FastTree is superior to the ordinary Tree in performance and speed, for it only refreshes the visible part. Its imperfection lies in the lack of some decoratory functions, such as the animation effects when expanding nodes and the highlight in the current line when moving the mouse. In the following codes we will display with an example in which aspects FastTree is fast.
  1. Faster in sequencing and filtering
  2. The following example displays the filtering and sequencing functions of FastTree and Tree respectively: At the moment when 10 thousand nodes have been loaded in FastTree, after inputting a filter condition, the result will show up right away; with 1 thousand nodes loaded in the ordinary Tree, 2-3 seconds bring the result out.

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:twaver="http://www.servasoftware.com/2009/twaver/flex"
                    layout="absolute" creationComplete="init()">

        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var input:String = "";

                private function init():void {
                    // Init Filter.
                    fastTree.visibleFunction = function(node:IData):Boolean{
                        if(input == ""){
                            return true;
                        }
                        return isVisible(node);
                    };

                    // Init Sort.
                    fastTree.compareFunction = function(d1:IData, d2:IData):int {
                        if(d1.name > d2.name){
                            return 1;
                        }else if(d1.name == d2.name){
                            return 0;
                        }else{
                            return -1;
                        }
                    };

                    // Init DataBox
                    var i:int = 100;
                    var box:DataBox = fastTree.dataBox;
                    while(i--){
                        var node:Node = new Node();
                        node.name = 'n-' + i;
                        box.add(node);
                        var ii:int = 100;
                        while(ii--){
                            var child:Node = new Node();
                            child.name =  'n-' + i + '-' +ii;
                            node.addChild(child);
                            box.add(child);
                        }
                    }

                    // Expand All Nodes
                    fastTree.callLater(function():void {
                        fastTree.expandAll();
                    });
                }

                // All parents are visible if child is visible
                private function isVisible(item:IData):Boolean {
                    if(item.name.toLowerCase().indexOf(this.input) >= 0) {
                        return true;
                    }
                    var result:Boolean = false;
                    item.children.forEach(function(child:IData):Boolean {
                        if(isVisible(child)) {
                            result = true;
                            return false;
                        }
                        return true;
                    });
                    return result;
                }

                // Refresh FastTree when input is changed.
                private function handleSearchTextChanged():void {
                    this.input = this.textSearch.text.toLowerCase();
                    fastTree.invalidateModel();
                }
            ]]>
        </mx:Script>

        <mx:VBox width="100%" height="100%" >
            <mx:TextInput id="textSearch" change="handleSearchTextChanged()" />
            <mx:HBox width="100%" height="100%" >
                <twaver:FastTree id="fastTree" width="100%" height="100%"/>
            </mx:HBox>
        </mx:VBox>
    </mx:Application>

    TestTreeSortAndFilter.mxml:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:twaver="http://www.servasoftware.com/2009/twaver/flex"
                    layout="absolute" creationComplete="init()">

        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var input:String = "";

                private function init():void {
                    // Init Filter.
                    tree.visibleFunction = function(node:IData):Boolean{
                        if(input == ""){
                            return true;
                        }
                        return isVisible(node);
                    };

                    // Init Sort.
                    tree.compareFunction = function(d1:IData, d2:IData):int {
                        if(d1.name > d2.name){
                            return 1;
                        }else if(d1.name == d2.name){
                            return 0;
                        }else{
                            return -1;
                        }
                    };

                    // Init DataBox
                    var i:int = 100;
                    var box:DataBox = tree.dataBox;
                    while(i--){
                        var node:Node = new Node();
                        node.name = 'n-' + i;
                        box.add(node);
                        var ii:int = 10;
                        while(ii--){
                            var child:Node = new Node();
                            child.name =  'n-' + i + '-' +ii;
                            node.addChild(child);
                            box.add(child);
                        }
                    }

                    // Expand All Nodes
                    tree.callLater(function():void {
                        tree.expandAll();
                    });
                }

                // All parents are visible if child is visible
                private function isVisible(item:IData):Boolean {
                    if(item.name.toLowerCase().indexOf(this.input) >= 0) {
                        return true;
                    }
                    var result:Boolean = false;
                    item.children.forEach(function(child:IData):Boolean {
                        if(isVisible(child)) {
                            result = true;
                            return false;
                        }
                        return true;
                    });
                    return result;
                }

                // Refresh tree when input is changed.
                private function handleSearchTextChanged():void {
                    this.input = this.textSearch.text.toLowerCase();
                    tree.updateCompareAndVisibility();
                }
            ]]>
        </mx:Script>

        <mx:VBox width="100%" height="100%" >
            <mx:TextInput id="textSearch" change="handleSearchTextChanged()" />
            <mx:HBox width="100%" height="100%" >
                <twaver:Tree id="tree" width="100%" height="100%"/>
            </mx:HBox>
        </mx:VBox>
    </mx:Application>
     
  3. Faster in lazy load
  4. In the following example we have imitated the lazy load. 500 child nodes will be loaded every time you unfold a tree node: it costs only tens of milliseconds to load even when tens of thousands of nodes have already been unfolded under FastTree; it needs to takes over 10 seconds to load nodes when 3 thousand nodes have been expanded under Tree.

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex">
        <mx:Script>
            <![CDATA[
                import twaver.*;
                import twaver.controls.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    // Init DataBox
                    for(var i:int=0; i<40; i++){
                        var data:Data = new Data();
                        data.name = "Data" + i;
                        box.add(data);
                    }
                    fastTree.dataBox = box;

                    // Setup Lazy Load
                    fastTree.branchFunction = function(data:IData):Boolean {
                        return true;
                    };

                    // Load Children when expanding
                    fastTree.addInteractionListener(function(event:FastTreeInteractionEvent):void{
                        if(event.kind == FastTreeInteractionEvent.EXPAND &&
                            event.data.childrenCount == 0){
                            loadDatas(event.data);
                        }
                    });
                }

                // Load 500 Nodes
                private function loadDatas(data:IData):void{
                    var startTime:Date = new Date();
                    for(var j:int=0; j<500; j++){
                        var child:Data = new Data();
                        child.name = data.name + "," + j;
                        child.parent = data;
                        box.add(child);
                    }
                    console.text = "Count:"+box.count+"\tExpand Time:"+(new Date().getTime()-startTime.getTime())+"ms";
                }
            ]]>
        </mx:Script>

        <mx:Label id="console" width="400"/>
        <tw:FastTree id="fastTree" width="400" height="100%" editable="true" backgroundColor="#FFFFFF"/>
    </mx:Application>


    TestTreeLazyLoad.mxml:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex">
        <mx:Script>
            <![CDATA[
                import twaver.*;
                import twaver.controls.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    // Init DataBox
                    for(var i:int=0; i<40; i++){
                        var data:Data = new Data();
                        data.name = "Data" + i;
                        box.add(data);
                    }
                    tree.dataBox = box;

                    // Setup Lazy Load
                    tree.branchFunction = function(treeData:TreeData):Boolean {
                        return true;
                    };

                    // Load Children when expanding
                    tree.addInteractionListener(function(event:TreeInteractionEvent):void{
                        if(event.kind == TreeInteractionEvent.EXPAND_TREE_DATA &&
                                event.treeData.data != null &&
                                event.treeData.data.childrenCount == 0){
                            loadDatas(event.treeData.data);
                        }
                    });
                }

                // Load 500 Nodes
                private function loadDatas(data:IData):void{
                    var startTime:Date = new Date();
                    for(var j:int=0; j<500; j++){
                        var child:Data = new Data();
                        child.name = data.name + "," + j;
                        child.parent = data;
                        box.add(child);
                    }
                    console.text = "Count:"+box.count+"\tExpand Time:"+(new Date().getTime()-startTime.getTime())+"ms";
                }
            ]]>
        </mx:Script>

        <mx:Label id="console" width="400"/>
        <tw:Tree id="tree" width="400" height="100%" editable="true" backgroundColor="#FFFFFF"/>
    </mx:Application>
     
  5. Faster operational speed of ExpandAll with huge amount of data
  6. When 50 thousand nodes have been loaded by FastTree, clicking all to unfold them will only cost you several milliseconds; by ordinary Tree it turns out to be abnormal after 1 minute. TestFastTreeExpandAll.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex"
                    paddingLeft="0" paddingRight="0" paddingTop="0" paddingBottom="0" backgroundColor="0xFFFFFF"
                    creationComplete="init()">
        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    this.initBox();
                    trace(box.count);
                    fastTree.dataBox = box;
                }

                private function initBox():void {
                    var root:Data = this.addData("Root");
                    for (var k:int = 0; k < 10; k++) {
                        var ip:String = "10.0." + k + ".";
                        var count:int = 0;
                        var data:Data = this.addData(ip + count++);
                        data.parent = root;

                        for (var i:int = 0; i < 10; i++) {
                            var iData:Data = this.addData(ip + count++);
                            iData.parent = data;
                            for (var j:int = 0; j < 100; j++) {
                                var jData:Data = this.addData(ip + count++);
                                jData.parent = iData;
                            }
                        }
                    }
                }

                private function addData(name:String):Data {
                    var data:Data = new Data();
                    data.name = name;
                    data.toolTip = name;
                    this.box.add(data);
                    return data;
                }
            ]]>
        </mx:Script>

        <mx:HDividedBox width="100%" height="100%">
            <tw:FastTree id="fastTree" width="200" height="100%"/>
            <mx:HDividedBox width="100%" height="100%">
                <mx:Canvas id="canvas" width="100%" height="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off">
                    <mx:HBox id="toolbar" top="0" left="0" horizontalGap="0">
                        <mx:Button label="Expand All" click="fastTree.expandAll();"/>
                        <mx:Button label="Collapse All" click="fastTree.collapseAll();"/>
                    </mx:HBox>
                </mx:Canvas>
            </mx:HDividedBox>
        </mx:HDividedBox>
    </mx:Application>
     
    TestTreeExpandAll.mxml:
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex"
                    paddingLeft="0" paddingRight="0" paddingTop="0" paddingBottom="0" backgroundColor="0xFFFFFF"
                    creationComplete="init()">
        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    this.initBox();
                    trace(box.count);
                    tree.dataBox = box;
                }

                private function initBox():void {
                    var root:Data = this.addData("Root");
                    for (var k:int = 0; k < 10; k++) {
                        var ip:String = "10.0." + k + ".";
                        var count:int = 0;
                        var data:Data = this.addData(ip + count++);
                        data.parent = root;

                        for (var i:int = 0; i < 10; i++) {
                            var iData:Data = this.addData(ip + count++);
                            iData.parent = data;
                            for (var j:int = 0; j < 100; j++) {
                                var jData:Data = this.addData(ip + count++);
                                jData.parent = iData;
                            }
                        }
                    }
                }

                private function addData(name:String):Data {
                    var data:Data = new Data();
                    data.name = name;
                    data.toolTip = name;
                    this.box.add(data);
                    return data;
                }
            ]]>
        </mx:Script>

        <mx:HDividedBox width="100%" height="100%">
            <tw:Tree id="tree" width="200" height="100%"/>
            <mx:HDividedBox width="100%" height="100%">
                <mx:Canvas id="canvas" width="100%" height="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off">
                    <mx:HBox id="toolbar" top="0" left="0" horizontalGap="0">
                        <mx:Button label="Expand All" click="tree.expandAll();"/>
                        <mx:Button label="Collapse All" click="tree.collapseAll();"/>
                    </mx:HBox>
                </mx:Canvas>
            </mx:HDividedBox>
        </mx:HDividedBox>
    </mx:Application>
     
Besides, there are no such bugs as listed below:
Finally, the use of FastTree is similar to that of ordinary Tree but attention need to be paid to some differences between them. Most of them are that all TreeData's have been replaced by IData's. Through the example above you can also find some differences.
  1. The methods to make it refresh after the change of Filter or Sequencing Function.
  2. Replacing
    public function updateCompareAndVisibility(treeData:TreeData = null):void
    public function invalidateTreeData(treeData:TreeData = null):void
    with
    public function invalidateModel():void
     
  3. The methods to judge whether nodes are expanded.
  4. Replacing
    public function isOpen(data:IData):Boolean
    public function isDataOpen(data:IData):Boolean
    with
    public function isExpanded(data:IData):Boolean 
     
  5. The methods to judge whether nodes are visible
  6. Replacing
    public function isTreeDataVisible(treeData:TreeData):Boolean
    public function isDataVisible(data:IData):Boolean
    with
    public function isVisible(data:IData):Boolean
     
  7. The method to judge whether nodes can be expanded
  8. Replacing
    public function isBranch(treeData:TreeData):Boolean
    with
    public function isBranch(data:IData):Boolean
     
  9. The methods to spot a particular node
  10. Replacing
    public function getTreeData(data:IData):TreeData
    public function getDataIndex(data:IData):int
    public function getTreeDataByIndex(index:int):TreeData
    public function getTreeDataByMouseEvent(event:MouseEvent):TreeData
    public function getTreeDataByContextMenuEvent(event:ContextMenuEvent):TreeData
    with
    public function getRowIndexByData(data:IData):int
    public function getRowIndexById(id:Object):int
    public function getDataByRowIndex(index:int):IData
    public function getRowIndexByMouseEvent(e:MouseEvent):int
    public function getDataByMouseEvent(e:MouseEvent):IData
    public function getDataByContextMenuEvent(event:ContextMenuEvent):IData
    public function getRowIndexByStagePoint(point:Point):int
    public function getDataByStagePoint(point:Point):int
     
  11. The method to get the root nodes of the tree
  12. Replacing
    public function get rootTreeData():TreeData
    with
    public function get rootData():IData
     
  13. The method to expand and collapse the tree node
  14. Replacing
    public function expandData(data:IData, animate:Boolean = false):void
    public function collapse(data:IData, animate:Boolean = false):void
    with
    public function expand(data:IData):void
    public function collapse(data:IData):void
     
  15. The method to perform an operation for each tree node
  16. Replacing
    public function forEachTreeData(callbackFunction:Function, treeData:TreeData = null):void
    with
    tree.rowDatas.forEach
     
  17. Substituting TreeInteractionEvent with FastTreeInteractionEvent
  18. public function addInteractionListener(listener:Function, priority:int = 0, useWeakReference:Boolean = false):void 
     
  19. The method to get the user-defined components of tree nodes
  20. Replacing
    public function getIconsComponents(treeData:TreeData):Array
    with
    public function getIconsComponents(data:IData):Array
     
  21. The parameter TreeData of the methods iconsComponentsFunction and branchFunction has been replaced by IData.
  22. public function get iconsComponentsFunction():Function
    public function get branchFunction():Function

No comments:

Post a Comment