Skip to content

Commit

Permalink
feat(OData): add $select and $expand query options
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Jan 5, 2022
1 parent 9e50445 commit b445a79
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 1,872 deletions.
59 changes: 39 additions & 20 deletions src/app/examples/grid-odata.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,25 @@ <h2>
</div>

<div class="row">
<div class="col-sm-4">
<div class="col-sm-2">
<div [class]="status.class" role="alert" data-test="status">
<strong>Status: </strong> {{status.text}}
<span [hidden]="!processing">
<i class="fa fa-refresh fa-spin fa-lg fa-fw"></i>
</span>
</div>
<button class="btn btn-outline-secondary btn-sm" data-test="set-dynamic-filter" (click)="setFiltersDynamically()">
</div>
<div class="col-sm-10">
<div class="alert alert-info" data-test="alert-odata-query">
<strong>OData Query:</strong> <span data-test="odata-query-result">{{odataQuery}}</span>
</div>
</div>
</div>

<div class="row">
<div class="col-sm-4">
<button class="btn btn-outline-secondary btn-sm" data-test="set-dynamic-filter"
(click)="setFiltersDynamically()">
Set Filters Dynamically
</button>
<button class="btn btn-outline-secondary btn-sm" data-test="set-dynamic-sorting"
Expand All @@ -41,11 +52,8 @@ <h2>
| {{metrics.totalItemCount}} items
</span>
</div>
<div class="col-sm-8">
<div class="alert alert-info" data-test="alert-odata-query">
<strong>OData Query:</strong> <span data-test="odata-query-result">{{odataQuery}}</span>
</div>

<div class="col-sm-8">
<label>OData Version: </label>
<span data-test="radioVersion">
<label class="radio-inline control-label" for="radio2">
Expand All @@ -62,24 +70,35 @@ <h2>
(click)="changeCountEnableFlag()">
<span style="font-weight: bold">Enable Count</span> (add to OData query)
</label>
<span class="float-end">
<button class="btn btn-outline-danger btn-sm " data-test="throw-page-error-btn"
(click)="throwPageChangeError()">
<span>Throw Error Going to Last Page... </span>
<i class="mdi mdi-page-last"></i>
</button>
</span>
<label class="checkbox-inline control-label" for="enableSelect" style="margin-left: 20px">
<input type="checkbox" id="enableSelect" data-test="enable-select" [checked]="isSelectEnabled"
(click)="changeEnableSelectFlag()">
<span style="font-weight: bold">Enable Select</span> (add to OData query)
</label>
<label class="checkbox-inline control-label" for="enableExpand" style="margin-left: 20px">
<input type="checkbox" id="enableExpand" data-test="enable-expand" [checked]="isExpandEnabled"
(click)="changeEnableExpandFlag()">
<span style="font-weight: bold">Enable Expand</span> (add to OData query)
</label>
</div>
</div>
<div class="row">
<div class="row mt-2 mb-1">
<div class="col-md-12">
<span>Programmatically go to first/last page:</span>
<button class="btn btn-outline-secondary btn-xs" data-test="goto-first-page" (click)="goToFirstPage()">
<i class="fa fa-caret-left fa-lg"></i>
</button>
<button class="btn btn-outline-secondary btn-xs" data-test="goto-last-page" (click)="goToLastPage()">
<i class="fa fa-caret-right fa-lg"></i>
<button class="btn btn-outline-danger btn-sm" data-test="throw-page-error-btn"
(click)="throwPageChangeError()">
<span>Throw Error Going to Last Page... </span>
<i class="mdi mdi-page-last"></i>
</button>

<span class="ms-2">
<label>Programmatically go to first/last page:</label>
<button class="btn btn-outline-secondary btn-xs" data-test="goto-first-page" (click)="goToFirstPage()">
<i class="fa fa-caret-left fa-lg"></i>
</button>
<button class="btn btn-outline-secondary btn-xs" data-test="goto-last-page" (click)="goToLastPage()">
<i class="fa fa-caret-right fa-lg"></i>
</button>
</span>
</div>
</div>

