# Group Lazy-Loading

## Content

## Enabling Server Side Group LazyLoading using RestCollectionView

Groups lazy-loading is most beneficial when dealing with hierarchical data that must be loaded in chunks, lowering initial data load and enhancing efficiency. With this feature, just top-level groups are loaded at first, and the remainder of the data is loaded upon request by expanding the specified group.
The RestCollectionView class allows lazy loading of groups by setting the `groupLazyLoading` and `groupOnServer` property to true. By extending the RestCollectionView class, we can create a custom class called `GroupRestCollectionView` that would allow to load only top-level groups initially.

#### **Supported operations**

* Server-Side Grouping: Groups data on the server
* Server-Side Filtering and Sorting: Applies filters and sorting on the server side to optimize data retrieval.
* Group Lazy Loading: allows to load data in a grouped manner and loads group specific data for expanded group
* Aggregate Functions: Supports server-side aggregation when grouping is enabled.

> type=info
> This feature is supported for FlexGrid, MultiRow.

## Extending RestCollectionView

<span style="color: rgb(23, 43, 77); font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.08px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: pre-wrap; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">For Server-Side Grouping feature with lazy-loading, we'll need to initialize our variables and override </span>`getItems`<span style="color: rgb(23, 43, 77); font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.08px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: pre-wrap; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"> and </span>`getGroupItems`<span style="color: rgb(23, 43, 77); font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.08px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: pre-wrap; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"> method to fetch data from Server</span>

```auto
import { RestCollectionView } from '@mescius/wijmo.rest';
import {copy,httpRequest,asNumber,CollectionViewGroup} from "@mescius/wijmo";

export class RestLazyLoadGroupCollectionView extends ODataFilterDef {
    _url: string = '';
    constructor(url, options?) {
        super(options);
        this._url = url;
        this.groupOnServer = true;
        this.groupLazyLoading = true;
        copy(this, options);
    }
}
```

<span style="color: rgb(23, 43, 77); font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.08px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: pre-wrap; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">The data received from Sever contains the dates in string format and to convert them to JavaScript Dates objects, we require the JSON Revier method.</span>

```auto
protected _jsonReviver(key: string, value: any): any {
        const _rxDate = /^\d{4}\-\d{2}\-\d{2}T\d{2}\:\d{2}\:\d{2}|\/Date\([\d\-]*?\)/;
        if (typeof value === 'string' && _rxDate.test(value)) {
            value = value.indexOf('/Date(') == 0 // verbosejson
                ? new Date(parseInt(value.substr(6)))
                : new Date(value);
        }
        return value;
    }
```

Before making the request to the server to retrieve the data, we must prepare the request parameters. To do this, we would write the `_getReadParams` method with the following parameters:

* **Filtering**: Converts the filter criteria into OData format and applies it to the request.
* **Sorting**: Constructs an order-by clause based on the sorting descriptions.
* **Grouping**: Constructs a group-by clause and sorts the grouping properties
* **Sorting**: Additional sorting logic is applied on top of the grouping.
* **Aggregation**: Includes aggregate functions if required when grouping is enabled.

```auto
 _getReadParams(item?: CollectionViewGroup, groupInfo: boolean = false) {
        let gDescs = this.groupDescriptions;
        let settings: any = {};
        // apply filter 
        if (this.filterOnServer && this._filterProvider) {
            let filter = this._asODataFilter(this._filterProvider);
            if (filter.length > 0) {
                settings.filterBy = filter;
            }
        }
        // update groupBy
        if (this._groupOnServer && gDescs.length > 0) {
            // for groups lazyloading
            if (this.groupLazyLoading) {
                if (item) {
                    let filters = []
                    for (let i = 0; i <= (item.isBottomLevel ? gDescs.length - 1 : item.level); i++) {
                        let group = gDescs[i]['propertyName'];
                        const val = item.items[0][group];
                        filters.push(`(${group} eq '${val}')`);
                    }
                    if (settings.filterBy && settings.filterBy.length > 0) {
                        filters.splice(0, 0, settings.filterBy);
                    }
                    if (filters.length > 0) {
                        settings.filterBy = filters.join(' and ');
                    }
                    if (!item.isBottomLevel)
                        settings.groupBy = item.groups[0].groupDescription['propertyName'];

                } else
                    settings.groupBy = gDescs[0]['propertyName'];
            }
        }
        if (!groupInfo)
            delete settings.groupBy
        //update orderBy
        if (this.sortDescriptions.length > 0) {
            let _sortBy = [];
            for (let i = 0; i < this.sortDescriptions.length; i++) {
                let sort = `${this.sortDescriptions[i].property} ${this.sortDescriptions[i].ascending ? 'ASC' : 'DESC'}`;
                _sortBy.push(sort);// add new sort
            }
            settings.orderBy = _sortBy.join(',');
        }
        // set aggregates property if required (only with groupBy)
        if (groupInfo && this.aggregates && this.aggregates.length > 0 && this.groupDescriptions.length > 0) {
            settings.aggregates = this.aggregates;
        }
        return settings;
    }
```

