Skip to content

Commit

Permalink
fix(odata): use contains with OData version 4 (#215)
Browse files Browse the repository at this point in the history
- defaults to OData version 2 (which uses substringof)
- ref Angular-Slickgrid issue [#263](ghiscoding/Angular-Slickgrid#263)
  • Loading branch information
ghiscoding committed Aug 10, 2019
1 parent d1949aa commit 2ff9a91
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 77 deletions.
3 changes: 3 additions & 0 deletions src/aurelia-slickgrid/models/odataOption.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export interface OdataOption extends BackendServiceOption {
/** Sorting string (or array of string) that must be a valid OData string */
orderBy?: string | string[];

/** OData (or any other) version number (the query string is different between versions) */
version?: number;

/** When accessed as an object */
[key: string]: any;
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ describe('GridOdataService', () => {
const mockColumn = { id: 'gender', field: 'gender' } as Column;
const mockColumnName = { id: 'firstName', field: 'firstName' } as Column;
const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'] } as ColumnFilter;
const mockColumnFilterName = { columnDef: mockColumnName, columnId: 'firstName', operator: 'startsWith', searchTerms: ['John'] } as ColumnFilter;
const mockColumnFilterName = { columnDef: mockColumnName, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'] } as ColumnFilter;
const mockFilterChangedArgs = {
columnDef: mockColumn,
columnId: 'gender',
Expand All @@ -272,7 +272,7 @@ describe('GridOdataService', () => {
expect(resetSpy).toHaveBeenCalled();
expect(currentFilters).toEqual([
{ columnId: 'gender', operator: 'EQ', searchTerms: ['female'] },
{ columnId: 'firstName', operator: 'startsWith', searchTerms: ['John'] }
{ columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'] }
]);
});
});
Expand Down Expand Up @@ -721,6 +721,29 @@ describe('GridOdataService', () => {
});
});

describe('updateFilters method with OData version 4', () => {
beforeEach(() => {
serviceOptions.version = 4;
serviceOptions.columnDefinitions = [{ id: 'company', field: 'company' }, { id: 'gender', field: 'gender' }, { id: 'name', field: 'name' }];
});

it('should return a query with a date showing as DateTime as per OData requirement', () => {
const expectation = `$top=10&$filter=(contains(Company, 'abc') and UpdatedDate eq DateTime'2001-02-28T00:00:00Z')`;
const mockColumnCompany = { id: 'company', field: 'company' } as Column;
const mockColumnUpdated = { id: 'updatedDate', field: 'updatedDate', type: FieldType.date } as Column;
const mockColumnFilters = {
company: { columnId: 'company', columnDef: mockColumnCompany, searchTerms: ['abc'], operator: 'Contains' },
updatedDate: { columnId: 'updatedDate', columnDef: mockColumnUpdated, searchTerms: ['2001-02-28'], operator: 'EQ' },
} as ColumnFilters;

service.init(serviceOptions, paginationOptions, gridStub);
service.updateFilters(mockColumnFilters, false);
const query = service.buildQuery();

expect(query).toBe(expectation);
});
});

describe('updateSorters method', () => {
beforeEach(() => {
serviceOptions.columnDefinitions = [{ id: 'company', field: 'company' }, { id: 'gender', field: 'gender' }, { id: 'name', field: 'name' }];
Expand Down
7 changes: 5 additions & 2 deletions src/aurelia-slickgrid/services/grid-odata.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,15 @@ export class GridOdataService implements BackendService {
} else if (fieldType === FieldType.string) {
// string field needs to be in single quotes
if (operator === '' || operator === OperatorType.contains || operator === OperatorType.notContains) {
searchBy = `substringof('${searchValue}', ${fieldName})`;
if (this._odataService.options.version >= 4) {
searchBy = `contains(${fieldName}, '${searchValue}')`;
} else {
searchBy = `substringof('${searchValue}', ${fieldName})`;
}
if (operator === OperatorType.notContains) {
searchBy = `not ${searchBy}`;
}
} else {
// searchBy = `substringof('${searchValue}', ${fieldNameCased}) ${this.mapOdataOperator(operator)} true`;
searchBy = `${fieldName} ${this.mapOdataOperator(operator)} '${searchValue}'`;
}
} else {
Expand Down
68 changes: 34 additions & 34 deletions src/examples/slickgrid/example3.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<h2>${title}</h2>
<div class="subtitle"
innerhtml.bind="subTitle"></div>
innerhtml.bind="subTitle"></div>

<div class="col-sm-6">
<label>autoEdit setting</label>
Expand All @@ -10,56 +10,56 @@ <h2>${title}</h2>
<div class="row">

<label class="radio-inline control-label"
for="radioTrue">
for="radioTrue">
<input type="radio"
name="inlineRadioOptions"
id="radioTrue"
checked
value.bind="isAutoEdit"
click.delegate="setAutoEdit(true)"> ON
name="inlineRadioOptions"
id="radioTrue"
checked
value.bind="isAutoEdit"
click.delegate="setAutoEdit(true)"> ON
(single-click)
</label>
<label class="radio-inline control-label"
for="radioFalse">
for="radioFalse">
<input type="radio"
name="inlineRadioOptions"
id="radioFalse"
value.bind="isAutoEdit"
click.delegate="setAutoEdit(false)"> OFF
name="inlineRadioOptions"
id="radioFalse"
value.bind="isAutoEdit"
click.delegate="setAutoEdit(false)"> OFF
(double-click)
</label>
</div>
<div class="row">
<button class="btn btn-default btn-sm"
click.delegate="undo()">
click.delegate="undo()">
<i class="fa fa-undo"></i>
Undo last edit(s)
</button>
<label class="checkbox-inline control-label"
for="autoCommitEdit">
for="autoCommitEdit">
<input type="checkbox"
id="autoCommitEdit"
value.bind="gridOptions.autoCommitEdit"
click.delegate="changeAutoCommit()">
id="autoCommitEdit"
value.bind="gridOptions.autoCommitEdit"
click.delegate="changeAutoCommit()">
Auto Commit Edit
</label>
</div>
</span>
<div class="row"
style="margin-top: 5px">
style="margin-top: 5px">
<button class="btn btn-default btn-sm"
click.delegate="aureliaGrid.filterService.clearFilters()">Clear Filters</button>
click.delegate="aureliaGrid.filterService.clearFilters()">Clear Filters</button>
<button class="btn btn-default btn-sm"
click.delegate="aureliaGrid.sortService.clearSorting()">Clear Sorting</button>
click.delegate="aureliaGrid.sortService.clearSorting()">Clear Sorting</button>
<button class="btn btn-default btn-sm btn-info"
click.delegate="addItem()"
title="Clear Filters &amp; Sorting to see it better">
click.delegate="addItem()"
title="Clear Filters &amp; Sorting to see it better">
Add item
</button>
<button class="btn btn-default btn-sm btn-danger"
click.delegate="deleteItem()">Delete item</button>
click.delegate="deleteItem()">Delete item</button>
<button class="btn btn-default btn-sm"
click.delegate="dynamicallyAddTitleHeader()">
click.delegate="dynamicallyAddTitleHeader()">
<i class="fa fa-plus"></i>
Dynamically Duplicate Title Column
</button>
Expand All @@ -68,25 +68,25 @@ <h2>${title}</h2>

<div class="col-sm-6">
<div class="alert alert-info"
show.bind="updatedObject">
show.bind="updatedObject">
<strong>Updated Item:</strong> ${updatedObject | stringify}
</div>
<div class="alert alert-warning"
show.bind="alertWarning">
show.bind="alertWarning">
${alertWarning}
</div>
</div>

<div id="grid-container"
class="col-sm-12">
class="col-sm-12">
<aurelia-slickgrid grid-id="grid1"
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset"
sg-on-cell-change.delegate="onCellChanged($event.detail.eventData, $event.detail.args)"
sg-on-click.delegate="onCellClicked($event.detail.eventData, $event.detail.args)"
sg-on-validation-error.delegate="onCellValidation($event.detail.eventData, $event.detail.args)"
asg-on-aurelia-grid-created.delegate="aureliaGridReady($event.detail)">
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset"
sg-on-cell-change.delegate="onCellChanged($event.detail.eventData, $event.detail.args)"
sg-on-click.delegate="onCellClicked($event.detail.eventData, $event.detail.args)"
sg-on-validation-error.delegate="onCellValidation($event.detail.eventData, $event.detail.args)"
asg-on-aurelia-grid-created.delegate="aureliaGridReady($event.detail)">
</aurelia-slickgrid>
</div>
</template>
40 changes: 31 additions & 9 deletions src/examples/slickgrid/example5.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<template>
<h2>${title}</h2>
<div class="subtitle"
innerhtml.bind="subTitle"></div>
innerhtml.bind="subTitle"></div>

<div class="row">
<div class="col-sm-4">
<div class.bind="status.class"
role="alert"
data-test="status">
role="alert"
data-test="status">
<strong>Status: </strong> ${status.text}
<span hidden.bind="!processing">
<i class="fa fa-refresh fa-spin fa-lg fa-fw"></i>
Expand All @@ -21,17 +21,39 @@ <h2>${title}</h2>
</div>
<div class="col-sm-8">
<div class="alert alert-info"
data-test="alert-odata-query">
data-test="alert-odata-query">
<strong>OData Query:</strong> <span data-test="odata-query-result">${odataQuery}</span>
</div>
<label>OData Version: </label>
<span data-test="radioVersion">
<label class="radio-inline control-label"
for="radio2">
<input type="radio"
name="inlineRadioOptions"
data-test="version2"
id="radio2"
checked
value.bind="2"
click.delegate="setOdataVersion(2)"> 2
</label>
<label class="radio-inline control-label"
for="radio4">
<input type="radio"
name="inlineRadioOptions"
data-test="version4"
id="radio4"
value.bind="4"
click.delegate="setOdataVersion(4)"> 4
</label>
</span>
</div>
</div>

<aurelia-slickgrid grid-id="grid5"
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset"
asg-on-aurelia-grid-created.delegate="aureliaGridReady($event.detail)"
asg-on-grid-state-changed.delegate="gridStateChanged($event)">
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset"
asg-on-aurelia-grid-created.delegate="aureliaGridReady($event.detail)"
asg-on-grid-state-changed.delegate="gridStateChanged($event)">
</aurelia-slickgrid>
</template>
62 changes: 34 additions & 28 deletions src/examples/slickgrid/example5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class Example5 {
dataset = [];
statistics: Statistic;

odataVersion = 2;
odataQuery = '';
processing = false;
status = { text: '', class: '' };
Expand Down Expand Up @@ -106,6 +107,7 @@ export class Example5 {
},
backendServiceApi: {
service: new GridOdataService(),
options: { version: this.odataVersion }, // defaults to 2, the query string is slightly different between OData 2 and 4
preProcess: () => this.displaySpinner(true),
process: (query) => this.getCustomerApiCall(query),
postProcess: (response) => {
Expand Down Expand Up @@ -159,55 +161,48 @@ export class Example5 {
const columnFilters = {};

for (const param of queryParams) {
if (param.indexOf('$top=') > -1) {
if (param.includes('$top=')) {
top = +(param.substring('$top='.length));
}
if (param.indexOf('$skip=') > -1) {
if (param.includes('$skip=')) {
skip = +(param.substring('$skip='.length));
}
if (param.indexOf('$orderby=') > -1) {
if (param.includes('$orderby=')) {
orderBy = param.substring('$orderby='.length);
}
if (param.indexOf('$filter=') > -1) {
if (param.includes('$filter=')) {
const filterBy = param.substring('$filter='.length).replace('%20', ' ');
if (filterBy.indexOf('substringof') > -1) {
if (filterBy.includes('contains')) {
const filterMatch = filterBy.match(/contains\(([a-zA-Z\/]+),\s?'(.*?)'/);
const fieldName = filterMatch[1].trim();
columnFilters[fieldName] = { type: 'substring', term: filterMatch[2].trim() };
}
if (filterBy.includes('substringof')) {
const filterMatch = filterBy.match(/substringof\('(.*?)',([a-zA-Z ]*)/);
const fieldName = filterMatch[2].trim();
columnFilters[fieldName] = {
type: 'substring',
term: filterMatch[1].trim()
};
columnFilters[fieldName] = { type: 'substring', term: filterMatch[1].trim() };
}
if (filterBy.indexOf('eq') > -1) {
if (filterBy.includes('eq')) {
const filterMatch = filterBy.match(/([a-zA-Z ]*) eq '(.*?)'/);
const fieldName = filterMatch[1].trim();
columnFilters[fieldName] = {
type: 'equal',
term: filterMatch[2].trim()
};
columnFilters[fieldName] = { type: 'equal', term: filterMatch[2].trim() };
}
if (filterBy.indexOf('startswith') > -1) {
if (filterBy.includes('startswith')) {
const filterMatch = filterBy.match(/startswith\(([a-zA-Z ]*),\s?'(.*?)'/);
const fieldName = filterMatch[1].trim();
columnFilters[fieldName] = {
type: 'starts',
term: filterMatch[2].trim()
};
columnFilters[fieldName] = { type: 'starts', term: filterMatch[2].trim() };
}
if (filterBy.indexOf('endswith') > -1) {
if (filterBy.includes('endswith')) {
const filterMatch = filterBy.match(/endswith\(([a-zA-Z ]*),\s?'(.*?)'/);
const fieldName = filterMatch[1].trim();
columnFilters[fieldName] = {
type: 'ends',
term: filterMatch[2].trim()
};
columnFilters[fieldName] = { type: 'ends', term: filterMatch[2].trim() };
}
}
}

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

Expand Down Expand Up @@ -250,7 +245,7 @@ export class Example5 {
case 'equal': return filterTerm.toLowerCase() === searchTerm;
case 'ends': return filterTerm.toLowerCase().endsWith(searchTerm);
case 'starts': return filterTerm.toLowerCase().startsWith(searchTerm);
case 'substring': return (filterTerm.toLowerCase().indexOf(searchTerm) > -1);
case 'substring': return filterTerm.toLowerCase().includes(searchTerm);
}
}
});
Expand All @@ -269,6 +264,17 @@ export class Example5 {

/** Dispatched event of a Grid State Changed event */
gridStateChanged(gridStateChanges: GridStateChange) {
console.log('OData sample, Grid State changed:: ', gridStateChanges);
console.log('Client sample, Grid State changed:: ', gridStateChanges);
}

// THIS IS ONLY FOR DEMO PURPOSES DO NOT USE THIS CODE
setOdataVersion(version: number) {
this.odataVersion = version;
const odataService = this.gridOptions.backendServiceApi.service;
// @ts-ignore
odataService.updateOptions({ version: this.odataVersion });
odataService.clearFilters();
this.aureliaGrid.filterService.clearFilters();
return true;
}
}
Loading

0 comments on commit 2ff9a91

Please sign in to comment.