# 그룹 지연 로딩

## Content

## RestCollectionView를 사용한 서버 측 그룹 지연 로딩 활성화

그룹 지연 로딩은 계층적 데이터를 청크 단위로 로드해야 할 때 가장 유용하며, 초기 데이터 로드를 줄이고 효율성을 향상시킵니다. 이 기능을 사용하면 처음에는 최상위 그룹만 로드되고, 지정된 그룹을 확장할 때 나머지 데이터가 요청에 따라 로드됩니다.
RestCollectionView 클래스는 `groupLazyLoading` 및 `groupOnServer` 속성을 true로 설정하여 그룹의 지연 로딩을 허용합니다. `RestCollectionView` 클래스를 확장하여 `GroupRestCollectionView` 라는 사용자 정의 클래스를 만들면 처음에 최상위 그룹만 로드할 수 있습니다.

#### **지원되는 기능**

* 서버 측 그룹화: 서버에서 데이터를 그룹화합니다.
* 서버 측 필터링 및 정렬: 데이터 검색을 최적화하기 위해 서버 측에서 필터링과 정렬을 적용합니다.
* 그룹 지연 로딩: 데이터를 그룹화된 방식으로 로드하고 확장된 그룹에 대한 특정 데이터를 로드합니다.
* 집계 함수: 그룹화가 활성화된 경우 서버 측 집계를 지원합니다.

>type=info
> 이 기능은 FlexGrid와 MultiRow에서 지원됩니다.

## RestCollectionView 확장

지연 로딩 기능을 가진 서버 측 그룹화 기능을 위해 변수를 초기화하고, 서버에서 데이터를 가져오기 위해 `getItems`<span style="margin: 0px; padding: 0px; -webkit-font-smoothing: antialiased; box-sizing: border-box; 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; float: none; display: inline !important;"> </span>및 `getGroupItems`<span style="margin: 0px; padding: 0px; -webkit-font-smoothing: antialiased; box-sizing: border-box; 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; float: none; display: inline !important;"> </span>메서드를 재정의해야 합니다.

```javascript
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);
    }
}
```

서버에서 받은 데이터는 문자열 형식의 날짜를 포함하고 있으며, 이를 JavaScript의 Date 객체로 변환하려면 JSON Reviver 메서드가 필요합니다.

```javascript
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;
    }
```

서버에 데이터를 요청하기 전에 요청 파라미터를 준비해야 합니다. 이를 위해 `_getReadParams` 메서드를 다음과 같은 파라미터로 작성합니다:

* **필터링**: 필터 기준을 OData 형식으로 변환하여 요청에 적용합니다.
* **정렬**: 정렬 설명을 바탕으로 order-by 절을 구성합니다.
* **그룹화**: 그룹화 속성을 기준으로 group-by 절을 구성하고 정렬합니다.
* **추가 정렬**: 그룹화 위에 추가적인 정렬 논리를 적용합니다.
* **집계**: 그룹화가 활성화된 경우 필요에 따라 집계 함수를 포함합니다.

```javascript
 _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
> 필터는 \_asODataFilter 함수를 사용하여 OData 형식으로 변환됩니다. 만약 데이터 소스가 ODataFormat을 지원하지 않는다면, 필터 쿼리를 데이터 소스에서 허용하는 형식으로 변환하는 사용자 정의 메서드를 만들 수 있습니다.

`_asODataFilter`<span style="box-sizing: content-box; margin: 0px; padding: 0px; transition: filter 0.5s ease-in-out; line-height: 25.6px; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji, Font Awesome 5 Free; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;"> 메서드 코드는 </span>[RestCollectionView\OData 데모 샘플](https://demo.mescius.co.kr/wijmo/learn-wijmo/Core/CollectionView/RestCollectionView/OData/purejs)<span style="box-sizing: content-box; margin: 0px; padding: 0px; transition: filter 0.5s ease-in-out; line-height: 25.6px; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji, Font Awesome 5 Free; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">의 rest-collection-view-odata.js 파일에서 가져올 수 있습니다.</span>
요청 파라미터가 준비되었으므로 이제 서버에 데이터와 집계가 포함된 그룹을 요청할 준비가 되었습니다. 이를 위해 `getItems` 메서드를 재정의하여 데이터 항목을 가져오고, `getGroupItems`메서드를 재정의하여 집계가 포함된 그룹을 가져오게 됩니다.

```javascript
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="box-sizing: border-box; margin: 0px; padding: 0px; transition: filter 0.5s ease-in-out; line-height: 25.6px; -webkit-font-smoothing: antialiased; 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; float: none; display: inline !important;">이제 JavaScript 파일에서 RESTCollectionView를 호출하여 FlexGrid 컨트롤의 데이터 소스로 사용할 수 있습니다.:</span>

```javascript
// 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=info
> 참고:
>
> * 이 기능은 가상화 기능과 함께 사용할 수 없습니다.
> * 페이지네이션은 지원되지 않습니다.
> * collapseGroupsToLevel() 메서드와 Ctrl+Click을 통한 일괄 확장은 의도적으로 제한됩니다.

<span style="box-sizing: content-box; margin: 0px; padding: 0px; transition: filter 0.5s ease-in-out; line-height: 25.6px; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji, Font Awesome 5 Free; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); float: none; display: inline !important;">서버 및 클라이언트 코드와 함께 샘플 구현을 확인하려면 </span>[Wijmo-Rest-CollectionView-Sample](https://github.com/wijmo/Wijmo-Rest-CollectionView-Sample)<span style="box-sizing: content-box; margin: 0px; padding: 0px; transition: filter 0.5s ease-in-out; line-height: 25.6px; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji, Font Awesome 5 Free; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; background-color: rgb(255, 255, 255); float: none; display: inline !important;">을 참고해주세요.</span>