> type=info
> The filters are converted to OData format using the \_asODataFilter function. If your data source does not support ODataFormat, you can create your own method to transform the filter query to the data source's acceptable format.

`_asODataFilter` method code can be get from [RestCollectionView\OData Demo](https://developer.mescius.com/wijmo/demos/Core/CollectionView/RestCollectionView/OData/purejs "https://developer.mescius.com/wijmo/demos/Core/CollectionView/RestCollectionView/OData/purejs") sample’s rest-collection-view-odata.js file
As the request parameters are ready, now we are ready to send the request to the Server to fetch the data and groups with aggregates. For this, `getItems` method would be overridden to get data items and `getGroupItems` to fetch groups with aggregates.

```auto
protected getItems(item): Promise<any[]> {
        // cancel any pending requests
        if (this._pendingReq) {
            this._pendingReq.abort();
        }
        return new Promise<any>(resolve => {
            let _settings = this._getReadParams(item); // get the items virtually 
            this._pendingReq = httpRequest(this._url, {
                requestHeaders: this.requestHeaders,
                data: _settings,
                success: async xhr => {
                    // parse response
                    let resp = JSON.parse(xhr.responseText, this._jsonReviver);
                    let _count = asNumber(resp.totalItemCount);
                    if (_count != this._totalItemCount)
                        this._totalItemCount = _count;
                    resolve(resp.items);
                },
                error: xhr => this._raiseError(xhr.responseText, false),
                complete: xhr => { this._pendingReq = null; }// no pending requests
            });
        });;
    }
    _pendingRequest: XMLHttpRequest;
    //fetch group items
    protected getGroupItems(item?: CollectionViewGroup): Promise<any[]> {

        // cancel any pending requests
        if (this._pendingRequest) {
            this._pendingRequest.abort();
        }

        return new Promise<any>(resolve => {
            let _settings = this._getReadParams(item, true);
            if (this.groupDescriptions.length > 0) {
                httpRequest(this._url, {
                    requestHeaders: this.requestHeaders,
                    data: _settings,
                    success: async xhr => {
                        // parse response
                        let re = xhr.responseText;
                        let resp = JSON.parse(xhr.responseText, this._jsonReviver);
                        if (resp.totalGroupCount) {
                            this._totalGroupItemCount = asNumber(resp.totalGroupCount);
                        }
                        let _count = asNumber(resp.totalItemCount);
                        if (_count != this._totalItemCount)
                            this._totalItemCount = _count;
                        resolve(resp.groupItems);
                    },
                    error: xhr => this._raiseError(xhr.responseText, false),
                    complete: xhr => this._pendingRequest = null // no pending requests
                });
            }
        });
    }
```

<span style="color: rgb(23, 43, 77); font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.08px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: pre-wrap; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">Now, we're able to call the RESTCollectionView in our JavaScript file and use that as our data source for your FlexGrid control:</span>

```auto
// Extended RESTCollectionView class
import { GroupRestCollectionView } from './group-rest-collection-view';
import { FlexGrid } from '@mescius/wijmo.grid';
function init(){
  let cv =  new GroupRestCollectionView(url,{
    aggregates: `Sum(actualCost) as actualCost,Sum(quantity) as quantity`, // perform aggregation for groups
    groupDescriptions: [
        // group description to add groups
        new PropertyGroupDescription('productName'),
        new PropertyGroupDescription('transactionType')
    ]
  }); // create CollectionView Instance
  let grid = new FlexGrid("#virtualGrid", {
        autoGenerateColumns: false,
        columns: [
            { binding: 'productId', header: 'Product ID', width: '' },
            { binding: 'color', header: 'Color', width: '' },
            { binding: 'modifiedDate', header: 'Modified Date', dataType: 'Date', format: 'd' },
            { binding: 'quantity', header: 'Quantity', dataType: 'Number', format: 'n2' },
            { binding: 'actualCost', header: 'Actual Cost', dataType: 'Number', format: 'n2',aggregate:'Sum'}
        ],
        itemsSource:cv // assign Custom Collection View Instance
    });
}
```

> type=note
> Note:
>
> * This feature cannot be used in combination with virtualization feature.
> * Pagination is not supported
> * collpaseGroupsToLevel() method and Ctrl+Click for bulk expansion are intentionally limited

You can check a sample implementation with server and client code **[Wijmo-Rest-CollectionView-Sample](https://github.com/wijmo/Wijmo-Rest-CollectionView-Sample)**