# Grouping on Server

## Content

## Enabling Server-Side Grouping with RestCollectionView

Server-Side Grouping is a useful feature that allows to group the data on Server and calculates aggregates for specified properties on Server. By extending `RestCollectionView` class, we can create a custom class called `GroupRestCollectionView`.

#### **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.
* Aggregate Functions: Supports server-side aggregation when grouping is enabled.

> type=info
> This feature is supported for FlexGrid, FlexSheet, MultiRow and Input controls like AutoComplete, ComboBox, ListBox

## 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, 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,PropertyGroupDescription,CollectionViewGroup} from "@mescius/wijmo";

export class RestGroupCollectionView extends RestCollectionView{
    _url: string;
    constructor(url, options?) {
        super();
        this.groupOnServer = true;
        this._url = url;
        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
// parse data
    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(groups: boolean): any {
        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
        let gDescs = this.groupDescriptions;
        if (this._groupOnServer && gDescs.length > 0) {
            // fetch all groups 
            let _groupBy = [];
            let _sortBy = [];
            for (let i = 0; i < gDescs.length; i++) {
                let _prop = (gDescs[i] as PropertyGroupDescription).propertyName;
                _groupBy.push(_prop);
                _sortBy.push(`${_prop} ASC`);
            }
            if (groups) // check if groupby clause required or not
                settings.groupBy = _groupBy;
            settings.orderBy = _sortBy.join(',');
        }

        //update orderBy for group descriptions 
        if (this.sortDescriptions.length > 0) {
            let _sortBy = [];
            //check existing sort
            if (settings.orderBy) {
                _sortBy = settings.orderBy.split(',');
            }
            for (let i = 0; i < this.sortDescriptions.length; i++) {
                let sort = `${this.sortDescriptions[i].property} ${this.sortDescriptions[i].ascending ? 'ASC' : 'DESC'}`;
                var _cSrtIdx = _sortBy.findIndex(x => x.indexOf(this.sortDescriptions[i].property) > -1);
                if (_cSrtIdx > -1)
                    _sortBy[_cSrtIdx] = sort; // update existing sort
                else
                    _sortBy.push(sort);// add new sort
            }
            settings.orderBy = _sortBy.join(',');
        }
        // set aggregates if required (only with server side grouping )
        if (groups && this.aggregates && this.aggregates.length > 0 && this.groupDescriptions.length > 0 && settings.groupBy) {
            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(): Promise<any[]> {
        // cancel any pending requests
        if (this._pendingReq) {
            this._pendingReq.abort();
        }
        return new Promise<any>(resolve => {
            let _settings = this._getReadParams(false); // 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(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
    });
}
```

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