Expand Down
136 changes: 91 additions & 45 deletions src/app/examples/grid-odata.component.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { GridOdataService, OdataServiceApi, OdataOption } from '@slickgrid-universal/odata';
import { ChangeDetectorRef, Component, OnInit, } from '@angular/core';
import { HttpClient } from '@angular/common/http';
Expand Down Expand Up @@ -49,6 +50,8 @@ export class GridOdataComponent implements OnInit {
paginationOptions!: Pagination;

isCountEnabled = true;
isSelectEnabled = false;
isExpandEnabled = false;
odataVersion = 2;
odataQuery = '';
processing = true;
Expand All @@ -73,13 +76,14 @@ export class GridOdataComponent implements OnInit {
}
},
{
id: 'gender', name: 'Gender', field: 'gender', filterable: true,
id: 'gender', name: 'Gender', field: 'gender', filterable: true, sortable: true,
filter: {
model: Filters.singleSelect,
collection: [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }]
}
},
{ id: 'company', name: 'Company', field: 'company', filterable: true, sortable: true },
{ id: 'category_name', name: 'Category', field: 'category/name', filterable: true, sortable: true },
];

this.gridOptions = {
Expand Down Expand Up @@ -117,7 +121,9 @@ export class GridOdataComponent implements OnInit {
backendServiceApi: {
service: new GridOdataService(),
options: {
enableCount: this.isCountEnabled, // add the count in the OData query, which will return a property named "odata.count" (v2) or "@odata.count" (v4)
enableCount: this.isCountEnabled, // add the count in the OData query, which will return a property named "__count" (v2) or "@odata.count" (v4)
enableSelect: this.isSelectEnabled,
enableExpand: this.isExpandEnabled,
version: this.odataVersion // defaults to 2, the query string is slightly different between OData 2 and 4
},
onError: (error: Error) => {
Expand Down Expand Up @@ -145,26 +151,26 @@ export class GridOdataComponent implements OnInit {
this.status = { text: 'ERROR!!!', class: 'alert alert-danger' };
} else {
this.status = (isProcessing)
? { text: 'loading...', class: 'alert alert-warning' }
? { text: 'loading', class: 'alert alert-warning' }
: { text: 'finished', class: 'alert alert-success' };
}
}

getCustomerCallback(data: any) {
// totalItems property needs to be filled for pagination to work correctly
// however we need to force Angular to do a dirty check, doing a clone object will do just that
let countPropName = 'totalRecordCount'; // you can use "totalRecordCount" or any name or "odata.count" when "enableCount" is set
let totalItemCount: number = data['totalRecordCount']; // you can use "totalRecordCount" or any name or "odata.count" when "enableCount" is set
if (this.isCountEnabled) {
countPropName = (this.odataVersion === 4) ? '@odata.count' : 'odata.count';
totalItemCount = (this.odataVersion === 4) ? data['@odata.count'] : data['d']['__count'];
}
if (this.metrics) {
this.metrics.totalItemCount = data[countPropName];
this.metrics.totalItemCount = totalItemCount;
}

// once pagination totalItems is filled, we can update the dataset
this.dataset = data['items'];
this.paginationOptions = { ...this.gridOptions.pagination, totalItems: totalItemCount } as Pagination;
this.dataset = this.odataVersion === 4 ? data.value : data.d.results;
this.odataQuery = data['query'];
this.paginationOptions = { ...this.gridOptions.pagination as Pagination, totalItems: data[countPropName] as number };
}

getCustomerApiCall(query: string) {
Expand Down Expand Up @@ -234,7 +240,7 @@ export class GridOdataComponent implements OnInit {
(columnFilters as any)[fieldName] = { type: 'substring', term: filterMatch![2].trim() };
}
if (filterBy.includes('substringof')) {
const filterMatch = filterBy.match(/substringof\('(.*?)',([a-zA-Z ]*)/);
const filterMatch = filterBy.match(/substringof\('(.*?)',\s([a-zA-Z\/]+)/);
const fieldName = filterMatch![2].trim();
(columnFilters as any)[fieldName] = { type: 'substring', term: filterMatch![1].trim() };
}
Expand All @@ -256,43 +262,51 @@ export class GridOdataComponent implements OnInit {
(columnFilters as any)[fieldName] = { type: 'ends', term: filterMatch![2].trim() };
}

// simular a backend error when trying to sort on the "Company" field
// simulate a backend error when trying to sort on the "Company" field
if (filterBy.includes('company')) {
throw new Error('Server could not filter using the field "Company"');
}
}
}

// simular a backend error when trying to sort on the "Company" field
// simulate a backend error when trying to sort on the "Company" field
if (orderBy.includes('company')) {
throw new Error('Server could not sort using the field "Company"');
}

const sort = orderBy.includes('asc')
? 'ASC'
: orderBy.includes('desc')
? 'DESC'
: '';

let url;
switch (sort) {
case 'ASC':
url = `${sampleDataRoot}/customers_100_ASC.json`;
break;
case 'DESC':
url = `${sampleDataRoot}/customers_100_DESC.json`;
break;
default:
url = `${sampleDataRoot}/customers_100.json`;
break;
}
this.http.get(`${sampleDataRoot}/customers_100.json`).subscribe(response => {
let data = response as any[];

// Sort the data
if (orderBy?.length > 0) {
const orderByClauses = orderBy.split(',');
for (const orderByClause of orderByClauses) {
const orderByParts = orderByClause.split(' ');
const orderByField = orderByParts[0];

let selector = (obj: any): string => obj;
for (const orderByFieldPart of orderByField.split('/')) {
const prevSelector = selector;
selector = (obj: any) => {
return prevSelector(obj)[orderByFieldPart as any];
};
}

this.http.get(url).subscribe(data => {
const dataArray = <any[]>data;
const sort = orderByParts[1] ?? 'asc';
switch (sort.toLocaleLowerCase()) {
case 'asc':
data = data.sort((a, b) => selector(a).localeCompare(selector(b)));
break;
case 'desc':
data = data.sort((a, b) => selector(b).localeCompare(selector(a)));
break;
}
}
}

// Read the result field from the JSON response.
const firstRow = skip;
let filteredData = dataArray;
let filteredData = data;
if (columnFilters) {
for (const columnId in columnFilters) {
if (columnFilters.hasOwnProperty(columnId)) {
Expand All @@ -304,7 +318,14 @@ export class GridOdataComponent implements OnInit {
const splitIds = columnId.split(' ');
colId = splitIds[splitIds.length - 1];
}
const filterTerm = column[colId];

let filterTerm;
let col = column;
for (const part of colId.split('/')) {
filterTerm = (col as any)[part];
col = filterTerm;
}

if (filterTerm) {
switch (filterType) {
case 'equal': return filterTerm.toLowerCase() === searchTerm;
Expand All @@ -318,14 +339,26 @@ export class GridOdataComponent implements OnInit {
}
countTotalItems = filteredData.length;
}
const updatedData = filteredData.slice(firstRow, firstRow + top);
const updatedData = filteredData.slice(firstRow, firstRow + top!);

setTimeout(() => {
let countPropName = 'totalRecordCount';
if (this.isCountEnabled) {
countPropName = (this.odataVersion === 4) ? '@odata.count' : 'odata.count';
const backendResult: any = { query };
if (!this.isCountEnabled) {
backendResult['totalRecordCount'] = countTotalItems;
}

if (this.odataVersion === 4) {
backendResult['value'] = updatedData;
if (this.isCountEnabled) {
backendResult['@odata.count'] = countTotalItems;
}
} else {
backendResult['d'] = { results: updatedData };
if (this.isCountEnabled) {
backendResult['d']['__count'] = countTotalItems;
}
}
const backendResult = { items: updatedData, [countPropName]: countTotalItems, query };

// console.log('Backend Result', backendResult);
resolve(backendResult);
}, 100);
Expand Down Expand Up @@ -369,19 +402,32 @@ export class GridOdataComponent implements OnInit {

changeCountEnableFlag() {
this.isCountEnabled = !this.isCountEnabled;
const odataService = this.gridOptions.backendServiceApi!.service as GridOdataService;
odataService.updateOptions({ enableCount: this.isCountEnabled } as OdataOption);
odataService.clearFilters();
this.angularGrid.filterService.clearFilters();
this.resetOptions({ enableCount: this.isCountEnabled });
return true;
}

changeEnableSelectFlag() {
this.isSelectEnabled = !this.isSelectEnabled;
this.resetOptions({ enableSelect: this.isSelectEnabled });
return true;
}

changeEnableExpandFlag() {
this.isExpandEnabled = !this.isExpandEnabled;
this.resetOptions({ enableExpand: this.isExpandEnabled });
return true;
}

setOdataVersion(version: number) {
this.odataVersion = version;
this.resetOptions({ version: this.odataVersion });
return true;
}

private resetOptions(options: Partial<OdataOption>) {
const odataService = this.gridOptions.backendServiceApi!.service as GridOdataService;
odataService.updateOptions({ version: this.odataVersion } as OdataOption);
odataService.updateOptions(options);
odataService.clearFilters();
this.angularGrid.filterService.clearFilters();
return true;
this.angularGrid?.filterService.clearFilters();
}
}

0 comments on commit b445a79

Please sign in to comment.