Issue Details (XML | Word | Printable)

Key: GDS-563
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: William Draï
Reporter: benoit moraillon
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
GraniteDS

Advanceddatagrid grouping and PageQuery

Created: 16/Nov/09 09:53 AM   Updated: 05/Jan/11 10:32 AM   Resolved: 05/Jan/11 10:32 AM
Component/s: Seam services
Affects Version/s: 2.1.0_RC1
Fix Version/s: 2.2.0_SP1


 Description  « Hide
Advanceddatagrid grouping doesn't work with PagedQuery
but it works perfectly with a RemoteList.

William Draï added a comment - 01/Dec/09 10:27 AM
Can you post more details (stacktrace or logs).

You can also try with a much bigger client-side page size (500 or 1000), I guess grouping is done on the client so AdvancedDataGrid probably needs a lot more rows than what is displayed.

benoit moraillon added a comment - 12/Mar/10 04:53 PM
No, even if i set a bigger page size.
Reproductible in 2.1.0.GA


William Draï added a comment - 04/Jan/11 05:04 PM
Should work with latest release, but requires to use a specific implementation of GroupingCollection2 (see in attach).

William Draï added a comment - 04/Jan/11 05:06 PM
////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2009 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package test.granite.components
{

import mx.collections.*;
import mx.collections.errors.ItemPendingError;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.utils.UIDUtil;
import flash.utils.Dictionary;
import flash.utils.Timer;
import flash.events.TimerEvent;


use namespace mx_internal;

//--------------------------------------
// Other metadata
//--------------------------------------

[DefaultProperty("grouping")]

/**
 * The GroupingCollection2 class lets you create grouped data from flat data
 * for display in the AdvancedDataGrid control.
 * When you create the instance of the GroupingCollection2 from your flat data,
 * you specify the field or fields of the data used to create the hierarchy.
 *
 * <p><b>Note: </b>In the previous release of Flex, you used the GroupingCollection class
 * with the AdvancedDataGrid control.
 * The GroupingCollection2 class is new for Flex 4 and provides better performance
 * than GroupingCollection.</p>
 *
 * <p>To populate the AdvancedDataGrid control with grouped data,
 * you create an instance of the GroupingCollection2 class from your flat data,
 * and then pass that GroupingCollection2 instance to the data provider
 * of the AdvancedDataGrid control.
 * To specify the grouping fields of your flat data,
 * you pass a Grouping instance to
 * the <code>GroupingCollection2.grouping</code> property.
 * The Grouping instance contains an Array of GroupingField instances,
 * one per grouping field. </p>
 *
 * <p>The following example uses the GroupingCollection2 class to define
 * two grouping fields: Region and Territory.</p>
 *
 * <pre>
 * &lt;mx:AdvancedDataGrid id=&quot;myADG&quot;
 * &lt;mx:dataProvider&gt;
 * &lt;mx:GroupingCollection2 id=&quot;gc&quot; source=&quot;{dpFlat}&quot;&gt;
 * &lt;mx:grouping&gt;
 * &lt;mx:Grouping&gt;
 * &lt;mx:GroupingField name=&quot;Region&quot;/&gt;
 * &lt;mx:GroupingField name=&quot;Territory&quot;/&gt;
 * &lt;/mx:Grouping&gt;
 * &lt;/mx:grouping&gt;
 * &lt;/mx:GroupingCollection2&gt;
 * &lt;/mx:dataProvider&gt;
 *
 * &lt;mx:columns&gt;
 * &lt;mx:AdvancedDataGridColumn dataField=&quot;Region&quot;/&gt;
 * &lt;mx:AdvancedDataGridColumn dataField=&quot;Territory&quot;/&gt;
 * &lt;mx:AdvancedDataGridColumn dataField=&quot;Territory_Rep&quot;/&gt;
 * &lt;mx:AdvancedDataGridColumn dataField=&quot;Actual&quot;/&gt;
 * &lt;mx:AdvancedDataGridColumn dataField=&quot;Estimate&quot;/&gt;
 * &lt;/mx:columns&gt;
 * &lt;/mx:AdvancedDataGrid&gt;
 * </pre>
 *
 * @mxml
 *
 * The <code>&lt;mx.GroupingCollection2&gt;</code> inherits all the tag attributes of its superclass,
 * and defines the following tag attributes:</p>
 *
 * <pre>
 * &lt;mx:GroupingCollection2
 * <b>Properties </b>
 * grouping="<i>No default</i>"
 * source="<i>No default</i>"
 * summaries="<i>No default</i>"
 * /&gt;
 * </pre>
 *
 * @see mx.controls.AdvancedDataGrid
 * @see mx.collections.Grouping
 * @see mx.collections.GroupingField
 *
 * @langversion 3.0
 * @playerversion Flash 10
 * @playerversion AIR 1.5
 * @productversion Flex 4
 */
public class GroupingCollection2 extends HierarchicalData implements IGroupingCollection2
{
    
    //--------------------------------------------------------------------------
    //
    // Constructor
    //
    //--------------------------------------------------------------------------

    /**
     * Constructor.
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    public function GroupingCollection2()
    {
        super();
        
        newCollection = new ArrayCollection();
        super.source = newCollection;
        
        objectSummaryMap = new Dictionary(false);
        
        parentMap = {};
    }
    
    //--------------------------------------------------------------------------
    //
    // Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     * @private
     *
     * denotes if the refresh is asynchronous.
     */
    private var async:Boolean = false;
    
    /**
     * @private
     *
     * if true, dispatch collection change events
     */
    private var dispatchCollectionEvents:Boolean = false;
    
    /**
     * @private
     */
    private var newCollection:ArrayCollection;
    
    /**
     * @private
     */
    private var _sourceCol:ICollectionView;
    
    /**
     * @private
     *
     * the object summary map.
     * keeps summaries corresponding to different objects
     */
    private var objectSummaryMap:Dictionary;
    
    /**
     * @private
     *
     * the original sort applied to the source collection
     */
    private var oldSort:Sort;
    
    /**
     * @private
     */
    private var prepared:Boolean = false;
    
    /**
     * @private
     */
    private var flatView:ICollectionView;
    
    /**
     * @private
     */
    private var flatCursor:IViewCursor;
    
    /**
     * @private
     */
    private var hView:ICollectionView;
    
    /**
     * @private
     */
    private var currentPosition:CursorBookmark = CursorBookmark.FIRST;
    
    /**
     * @private
     */
    private var gf:Array;
    
    /**
     * @private
     */
    private var fieldCount:int;
    
    /**
     * @private
     *
     * contains current data being compared
     */
    private var currentData:Object;
    
    /**
     * @private
     *
     * contains current group objects for different group fields
     */
    private var currentGroups:Array;
    
    /**
     * @private
     *
     * contains current group labels for different group fields
     */
    private var currentGroupLabels:Array;
    
    /**
     * @private
     *
     * contains next index for different group fields
     */
    private var currentIndices:Array;
    
    /**
     * @private
     *
     * item index
     */
    private var itemIndex:int;
    
    /**
     * @private
     *
     * the children array for the group
     */
    private var childrenArray:Array;
    
    /**
     * @private
     */
    private var summaryPresent:Boolean;
    
    /**
     * The timer which is associated with an asynchronous refresh operation.
     * You can use it to change the timing interval, pause the refresh,
     * or perform other actions.
     *
     * The default value for the <code>delay</code> property of the
     * Timer instance is 1, corresponding to 1 millisecond.
     *
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    protected var timer:Timer;
    
    /**
     * @private
     * Mapping of UID to parents. Must be maintained as things get removed/added
     * This map is created as objects are visited
     */
    protected var parentMap:Object;
    
    /**
     * @private
     * A Dictionary to maintain summaries
     */
    private var summariesTracker:Dictionary;
    
    /**
     * @private
     * An array to store all the summary fields.
     */
    private var summaryFields:Array;
    
    //--------------------------------------------------------------------------
    //
    // Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    // grouping
    //----------------------------------
    
    private var _grouping:Grouping;
    
    /**
     * Specifies the Grouping instance applied to the source data.
     * Setting the <code>grouping</code> property
     * does not automatically refresh the view,
     * so you must call the <code>refresh()</code> method
     * after setting this property.
     *
     * @see mx.collections.GroupingCollection2#refresh()
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    public function get grouping():Grouping
    {
        return _grouping;
    }
       
    /**
     * @private
     */
    public function set grouping(value:Grouping):void
    {
        _grouping = value;
    }
    
    //----------------------------------
    // summaries
    //----------------------------------
    
    /**
     * Array of SummaryRow instances that define any root-level data summaries.
     * Specify one or more SummaryRow instances to define the data summaries,
     * as the following example shows:
     *
     * <pre>
     * &lt;mx:AdvancedDataGrid id="myADG"
     * width="100%" height="100%"
     * initialize="gc.refresh();"&gt;
     * &lt;mx:dataProvider&gt;
     * &lt;mx:GroupingCollection2 id="gc" source="{dpFlat}"&gt;
     * &lt;mx:summaries&gt;
     * &lt;mx:SummaryRow summaryPlacement="last"&gt;
     * &lt;mx:fields&gt;
     * &lt;mx:SummaryField2 dataField="Actual"
     * label="Min Actual" summaryOperation="MIN"/&gt;
     * &lt;mx:SummaryField2 dataField="Actual"
     * label="Max Actual" summaryOperation="MAX"/&gt;
     * &lt;/mx:fields&gt;
     * &lt;/mx:SummaryRow&gt;
     * &lt;/mx:summaries&gt;
     * &lt;mx:Grouping&gt;
     * &lt;mx:GroupingField name="Region"/&gt;
     * &lt;mx:GroupingField name="Territory"/&gt;
     * &lt;/mx:Grouping&gt;
     * &lt;/mx:GroupingCollection2&gt;
     * &lt;/mx:dataProvider&gt;
     *
     * &lt;mx:columns&gt;
     * &lt;mx:AdvancedDataGridColumn dataField="Region"/&gt;
     * &lt;mx:AdvancedDataGridColumn dataField="Territory_Rep"
     * headerText="Territory Rep"/&gt;
     * &lt;mx:AdvancedDataGridColumn dataField="Actual"/&gt;
     * &lt;mx:AdvancedDataGridColumn dataField="Estimate"/&gt;
     * &lt;mx:AdvancedDataGridColumn dataField="Min Actual"/&gt;
     * &lt;mx:AdvancedDataGridColumn dataField="Max Actual"/&gt;
     * &lt;/mx:columns&gt;
     * &lt;/mx:AdvancedDataGrid&gt;</pre>
     *
     * @see mx.collections.SummaryRow
     * @see mx.collections.SummaryField2
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    public var summaries:Array;

    //--------------------------------------------------------------------------
    //
    // Overriden Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     * The source collection containing the flat data to be grouped.
     *
     * If the source is not a collection, it will be auto-wrapped into a collection.
     *
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    override public function get source():Object
    {
        return _sourceCol;
    }
    
    override public function set source(value:Object):void
    {
        if (_sourceCol)
            _sourceCol.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
        
        if (!value)
        {
            _sourceCol = null;
            return;
        }
            
        _sourceCol = getCollection(value);
        
        if(_sourceCol is ICollectionView)
            _sourceCol.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
    }
    
    /**
     * @private
     */
    override public function getChildren(node:Object):Object
    {
        var children:Object = super.getChildren(node);
        
        // populate the parentMap
        // parentMap will be populated only if the children is ICollectionView
        var uid:String;
        if (children != null)
        {
            if (children is ICollectionView)
            {
                var cursor:IViewCursor = ICollectionView(children).createCursor();
                while (!cursor.afterLast)
                {
                    uid = UIDUtil.getUID(cursor.current);
                    parentMap[uid] = node;
                    cursor.moveNext();
                }
            }
            else
            {
                //if the children is not ICollectionView then
                //it was not introduced by GC. (this happens in the case of XML.)
                return null;
            }
        }
        
        return children;
    }
    
    /**
     * Return <code>super.source</code>, if the <code>grouping</code> property is set,
     * and an ICollectionView instance that refers to <code>super.source</code> if not.
     *
     * @return The object to return.
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    override public function getRoot():Object
    {
        return super.source;
    }
    
    //--------------------------------------------------------------------------
    //
    // Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     * @inheritDoc
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    public function refresh(async:Boolean = false, dispatchCollectionEvents:Boolean = false):Boolean
    {
        this.async = async;
        this.dispatchCollectionEvents = async ? true : dispatchCollectionEvents;
        
        var resetEvent:CollectionEvent;
        
        // return if no grouping or groupingFields are supplied
        if (!grouping || grouping.fields.length < 1 )
        {
            super.source = source;
            // dispatch collection change event of kind reset.
            resetEvent =
                    new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
            resetEvent.kind = CollectionEventKind.RESET;
            dispatchEvent(resetEvent);
            return true;
        }
        
        super.source = newCollection;
        
        // dispatch collection change event of kind reset.
        resetEvent =
                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
        resetEvent.kind = CollectionEventKind.RESET;
        dispatchEvent(resetEvent);
        
        // reset the parent map
        parentMap = {};
        
        // reset the object summary map
        objectSummaryMap = new Dictionary(false);
        
        // reset the summaryTracker
        summariesTracker = null;
        
        // check if any summary is specified
        summaryPresent = false;
        prepareSummaryFields();
                
        var grouped:Boolean;
        if(source && grouping)
        {
            grouped = makeGroupedCollection();
        }
        
        return grouped;
    }
    
    /**
     * @inheritDoc
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    public function cancelRefresh():void
    {
        if (timer)
        {
            timer.stop();
            timer = null;
            cleanUp();
        }
    }
    
    /**
     * @private
     *
     * check for the existing groups if they can accomodate the item
     * otherwise create new group and place the item.
     */
    private function addItem(node:Object, depth:int = 0):void
    {
        var coll:ICollectionView = super.source as ICollectionView;
        var parent:Object = null;
        var i:int = 0;
        while (i < grouping.fields.length)
        {
            // check for the parent item
            var obj:Object = checkForParentExistence(coll, node, i);
            // if no parent, then use the previous parent
            if (!obj)
                break;
            // change the parent to point to its parent and start looking for parent again
            parent = obj;
            
            if (parent)
            {
                coll = getChildren(parent) as ICollectionView;
                i++;
            }
        }
        
        // parent found
        if (parent)
        {
            coll = getChildren(parent) as ICollectionView;
            
            // create groups for the item as no existing group matched with the item
            if (i <= grouping.fields.length-1)
            {
                // create groups and place item
                createGroupsAndInsertItem(coll, node, i);
                // update the summary for the parents
                updateSummary(node);
                return;
            }
            // if there already exist a group for the item and insert the item in that group
            if (coll is IList)
            {
                // insert the item
                IList(coll).addItem(node);
                // update the parent map
                updateParentMap(parent, node);
                // update the summary for the parents
                updateSummary(node);
            }
        }
        else
        {
            // no groups for the item, create groups and add item to them
            createGroupsAndInsertItem(super.source as ICollectionView, node);
            // update the summary for the parents
            updateSummary(node);
        }
        
    }
    
    /**
     * @private
     *
     * check for existence of the groups for an item in the collection
     * and return all the parent of that item.
     */
    private function checkForParentExistence(coll:ICollectionView, node:Object, depth:int):Object
    {
        var cursor:IViewCursor = coll.createCursor();
        
        var label:String = getDataLabel(node, grouping.fields[depth]);
        
        while (!cursor.afterLast)
        {
            var current:Object = cursor.current;
            // check for matching fields
            if (current.hasOwnProperty(grouping.label) &&
                current[grouping.label] == label)
            {
                return current;
            }
            cursor.moveNext();
        }
        
        return null;
    }
    
    /**
     * @private
     *
     * creating the group for the item in the collection
     * and placing it there.
     */
    private function createGroupsAndInsertItem(coll:ICollectionView, node:Object, depth:int = 0):void
    {
        var parent:Object;
        var n:int = grouping.fields.length;
        for (var i:int = depth; i < n ; i++)
        {
            // get the dataField and value
            var dataField:String = grouping.fields[i].name;
            var value:String = getDataLabel(node, grouping.fields[i]);
            
            if (!value)
                return;
                
            // create a new group
            var obj:Object;
            if (grouping.fields[i].groupingObjectFunction != null)
                obj = grouping.fields[i].groupingObjectFunction(value);
            else if (grouping.groupingObjectFunction != null)
                obj = grouping.groupingObjectFunction(value);
            else
                obj = {};
            
            obj[childrenField] = new ArrayCollection();
            
            obj[grouping.label] = value;
            
            if (coll is IList)
            {
                IList(coll).addItem(obj);
                
                // get the parent and update parent map
                parent = parent != null ? parent : getParent(coll.createCursor().current);
                updateParentMap(parent, obj);
                
                coll = getChildren(obj) as ICollectionView;
                parent = obj;
                // if we reach the end of the fields, just insert the item in the collection
                if (i == n - 1)
                {
                    IList(coll).addItem(node);
                    // update the parent map
                    updateParentMap(obj, node);
                    break;
                }
            }
        }
    }
    
    /**
     * @private
     *
     * returns the collection view of the given object
     */
    private function getCollection(value:Object):ICollectionView
    {
        // handle strings and xml
        if (typeof(value)=="string")
            value = new XML(value);
        else if (value is XMLList)
            value = new XMLListCollection(value as XMLList);
        
        if (value is XML)
        {
            var xl:XMLList = new XMLList();
            xl += value;
            return new XMLListCollection(xl);
        }
        //if already a collection dont make new one
        else if (value is ICollectionView)
        {
            return ICollectionView(value);
        }
        else if (value is Array)
        {
            return new ArrayCollection(value as Array);
        }
        //all other types get wrapped in an ArrayCollection
        else if (value is Object)
        {
            // convert to an array containing this one item
            var tmp:Array = [];
            tmp.push(value);
            return new ArrayCollection(tmp);
        }
        else
        {
            return new ArrayCollection();
        }
    }
    
    /**
     * @private
     * get the data label from user specified function.
     * otherwise get the label from the data.
     */
    private function getDataLabel(data:Object,field:GroupingField):String
    {
        if (field.groupingFunction != null)
            return field.groupingFunction(data, field);
        
        // should we create a defaultGroupLabelValue property
        return data.hasOwnProperty(field.name) ? data[field.name] : "Not Available";
    }
    
    /**
     * Returns the parent of a node.
     * The parent of a top-level node is <code>null</code>.
     *
     * @param node The Object that defines the node.
     *
     * @return The parent node containing the node as child,
     * <code>null</code> for a top-level node,
     * and <code>undefined</code> if the parent cannot be determined.
     *
     * @langversion 3.0
     * @playerversion Flash 10
     * @playerversion AIR 1.5
     * @productversion Flex 3
     */
    protected function getParent(node:Object):*
    {
        var uid:String = UIDUtil.getUID(node);
        if (parentMap.hasOwnProperty(uid))
            return parentMap[uid];
            
        return undefined;
    }
    
    /**
     * @private
     * Generate the root level summaries
     */
    private function generateRootSummaries(flatData:Boolean = false):void
    {
        var coll:ICollectionView = super.source as ICollectionView;
        
        getSummaries(coll, -1);
    }
    
    /**
     * @private
     * Calculate summaries for a node.
     */
    private function getSummariesForRow(node:Object, collection:ICollectionView, depth:int):void
    {
        // calculate the summary at the last level
        var hd:HierarchicalData = new HierarchicalData(collection);
        hd.childrenField = this.childrenField;
        var hColl:ICollectionView = new HierarchicalCollectionView(hd, {});
        var hCursor:IViewCursor = new LeafNodeCursor(HierarchicalCollectionView(hColl), hColl, hd);
        
        // for all summary fields
        var m:int = summaryFields.length;
        var i:int = 0;
        
        var defaultSummaryCalculator:DefaultSummaryCalculator = new DefaultSummaryCalculator();
        var summaryMap:Dictionary = new Dictionary(false);
        var summaryField:SummaryField2;
        var summaryCalculator:ISummaryCalculator;
        
        for (i = 0; i < m; i++)
        {
            summaryField = summaryFields[i];
            
            if (!(summaryField.summaryOperation is String) &&
                summaryField.summaryOperation is ISummaryCalculator)
                summaryCalculator = ISummaryCalculator(summaryField.summaryOperation);
            else
                summaryCalculator = defaultSummaryCalculator;
            
            summaryMap[summaryField] = summaryCalculator.summaryCalculationBegin(summaryField);
        }
        
        while (!hCursor.afterLast)
        {
            for (i = 0; i < m; i++)
            {
                summaryField = summaryFields[i];
                
                if (!(summaryField.summaryOperation is String) &&
                    summaryField.summaryOperation is ISummaryCalculator)
                    summaryCalculator = ISummaryCalculator(summaryField.summaryOperation);
                else
                    summaryCalculator = defaultSummaryCalculator;
                
                summaryCalculator.calculateSummary(summaryMap[summaryField], summaryField, hCursor.current);
            }
            
            hCursor.moveNext();
        }
        
        for (i = 0; i < m; i++)
        {
            var summary:Number = 0.0;
            summaryField = summaryFields[i];
            
            if (!(summaryField.summaryOperation is String) &&
                summaryField.summaryOperation is ISummaryCalculator)
                summaryCalculator = ISummaryCalculator(summaryField.summaryOperation);
            else
                summaryCalculator = defaultSummaryCalculator;
            
            summary = summaryCalculator.returnSummary(summaryMap[summaryField], summaryField);
            
            populateSummary(node, summaryField, summaryMap[summaryField], summary);
        }
    }
    
    /**
     * @private
     * Store the summary in a dictionary for later use.
     */
    private function populateSummary(node:Object, summaryField:SummaryField2, summaryObject:Object, summary:Number):void
    {
        var op:String = summaryField.summaryOperation.toString();
        
        if (summariesTracker == null)
            summariesTracker = new Dictionary(false);
        
        if (summariesTracker[node] == undefined)
            summariesTracker[node] = new Dictionary(false);
        if (summariesTracker[node][op] == undefined)
            summariesTracker[node][op] = new Dictionary(false);
        if (summariesTracker[node][op][summaryField.dataField] == undefined)
            summariesTracker[node][op][summaryField.dataField] = new Dictionary(false);
        
        summariesTracker[node][op][summaryField.dataField] = {summaryObject:summaryObject, value:summary};
    }
    
    /**
     * @private
     * Get the summary from the dictionary
     */
    private function getSummary(node:Object, summaryField:SummaryField2):Object
    {
        var op:String = summaryField.summaryOperation.toString();
        
        if (summariesTracker == null ||
            summariesTracker[node] == undefined ||
            summariesTracker[node][op] == undefined ||
            summariesTracker[node][op][summaryField.dataField] == undefined)
        return null;
        
        return summariesTracker[node][op][summaryField.dataField];
    }
    
    /**
     * @private
     * Calcualte and insert summary for a node
     */
    private function getSummaries(node:Object, depth:int):void
    {
        if (depth > grouping.fields.length - 1)
            return;
        
        var children:ICollectionView = this.getChildren(node) as ArrayCollection;
        
        if (node == super.source)
            children = node as ICollectionView;
        
        if (!children || children.length == 0)
            return;
        
        if (depth == grouping.fields.length - 1)
        {
            // calculate actual summaries here
            getSummariesForRow(node, children, depth);
        }
        else
        {
            var cursor:IViewCursor = children.createCursor();
            var isFirst:Boolean = true;
            
            var defaultSummaryCalculator:DefaultSummaryCalculator = new DefaultSummaryCalculator();
            var summaryField:SummaryField2;
            var summaryMap:Dictionary = new Dictionary(false);
            var summaryCalculator:ISummaryCalculator;
            
            while (!cursor.afterLast)
            {
                var current:Object = cursor.current;
                if (!(current is SummaryObject))
                {
                    // for all summary fields
                    var m:int = summaryFields.length;
                    var i:int = 0;
                    
                    for (i = 0; i < m; i++)
                    {
                        summaryField = summaryFields[i];
                        
                        if (!(summaryField.summaryOperation is String) &&
                            summaryField.summaryOperation is ISummaryCalculator)
                            summaryCalculator = ISummaryCalculator(summaryField.summaryOperation);
                        else
                            summaryCalculator = defaultSummaryCalculator;
                        
                        var summaryObject:Object = getSummary(current, summaryField);
                        
                        if (isFirst)
                        {
                            summaryMap[summaryField] = summaryCalculator.summaryOfSummaryCalculationBegin(summaryObject["summaryObject"], summaryField);
                        }
                        else
                        {
                            summaryCalculator.calculateSummaryOfSummary(summaryMap[summaryField], summaryObject["summaryObject"], summaryField);
                        }
                    }
                }
                
                isFirst = false;
                cursor.moveNext();
            }
            
            // check if there were some items
            if (!isFirst)
            {
                for (i = 0; i < m; i++)
                {
                    summaryField = summaryFields[i];
                    
                    if (!(summaryField.summaryOperation is String) &&
                        summaryField.summaryOperation is ISummaryCalculator)
                        summaryCalculator = ISummaryCalculator(summaryField.summaryOperation);
                    else
                        summaryCalculator = defaultSummaryCalculator;
                    
                    var summary:Number = summaryCalculator.returnSummaryOfSummary(summaryMap[summaryField], summaryField);
                    
                    populateSummary(node, summaryField, summaryMap[summaryField], summary);
                }
            }
        }
        
        if (depth == -1)
            insertSummaries(super.source as ICollectionView, -1, true);
        else
            insertSummaries(node, depth);
    }
    
    private function insertSummaries(node:Object, depth:int, rootSummary:Boolean = false):void
    {
        var summaries:Array = this.summaries;
        
        if (!rootSummary)
            summaries = grouping.fields[depth].summaries;
        
        if (!summaries)
            return;
        
        var summaryObj:Array = [];
        
        var n:int = summaries.length;
        for (var i:int = 0; i < n; i++)
        {
            var summaryRow:SummaryRow = summaries[i];
            summaryObj[i] =
                summaryRow.summaryObjectFunction != null ?
                summaryRow.summaryObjectFunction() : new SummaryObject();
            
            // for all summary fields
            var m:int = summaryRow.fields.length;
            for (var j:int = 0; j < m; j++)
            {
                var summaryField:SummaryField2 = summaryRow.fields[j];
                var summary:Number = 0.0;
                var label:String = summaryField.label ? summaryField.label : summaryField.dataField;
                
                var summaryObject:Object = getSummary(node, summaryField);
                
                if (summaryObject != null)
                {
                    summary = summaryObject["value"];
                    // populate the summary object
                    summaryObj[i][label] = summary;
                }
            }
            // populate the object summary map
            if (objectSummaryMap[node] == undefined)
                objectSummaryMap[node] = [];
            
            objectSummaryMap[node].push(summaryObj[i]);
        }
        
        // insert the summary
        if (rootSummary)
            insertRootSummary(summaryObj);
        else
            insertSummary(node, summaryObj, summaries);
        
    }
    
    /**
     * @private
     *
     * initialize the variables
     *
     */
    private function initialize():void
    {
        currentData = null;
        currentGroups = [];
    
        currentGroupLabels = [];
        currentIndices = [] ;
        
        childrenArray = null;
    }
    
    /**
     * @private
     *
     * insert the root summaries
     *
     */
    private function insertRootSummary(summaryObj:Array):void
    {
        var coll:ICollectionView = super.source as ICollectionView;
        
        if (!(coll is IList))
            return;
        
        var n:int = summaryObj.length;
        for (var i:int = 0; i < n; i++)
        {
            var summaryRow:SummaryRow = summaries[i];
            
            if (summaryRow.summaryPlacement.indexOf("first") != -1)
            {
                IList(coll).addItemAt(summaryObj[i], 0);
            }
            
            if (summaryRow.summaryPlacement.indexOf("last") != -1)
            {
                IList(coll).addItem(summaryObj[i]);
            }
        }
    }
    
    /**
     * @private
     *
     * inserts the summaries in the children collection of the parent node
     */
    private function insertSummary(parent:Object, summaryObj:Array, summaries:Array):void
    {
        var n:int = summaries.length;
        for (var i:int = 0; i < n; i++)
        {
            var summaryRow:SummaryRow = summaries[i];
            if (summaryRow.summaryPlacement.indexOf("group") != -1)
            {
                for (var p:String in summaryObj[i])
                    parent[p] = summaryObj[i][p];
            }
            
            var children:IList;
            if (summaryRow.summaryPlacement.indexOf("first") != -1)
            {
                children = (getChildren(parent) as ArrayCollection);
                if (children)
                    children.addItemAt(summaryObj[i], 0);
            }
            
            if (summaryRow.summaryPlacement.indexOf("last") != -1)
            {
                children = (getChildren(parent) as ArrayCollection);
                if (children)
                    children.addItem(summaryObj[i]);
            }
        }
    }
    
    /**
     * @private
     */
    private function makeGroupedCollection():Boolean
    {
        // save the sorting information of the source collection
        // sort the source collection and create a grouped collection
        // restore the sort of the original source collection
        
        var fields:Array = [];
        var n:int = grouping.fields.length;
        for (var i:int = 0; i < n; i++)
        {
            var groupingField:GroupingField = grouping.fields[i];
            var sortField:SortField = new SortField(groupingField.name,
                        groupingField.caseInsensitive,
                        groupingField.descending, groupingField.numeric);
            sortField.compareFunction = groupingField.compareFunction;
            fields.push(sortField);
        }
        
        oldSort = source.sort;
        source.sort = new Sort();
        
        // Set the compare function
        if (grouping.compareFunction != null)
            source.sort.compareFunction = grouping.compareFunction;
        
        source.sort.fields = fields;
        
        var refreshed:Boolean = source.refresh();
        if (!refreshed)
            return refreshed;
        
        if (async)
        {
            timer = new Timer(1);
            timer.addEventListener(TimerEvent.TIMER, timerHandler);
            timer.start();
        }
        else
        {
            return buildGroups();
        }
        
        return true;
    }
    
    /**
     * @private
     */
    private function timerHandler(event:TimerEvent):void {
        if (buildGroups()) {
            timer.stop();
            timer = null;
        }
    }
    
    /**
     * @private
     */
    private function timerOnceHandler(event:TimerEvent):void {
        buildGroups();
    }
    
    /**
     * @private
     *
     * Start building the groups
     */
    private function buildGroups():Boolean
    {
        if (!prepared)
        {
            var _openItems:Object = {};
            
            // initialize the variables
            initialize();
        
            // remove the items from the source collection if there are any.
            newCollection.removeAll();
            
            if ((source as ICollectionView).length == 0)
                return false;
            
            if (dispatchCollectionEvents)
            {
                var hierarchicalData:IHierarchicalData = new HierarchicalData(newCollection);
                HierarchicalData(hierarchicalData).childrenField = childrenField;
                
                hView = new HierarchicalCollectionView(
                                            hierarchicalData, _openItems);
            }
    
            flatView = source as ICollectionView;
            flatCursor = flatView.createCursor();
    
            gf = grouping.fields;
            fieldCount = gf.length;
            
            if (gf)
            {
                prepared = true;
            
                if (async)
                    return false;
            }
            
            if (async)
                return true;
        }
        
        flatCursor.seek(currentPosition);
        
        while(!flatCursor.afterLast && currentPosition != CursorBookmark.LAST)
        {
            currentData = flatCursor.current;
            
            var n:int = 0;
            
            for (var i:int = 0; i < fieldCount ; ++i)
            {
                var groupingField:String = gf[i].name;
                
                var label:String = getDataLabel(currentData, gf[i]);

                if(label != currentGroupLabels[i])
                {
                    if (childrenArray && childrenArray.length)
                    {
                        ArrayCollection(currentGroups[fieldCount - 1][childrenField]).source = childrenArray;
                        childrenArray = [];
                    }
                    
                    // calculate summaries for created groups
                    if (summaryPresent)
                    {
                        for (n = currentGroups.length - 1; n >= i; n--)
                            getSummaries(currentGroups[n], n);
                    }
                    
                    currentGroupLabels.splice(i+1);
                    currentGroups.splice(i+1);
                    currentIndices.splice(i+1);
                    
                    currentGroupLabels[i] = label;
                    
                    // check for grouping Object Function
                    if (gf[i].groupingObjectFunction != null)
                        currentGroups[i] = gf[i].groupingObjectFunction(label);
                    else if (grouping.groupingObjectFunction != null)
                        currentGroups[i] = grouping.groupingObjectFunction(label);
                    else
                        currentGroups[i] = {};
                    
                    currentGroups[i][childrenField] = new ArrayCollection();
                    
                    currentGroups[i][grouping.label] = currentGroupLabels[i];
                    
                    itemIndex = currentIndices[i-1];
                    // create the group
                    if (dispatchCollectionEvents)
                    {
                        IHierarchicalCollectionView(hView).addChild(currentGroups[i-1], currentGroups[i]);
                    } else
                    {
                        if (i > 0)
                        {
                            currentGroups[i-1][childrenField].source.push(currentGroups[i]);
                        }
                        else
                        {
                            newCollection.source.push(currentGroups[i]);
                        }
                    }
                    currentIndices[i-1] = ++itemIndex;
                }
                
                // insert the node as a child of the group
                if ( i == fieldCount - 1)
                {
                    itemIndex = currentIndices[i];
                    if (!childrenArray)
                        childrenArray = [];
                    childrenArray.push(currentData);
                    currentIndices[i] = ++itemIndex;
                }
            }

            try {
                flatCursor.moveNext();
                currentPosition = flatCursor.bookmark;
                // return in case of async refresh
                if (async)
                    return false;
            }
            catch (e:ItemPendingError) {
                e.addResponder(new ItemResponder(
                    function(data:Object, token:Object=null):void
                    {
                        continueMakeGroupedCollection();
                    },
                    function(info:Object, token:Object=null):void
                    {
                        //no-op
                    })
                );
                    
             // Stop the timer while waiting for new data
             if (timer != null) {
timer.stop();
timer = null;
             }
                return false;
            }
        }
        if (currentPosition == CursorBookmark.LAST)
        {
            if (childrenArray && childrenArray.length)
            {
                ArrayCollection(currentGroups[fieldCount - 1][childrenField]).source = childrenArray;
                
                // calculate summaries for created groups
                if (summaryPresent)
                {
                    for (n = fieldCount - 1; n >= 0; n--)
                        getSummaries(currentGroups[n], n);
                }
            }
            
            // calculate root summaries
            if (source && summaries)
            {
                if (!super.source)
                    super.source = new ArrayCollection([source]) as Object;
                
                generateRootSummaries(grouping == null);
            }
            
// refresh the collection to reflect the changes
// we made while grouping.
// This is needed as we made changes directly to
// the source of the collection when
// dispatchCollectionEvents is false
newCollection.refresh();

            // dispatch collection change event of kind refresh.
            var refreshEvent:CollectionEvent =
                    new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
            refreshEvent.kind = CollectionEventKind.REFRESH;
            dispatchEvent(refreshEvent);
            
            cleanUp();
        }
        return true;
    }
    

    /**
     * @private
     * Continue building groups process after IPE result
     */
private function continueMakeGroupedCollection():void
{
        if (async) {
            timer = new Timer(1);
            timer.addEventListener(TimerEvent.TIMER, timerHandler);
            timer.start();
        }
        else {
            timer = new Timer(1, 1);
            timer.addEventListener(TimerEvent.TIMER, timerOnceHandler);
            timer.start();
        }
  }
    
    
    /**
     * @private
     * Get all the summary fields and store them in an Array
     */
    private function prepareSummaryFields():void
    {
        summaryFields = [];
        
        var sr:SummaryRow
        
        // for grouping fields summaries
        if (grouping.fields != null)
        {
            for (var i:int = 0; i < grouping.fields.length; i++)
            {
                var gf:GroupingField = grouping.fields[i];
                if (gf.summaries != null)
                {
                    for (var j:int = 0; j < gf.summaries.length; j++)
                    {
                        sr = gf.summaries[j];
                        if (sr.fields != null)
                        {
                            for (var k:int = 0; k < sr.fields.length; k++)
                            {
                                summaryPresent = true;
                                summaryFields.push(sr.fields[k]);
                            }
                        }
                    }
                }
            }
        }
        
        // for root level summaries
        if (summaries != null)
        {
            for (i = 0; i < summaries.length; i++)
            {
                sr = summaries[i];
                if (sr.fields != null)
                {
                    for (j = 0; j < sr.fields.length; j++)
                    {
                        summaryPresent = true;
                        summaryFields.push(sr.fields[j]);
                    }
                }
            }
        }
    }
    
    /**
     * @private
     *
     * Restores the original sort in the source collection
     * and clean up all the variables.
     */
    private function cleanUp():void
    {
        source.sort = oldSort;
        source.refresh();
        
        prepared = false;
        currentPosition = CursorBookmark.FIRST;
        oldSort = null;
        flatCursor = null;
    }
    
    /**
     * @private
     *
     * It will call the function func on each node of the collection.
     */
    private function applyFunctionForParentNodes(coll:ICollectionView, func:Function, depth:int = 0):void
    {
        if (coll.length > 0)
        {
            var masterCursor:IViewCursor = coll.createCursor();
            
            while(!masterCursor.afterLast)
            {
                var child:Object = getChildren(masterCursor.current);
                if (child is ArrayCollection)
                {
                    var childCursor:IViewCursor = ArrayCollection(child).createCursor();
                    
                    while (!childCursor.afterLast)
                    {
                        var current:Object = childCursor.current;
                        // recurse over each child
                        if (this.hasChildren(current))
                        {
                            applyFunctionForParentNodes(child as ArrayCollection, func, depth + 1);
                            break;
                        }
                        
                        childCursor.moveNext();
                    }
                }
                
                // calculate for the parent node
                func(masterCursor.current, depth);
                masterCursor.moveNext();
            }
        }
    }
    
    /**
     * @private
     *
     * removes the root summaries and
     * generates them again.
     *
     */
    private function regenerateRootSummaries():void
    {
        if(!summaries)
            return;
        
        var coll:ICollectionView = super.source as ICollectionView;
        if (!grouping)
            coll = coll.createCursor().current as ICollectionView;
        
        var summaryObj:Array = objectSummaryMap[coll];
        
        // for the first time there will be no summaries
        // so, this check will fail and root summaries wont
        // be removed and regenerated again.
        // for all the other times, the root summaries will be
        // regenerated.
        if (!summaryObj || !(coll is IList))
            return;
        
        // delete all the root level summaries
        var n:int = summaryObj.length;
        for (var i:int = 0; i < n; i++)
        {
            var index:int = IList(coll).getItemIndex(summaryObj[i]);
            if (index != -1)
                IList(coll).removeItemAt(index);
        }
        
        delete objectSummaryMap[coll];
        
        // generate the root level summaries
        generateRootSummaries(grouping == null);
    }
    
    /**
     * @private
     *
     * removes all the summaries from the collection
     */
    private function removeAllSummaries():void
    {
        // call generate summary with remove summary function as parameter
        applyFunctionForParentNodes(super.source as ICollectionView, removeSummary);
    }
    
    /**
     * @private
     *
     * remove the summaries for an item and its parents.
     * return the parents of the node going one level up each time.
     *
     */
    private function removeItemAndSummaries(coll:ICollectionView, node:Object, removeItem:Boolean = false):Array
    {
        var parentNodes:Array = [];
        var parent:Object = getParent(node);
        while (parent != null)
        {
            var index:int;
            var addParent:Boolean = true;
            var children:ICollectionView = getChildren(parent) as ArrayCollection;
            if (children)
            {
                if (children.contains(node))
                {
                    if (children is IList)
                    {
                        if (removeItem)
                        {
                            // remove the item from the group
                            index = IList(children).getItemIndex(node);
                            if (index != -1)
                            {
                                IList(children).removeItemAt(index);
                                // delete item from parent map
                                var uid:String = UIDUtil.getUID(node);
                                if (parentMap[uid])
                                    delete parentMap[uid];
                            }
                        }
                        
                        if (objectSummaryMap[parent])
                        {
                            var temp:Array = objectSummaryMap[parent];
                            var n:int = temp.length;
                            for (var i:int = 0; i < n; i++)
                            {
                                index = IList(children).getItemIndex(temp[i]);
                                if (index != -1)
                                {
                                    // remove the summary information for the group
                                    IList(children).removeItemAt(index);
                                }
                            }
                            // delete summary from parent summary map
                            delete objectSummaryMap[parent];
                        }
                        
                        if (summariesTracker != null && summariesTracker[parent])
                            summariesTracker[parent] = new Dictionary(false);
                        
                        if (removeItem)
                        {
                            // remove the group if no children is present
                            if (children.length == 0 && getParent(parent) != null)
                            {
                                addParent = false;
                            }
                            else
                                removeItem = false;
                        }
                    }
                }
                if (addParent)
                    parentNodes.push(parent);
                
                node = parent;
                
                parent = getParent(parent);
                if (!parent)
                {
                    // remove the parent node from the collection if its the only one remaining.
                    // first check for the child collection length
                    if (parentNodes.length == 1)
                    {
                        if ((getChildren(parentNodes[0]) as ICollectionView).length == 0 && coll is IList)
                        {
                            index = IList(coll).getItemIndex(node);
                            if (index != -1)
                            {
                                IList(coll).removeItemAt(index);
                                return null;
                            }
                        }
                    }
                    return parentNodes.reverse();
                }
            }
        }
        return null;
    }
    
    /**
     * @private
     *
     * removes the summaries from the parent node and all its children.
     *
     */
    private function removeSummary(parent:Object, depth:int):void
    {
        var children:ICollectionView = this.getChildren(parent) as ArrayCollection;
        
        if (!children)
            return;
        
        // remove summaries from the parents of the current object
        removeItemAndSummaries(super.source as ICollectionView, children.createCursor().current);
    }
    
    /**
     * @private
     *
     * updates the parent map
     */
    private function updateParentMap(parent:Object, node:Object):void
    {
        var uid:String = UIDUtil.getUID(node);
        parentMap[uid] = parent;
    }
    
    /**
     * @private
     *
     * removes the summary information and regenrates it for a particular node
     * and its parents.
     * optionally removes the item from the group also.
     */
    private function updateSummary(node:Object, removeItem:Boolean = false):void
    {
        var coll:ICollectionView = super.source as ICollectionView;
        
        var parentNodes:Array;
        if (summaryPresent || removeItem)
            parentNodes = removeItemAndSummaries(coll, node, removeItem);
        
        if (summaryPresent && parentNodes)
        {
            var n:int = parentNodes.length;
            for (var i:int = n - 1; i >= 0; i--)
            {
                getSummaries(parentNodes[i], i);
            }
        }
    }
    
    //--------------------------------------------------------------------------
    //
    // Event handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     * @private
     *
     * collection change handler.
     *
     */
    private function collectionChangeHandler(event:CollectionEvent):void
    {
        if (!grouping)
            return;
            
        if (!async && event.kind == CollectionEventKind.REFRESH && !prepared && oldSort != null) {
         // Continue grouping when underlying collection is refreshed and when we wait for it
         // oldSort is null indicates that we don't wait for anything as the underlying collection
         // has not been sorted for grouping
continueMakeGroupedCollection();
            return;
        }
        
        var i:int = 0;
        var j:int = 0;
        var n:int = 0;
        var m:int = 0;
        var obj:Object;
        
        if (event.kind == CollectionEventKind.UPDATE)
        {
            n = event.items.length;
            for (i = 0; i < n; i++)
            {
                var summaryCalculated:Boolean;
                // take the source property to get the updated object
                obj = event.items[i].source;
                
                if (!obj)
                    continue;
                
                m = grouping.fields.length;
                for (j = 0; j < m; j++)
                {
                    // check if the property corresponding to the group fields changed
                    if (event.items[i].property == grouping.fields[j].name)
                    {
                        summaryCalculated = true;
                        // update summaries - first remove the item from its group
                        updateSummary(obj,true);
                        // add the item to the group
                        addItem(obj);
                        break;
                    }
                }
                // for other properties just update the summaries
                if (!summaryCalculated)
                    updateSummary(obj);
            }
        }
        
        if (event.kind == CollectionEventKind.ADD)
        {
            n = event.items.length;
            for (i = 0; i < n; i++)
            {
                obj = event.items[i];
                // add the item to the group
                addItem(obj);
            }
        }
        
        if (event.kind == CollectionEventKind.REMOVE)
        {
            n = event.items.length;
            for (i = 0; i < n; i++)
            {
                obj = event.items[i];
                // update summaries - first remove the item from its group
                updateSummary(obj,true);
            }
        }
        
        if (event.kind == CollectionEventKind.REPLACE)
        {
            n = event.items.length;
            for (i = 0; i < n; i++)
            {
                var oldValue:Object = event.items[i].oldValue;
                var newValue:Object = event.items[i].newValue;
                // update summaries - first remove the item from its group
                updateSummary(oldValue,true);
                // add the item to the group
                addItem(newValue);
            }
        }
        
        // generate the root summaries
        regenerateRootSummaries();
    }
}

}