@slickgrid-universal/odata 2.4.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/index.js +22 -22
- package/dist/commonjs/interfaces/index.js +19 -19
- package/dist/commonjs/interfaces/odataOption.interface.js +2 -2
- package/dist/commonjs/interfaces/odataServiceApi.interface.js +2 -2
- package/dist/commonjs/interfaces/odataSortingOption.interface.js +2 -2
- package/dist/commonjs/services/grid-odata.service.js +582 -582
- package/dist/commonjs/services/index.js +18 -18
- package/dist/commonjs/services/odataQueryBuilder.service.js +208 -208
- package/dist/esm/index.js +3 -3
- package/dist/esm/interfaces/index.js +3 -3
- package/dist/esm/interfaces/odataOption.interface.js +1 -1
- package/dist/esm/interfaces/odataServiceApi.interface.js +1 -1
- package/dist/esm/interfaces/odataSortingOption.interface.js +1 -1
- package/dist/esm/services/grid-odata.service.js +580 -580
- package/dist/esm/services/index.js +2 -2
- package/dist/esm/services/odataQueryBuilder.service.js +204 -204
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/{commonjs → types}/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/index.d.ts +4 -3
- package/dist/types/interfaces/index.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/odataOption.interface.d.ts +38 -37
- package/dist/types/interfaces/odataOption.interface.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/odataServiceApi.interface.d.ts +10 -9
- package/dist/types/interfaces/odataServiceApi.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/odataSortingOption.interface.d.ts +6 -5
- package/dist/types/interfaces/odataSortingOption.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/services/grid-odata.service.d.ts +75 -74
- package/dist/types/services/grid-odata.service.d.ts.map +1 -0
- package/dist/{esm → types}/services/index.d.ts +3 -2
- package/dist/types/services/index.d.ts.map +1 -0
- package/dist/{commonjs → types}/services/odataQueryBuilder.service.d.ts +29 -28
- package/dist/types/services/odataQueryBuilder.service.d.ts.map +1 -0
- package/package.json +9 -9
- package/dist/commonjs/interfaces/index.d.ts +0 -3
- package/dist/commonjs/interfaces/odataServiceApi.interface.d.ts +0 -9
- package/dist/commonjs/services/index.d.ts +0 -2
- package/dist/esm/index.d.ts +0 -3
- package/dist/esm/interfaces/odataOption.interface.d.ts +0 -37
- package/dist/esm/interfaces/odataSortingOption.interface.d.ts +0 -5
- package/dist/esm/services/grid-odata.service.d.ts +0 -74
- package/dist/esm/services/odataQueryBuilder.service.d.ts +0 -28
|
@@ -1,581 +1,581 @@
|
|
|
1
|
-
import {
|
|
2
|
-
// utilities
|
|
3
|
-
parseUtcDate, mapOperatorByFieldType, CaseType, FieldType, SortDirection, OperatorType, } from '@slickgrid-universal/common';
|
|
4
|
-
import { titleCase } from '@slickgrid-universal/utils';
|
|
5
|
-
import { OdataQueryBuilderService } from './odataQueryBuilder.service';
|
|
6
|
-
const DEFAULT_ITEMS_PER_PAGE = 25;
|
|
7
|
-
const DEFAULT_PAGE_SIZE = 20;
|
|
8
|
-
export class GridOdataService {
|
|
9
|
-
/** Getter for the Column Definitions */
|
|
10
|
-
get columnDefinitions() {
|
|
11
|
-
return this._columnDefinitions;
|
|
12
|
-
}
|
|
13
|
-
/** Getter for the Odata Service */
|
|
14
|
-
get odataService() {
|
|
15
|
-
return this._odataService;
|
|
16
|
-
}
|
|
17
|
-
/** Getter for the Grid Options pulled through the Grid Object */
|
|
18
|
-
get _gridOptions() {
|
|
19
|
-
var _a;
|
|
20
|
-
return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getOptions) ? this._grid.getOptions() : {};
|
|
21
|
-
}
|
|
22
|
-
constructor() {
|
|
23
|
-
this._currentFilters = [];
|
|
24
|
-
this._currentPagination = null;
|
|
25
|
-
this._currentSorters = [];
|
|
26
|
-
this._columnDefinitions = [];
|
|
27
|
-
this.defaultOptions = {
|
|
28
|
-
top: DEFAULT_ITEMS_PER_PAGE,
|
|
29
|
-
orderBy: '',
|
|
30
|
-
caseType: CaseType.pascalCase
|
|
31
|
-
};
|
|
32
|
-
this._odataService = new OdataQueryBuilderService();
|
|
33
|
-
}
|
|
34
|
-
init(serviceOptions, pagination, grid, sharedService) {
|
|
35
|
-
var _a, _b;
|
|
36
|
-
this._grid = grid;
|
|
37
|
-
const mergedOptions = { ...this.defaultOptions, ...serviceOptions };
|
|
38
|
-
// unless user specifically set "enablePagination" to False, we'll add "top" property for the pagination in every other cases
|
|
39
|
-
if (this._gridOptions && !this._gridOptions.enablePagination) {
|
|
40
|
-
// save current pagination as Page 1 and page size as "top"
|
|
41
|
-
this._odataService.options = { ...mergedOptions, top: undefined };
|
|
42
|
-
this._currentPagination = null;
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
const topOption = (pagination && pagination.pageSize) ? pagination.pageSize : this.defaultOptions.top;
|
|
46
|
-
this._odataService.options = { ...mergedOptions, top: topOption };
|
|
47
|
-
this._currentPagination = {
|
|
48
|
-
pageNumber: 1,
|
|
49
|
-
pageSize: this._odataService.options.top || this.defaultOptions.top || DEFAULT_PAGE_SIZE
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
this.options = this._odataService.options;
|
|
53
|
-
this.pagination = pagination;
|
|
54
|
-
if (grid === null || grid === void 0 ? void 0 : grid.getColumns) {
|
|
55
|
-
const tmpColumnDefinitions = (_b = (_a = sharedService === null || sharedService === void 0 ? void 0 : sharedService.allColumns) !== null && _a !== void 0 ? _a : grid.getColumns()) !== null && _b !== void 0 ? _b : [];
|
|
56
|
-
this._columnDefinitions = tmpColumnDefinitions.filter((column) => !column.excludeFromQuery);
|
|
57
|
-
}
|
|
58
|
-
this._odataService.columnDefinitions = this._columnDefinitions;
|
|
59
|
-
this._odataService.datasetIdPropName = this._gridOptions.datasetIdPropertyName || 'id';
|
|
60
|
-
}
|
|
61
|
-
buildQuery() {
|
|
62
|
-
return this._odataService.buildQuery();
|
|
63
|
-
}
|
|
64
|
-
postProcess(processResult) {
|
|
65
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
66
|
-
const odataVersion = (_c = (_b = (_a = this._odataService) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.version) !== null && _c !== void 0 ? _c : 2;
|
|
67
|
-
if (this.pagination && ((_e = (_d = this._odataService) === null || _d === void 0 ? void 0 : _d.options) === null || _e === void 0 ? void 0 : _e.enableCount)) {
|
|
68
|
-
const countExtractor = ((_h = (_g = (_f = this._odataService) === null || _f === void 0 ? void 0 : _f.options) === null || _g === void 0 ? void 0 : _g.countExtractor) !== null && _h !== void 0 ? _h : odataVersion >= 4) ? (r) => r === null || r === void 0 ? void 0 : r['@odata.count'] :
|
|
69
|
-
odataVersion === 3 ? (r) => r === null || r === void 0 ? void 0 : r['__count'] :
|
|
70
|
-
(r) => { var _a; return (_a = r === null || r === void 0 ? void 0 : r.d) === null || _a === void 0 ? void 0 : _a['__count']; };
|
|
71
|
-
const count = countExtractor(processResult);
|
|
72
|
-
if (typeof count === 'number') {
|
|
73
|
-
this.pagination.totalItems = count;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if ((_k = (_j = this._odataService) === null || _j === void 0 ? void 0 : _j.options) === null || _k === void 0 ? void 0 : _k.enableExpand) {
|
|
77
|
-
const datasetExtractor = ((_o = (_m = (_l = this._odataService) === null || _l === void 0 ? void 0 : _l.options) === null || _m === void 0 ? void 0 : _m.datasetExtractor) !== null && _o !== void 0 ? _o : odataVersion >= 4) ? (r) => r === null || r === void 0 ? void 0 : r.value :
|
|
78
|
-
odataVersion === 3 ? (r) => r === null || r === void 0 ? void 0 : r.results :
|
|
79
|
-
(r) => { var _a; return (_a = r === null || r === void 0 ? void 0 : r.d) === null || _a === void 0 ? void 0 : _a.results; };
|
|
80
|
-
const dataset = datasetExtractor(processResult);
|
|
81
|
-
if (Array.isArray(dataset)) {
|
|
82
|
-
// Flatten navigation fields (fields containing /) in the dataset (regardless of enableExpand).
|
|
83
|
-
// E.g. given columndefinition 'product/name' and dataset [{id: 1,product:{'name':'flowers'}}], then flattens to [{id:1,'product/name':'flowers'}]
|
|
84
|
-
const navigationFields = new Set(this._columnDefinitions.flatMap(x => { var _a; return (_a = x.fields) !== null && _a !== void 0 ? _a : [x.field]; }).filter(x => x.includes('/')));
|
|
85
|
-
if (navigationFields.size > 0) {
|
|
86
|
-
const navigations = new Set();
|
|
87
|
-
for (const item of dataset) {
|
|
88
|
-
for (const field of navigationFields) {
|
|
89
|
-
const names = field.split('/');
|
|
90
|
-
const navigation = names[0];
|
|
91
|
-
navigations.add(navigation);
|
|
92
|
-
let val = item[navigation];
|
|
93
|
-
for (let i = 1; i < names.length; i++) {
|
|
94
|
-
const mappedName = names[i];
|
|
95
|
-
if (val && typeof val === 'object' && mappedName in val) {
|
|
96
|
-
val = val[mappedName];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
item[field] = val;
|
|
100
|
-
}
|
|
101
|
-
// Remove navigation objects from the dataset to free memory and make sure we never work with them.
|
|
102
|
-
for (const navigation of navigations) {
|
|
103
|
-
if (typeof item[navigation] === 'object') {
|
|
104
|
-
delete item[navigation];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
clearFilters() {
|
|
113
|
-
this._currentFilters = [];
|
|
114
|
-
this.updateFilters([]);
|
|
115
|
-
}
|
|
116
|
-
clearSorters() {
|
|
117
|
-
this._currentSorters = [];
|
|
118
|
-
this.updateSorters([]);
|
|
119
|
-
}
|
|
120
|
-
updateOptions(serviceOptions) {
|
|
121
|
-
this.options = { ...this.options, ...serviceOptions };
|
|
122
|
-
this._odataService.options = this.options;
|
|
123
|
-
}
|
|
124
|
-
removeColumnFilter(fieldName) {
|
|
125
|
-
this._odataService.removeColumnFilter(fieldName);
|
|
126
|
-
}
|
|
127
|
-
/** Get the Filters that are currently used by the grid */
|
|
128
|
-
getCurrentFilters() {
|
|
129
|
-
return this._currentFilters;
|
|
130
|
-
}
|
|
131
|
-
/** Get the Pagination that is currently used by the grid */
|
|
132
|
-
getCurrentPagination() {
|
|
133
|
-
return this._currentPagination;
|
|
134
|
-
}
|
|
135
|
-
/** Get the Sorters that are currently used by the grid */
|
|
136
|
-
getCurrentSorters() {
|
|
137
|
-
return this._currentSorters;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Mapper for mathematical operators (ex.: <= is "le", > is "gt")
|
|
141
|
-
* @param string operator
|
|
142
|
-
* @returns string map
|
|
143
|
-
*/
|
|
144
|
-
mapOdataOperator(operator) {
|
|
145
|
-
let map = '';
|
|
146
|
-
switch (operator) {
|
|
147
|
-
case '<':
|
|
148
|
-
map = 'lt';
|
|
149
|
-
break;
|
|
150
|
-
case '<=':
|
|
151
|
-
map = 'le';
|
|
152
|
-
break;
|
|
153
|
-
case '>':
|
|
154
|
-
map = 'gt';
|
|
155
|
-
break;
|
|
156
|
-
case '>=':
|
|
157
|
-
map = 'ge';
|
|
158
|
-
break;
|
|
159
|
-
case '<>':
|
|
160
|
-
case '!=':
|
|
161
|
-
map = 'ne';
|
|
162
|
-
break;
|
|
163
|
-
case '=':
|
|
164
|
-
case '==':
|
|
165
|
-
default:
|
|
166
|
-
map = 'eq';
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
return map;
|
|
170
|
-
}
|
|
171
|
-
/*
|
|
172
|
-
* Reset the pagination options
|
|
173
|
-
*/
|
|
174
|
-
resetPaginationOptions() {
|
|
175
|
-
this._odataService.updateOptions({
|
|
176
|
-
skip: 0
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
saveColumnFilter(fieldName, value, terms) {
|
|
180
|
-
this._odataService.saveColumnFilter(fieldName, value, terms);
|
|
181
|
-
}
|
|
182
|
-
/*
|
|
183
|
-
* FILTERING
|
|
184
|
-
*/
|
|
185
|
-
processOnFilterChanged(_event, args) {
|
|
186
|
-
const gridOptions = this._gridOptions;
|
|
187
|
-
const backendApi = gridOptions.backendServiceApi;
|
|
188
|
-
if (backendApi === undefined) {
|
|
189
|
-
throw new Error('Something went wrong in the GridOdataService, "backendServiceApi" is not initialized');
|
|
190
|
-
}
|
|
191
|
-
// keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
|
|
192
|
-
this._currentFilters = this.castFilterToColumnFilters(args.columnFilters);
|
|
193
|
-
if (!args || !args.grid) {
|
|
194
|
-
throw new Error('Something went wrong when trying create the GridOdataService, it seems that "args" is not populated correctly');
|
|
195
|
-
}
|
|
196
|
-
// loop through all columns to inspect filters & set the query
|
|
197
|
-
this.updateFilters(args.columnFilters);
|
|
198
|
-
this.resetPaginationOptions();
|
|
199
|
-
return this._odataService.buildQuery();
|
|
200
|
-
}
|
|
201
|
-
/*
|
|
202
|
-
* PAGINATION
|
|
203
|
-
*/
|
|
204
|
-
processOnPaginationChanged(_event, args) {
|
|
205
|
-
const pageSize = +(args.pageSize || ((this.pagination) ? this.pagination.pageSize : DEFAULT_PAGE_SIZE));
|
|
206
|
-
this.updatePagination(args.newPage, pageSize);
|
|
207
|
-
// build the OData query which we will use in the WebAPI callback
|
|
208
|
-
return this._odataService.buildQuery();
|
|
209
|
-
}
|
|
210
|
-
/*
|
|
211
|
-
* SORTING
|
|
212
|
-
*/
|
|
213
|
-
processOnSortChanged(_event, args) {
|
|
214
|
-
const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ columnId: args.sortCol.id, sortCol: args.sortCol, sortAsc: args.sortAsc });
|
|
215
|
-
// loop through all columns to inspect sorters & set the query
|
|
216
|
-
this.updateSorters(sortColumns);
|
|
217
|
-
// build the OData query which we will use in the WebAPI callback
|
|
218
|
-
return this._odataService.buildQuery();
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* loop through all columns to inspect filters & update backend service filters
|
|
222
|
-
* @param columnFilters
|
|
223
|
-
*/
|
|
224
|
-
updateFilters(columnFilters, isUpdatedByPresetOrDynamically) {
|
|
225
|
-
var _a, _b, _c, _d, _e, _f;
|
|
226
|
-
let searchBy = '';
|
|
227
|
-
const searchByArray = [];
|
|
228
|
-
const odataVersion = (_c = (_b = (_a = this._odataService) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.version) !== null && _c !== void 0 ? _c : 2;
|
|
229
|
-
// on filter preset load, we need to keep current filters
|
|
230
|
-
if (isUpdatedByPresetOrDynamically) {
|
|
231
|
-
this._currentFilters = this.castFilterToColumnFilters(columnFilters);
|
|
232
|
-
}
|
|
233
|
-
// loop through all columns to inspect filters
|
|
234
|
-
for (const columnId in columnFilters) {
|
|
235
|
-
if (columnFilters.hasOwnProperty(columnId)) {
|
|
236
|
-
const columnFilter = columnFilters[columnId];
|
|
237
|
-
// if user defined some "presets", then we need to find the filters from the column definitions instead
|
|
238
|
-
let columnDef;
|
|
239
|
-
if (isUpdatedByPresetOrDynamically && Array.isArray(this._columnDefinitions)) {
|
|
240
|
-
columnDef = this._columnDefinitions.find((column) => column.id === columnFilter.columnId);
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
columnDef = columnFilter.columnDef;
|
|
244
|
-
}
|
|
245
|
-
if (!columnDef) {
|
|
246
|
-
throw new Error('[GridOData Service]: Something went wrong in trying to get the column definition of the specified filter (or preset filters). Did you make a typo on the filter columnId?');
|
|
247
|
-
}
|
|
248
|
-
let fieldName = ((_d = columnDef.filter) === null || _d === void 0 ? void 0 : _d.queryField) || columnDef.queryFieldFilter || columnDef.queryField || columnDef.field || columnDef.name || '';
|
|
249
|
-
const fieldType = columnDef.type || FieldType.string;
|
|
250
|
-
let searchTerms = (columnFilter && columnFilter.searchTerms ? [...columnFilter.searchTerms] : null) || [];
|
|
251
|
-
let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : '';
|
|
252
|
-
if (typeof fieldSearchValue === 'undefined') {
|
|
253
|
-
fieldSearchValue = '';
|
|
254
|
-
}
|
|
255
|
-
if (!fieldName) {
|
|
256
|
-
throw new Error(`GridOData filter could not find the field name to query the search, your column definition must include a valid "field" or "name" (optionally you can also use the "queryfield").`);
|
|
257
|
-
}
|
|
258
|
-
fieldSearchValue = (fieldSearchValue === undefined || fieldSearchValue === null) ? '' : `${fieldSearchValue}`; // make sure it's a string
|
|
259
|
-
// run regex to find possible filter operators unless the user disabled the feature
|
|
260
|
-
const autoParseInputFilterOperator = (_e = columnDef.autoParseInputFilterOperator) !== null && _e !== void 0 ? _e : this._gridOptions.autoParseInputFilterOperator;
|
|
261
|
-
const matches = autoParseInputFilterOperator !== false
|
|
262
|
-
? fieldSearchValue.match(/^([<>!=\*]{0,2})(.*[^<>!=\*])([\*]?)$/) // group 1: Operator, 2: searchValue, 3: last char is '*' (meaning starts with, ex.: abc*)
|
|
263
|
-
: [fieldSearchValue, '', fieldSearchValue, '']; // when parsing is disabled, we'll only keep the search value in the index 2 to make it easy for code reuse
|
|
264
|
-
let operator = columnFilter.operator || (matches === null || matches === void 0 ? void 0 : matches[1]);
|
|
265
|
-
let searchValue = (matches === null || matches === void 0 ? void 0 : matches[2]) || '';
|
|
266
|
-
const lastValueChar = (matches === null || matches === void 0 ? void 0 : matches[3]) || (operator === '*z' || operator === OperatorType.endsWith) ? '*' : '';
|
|
267
|
-
const bypassOdataQuery = columnFilter.bypassBackendQuery || false;
|
|
268
|
-
// no need to query if search value is empty
|
|
269
|
-
if (fieldName && searchValue === '' && searchTerms.length <= 1) {
|
|
270
|
-
this.removeColumnFilter(fieldName);
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
if (Array.isArray(searchTerms) && searchTerms.length === 1 && typeof searchTerms[0] === 'string' && searchTerms[0].indexOf('..') >= 0) {
|
|
274
|
-
if (operator !== OperatorType.rangeInclusive && operator !== OperatorType.rangeExclusive) {
|
|
275
|
-
operator = (_f = this._gridOptions.defaultFilterRangeOperator) !== null && _f !== void 0 ? _f : OperatorType.rangeInclusive;
|
|
276
|
-
}
|
|
277
|
-
searchTerms = searchTerms[0].split('..', 2);
|
|
278
|
-
if (searchTerms[0] === '') {
|
|
279
|
-
operator = operator === OperatorType.rangeInclusive ? '<=' : operator === OperatorType.rangeExclusive ? '<' : operator;
|
|
280
|
-
searchTerms = searchTerms.slice(1);
|
|
281
|
-
searchValue = searchTerms[0];
|
|
282
|
-
}
|
|
283
|
-
else if (searchTerms[1] === '') {
|
|
284
|
-
operator = operator === OperatorType.rangeInclusive ? '>=' : operator === OperatorType.rangeExclusive ? '>' : operator;
|
|
285
|
-
searchTerms = searchTerms.slice(0, 1);
|
|
286
|
-
searchValue = searchTerms[0];
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
// if we didn't find an Operator but we have a Column Operator inside the Filter (DOM Element), we should use its default Operator
|
|
290
|
-
// multipleSelect is "IN", while singleSelect is "EQ", else don't map any operator
|
|
291
|
-
if (!operator && columnDef.filter) {
|
|
292
|
-
operator = columnDef.filter.operator;
|
|
293
|
-
}
|
|
294
|
-
// No operator and 2 search terms should lead to default range operator.
|
|
295
|
-
if (!operator && Array.isArray(searchTerms) && searchTerms.length === 2 && searchTerms[0] && searchTerms[1]) {
|
|
296
|
-
operator = this._gridOptions.defaultFilterRangeOperator;
|
|
297
|
-
}
|
|
298
|
-
// Range with 1 searchterm should lead to equals for a date field.
|
|
299
|
-
if ((operator === OperatorType.rangeInclusive || OperatorType.rangeExclusive) && Array.isArray(searchTerms) && searchTerms.length === 1 && fieldType === FieldType.date) {
|
|
300
|
-
operator = OperatorType.equal;
|
|
301
|
-
}
|
|
302
|
-
// if we still don't have an operator find the proper Operator to use by it's field type
|
|
303
|
-
if (!operator) {
|
|
304
|
-
operator = mapOperatorByFieldType(fieldType);
|
|
305
|
-
}
|
|
306
|
-
// extra query arguments
|
|
307
|
-
if (bypassOdataQuery) {
|
|
308
|
-
// push to our temp array and also trim white spaces
|
|
309
|
-
if (fieldName) {
|
|
310
|
-
this.saveColumnFilter(fieldName, fieldSearchValue, searchTerms);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
// Normalize all search values
|
|
315
|
-
searchValue = this.normalizeSearchValue(fieldType, searchValue, odataVersion);
|
|
316
|
-
if (Array.isArray(searchTerms)) {
|
|
317
|
-
searchTerms.forEach((_part, index) => {
|
|
318
|
-
searchTerms[index] = this.normalizeSearchValue(fieldType, searchTerms[index], odataVersion);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
searchBy = '';
|
|
322
|
-
// titleCase the fieldName so that it matches the WebApi names
|
|
323
|
-
if (this._odataService.options.caseType === CaseType.pascalCase) {
|
|
324
|
-
fieldName = titleCase(fieldName || '');
|
|
325
|
-
}
|
|
326
|
-
if (searchTerms && searchTerms.length > 1 && (operator === 'IN' || operator === 'NIN' || operator === 'NOTIN' || operator === 'NOT IN' || operator === 'NOT_IN')) {
|
|
327
|
-
// when having more than 1 search term (then check if we have a "IN" or "NOT IN" filter search)
|
|
328
|
-
const tmpSearchTerms = [];
|
|
329
|
-
if (operator === 'IN') {
|
|
330
|
-
// example:: (Stage eq "Expired" or Stage eq "Renewal")
|
|
331
|
-
for (let j = 0, lnj = searchTerms.length; j < lnj; j++) {
|
|
332
|
-
tmpSearchTerms.push(`${fieldName} eq ${searchTerms[j]}`);
|
|
333
|
-
}
|
|
334
|
-
searchBy = tmpSearchTerms.join(' or ');
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
// example:: (Stage ne "Expired" and Stage ne "Renewal")
|
|
338
|
-
for (let k = 0, lnk = searchTerms.length; k < lnk; k++) {
|
|
339
|
-
tmpSearchTerms.push(`${fieldName} ne ${searchTerms[k]}`);
|
|
340
|
-
}
|
|
341
|
-
searchBy = tmpSearchTerms.join(' and ');
|
|
342
|
-
}
|
|
343
|
-
if (!(typeof searchBy === 'string' && searchBy[0] === '(' && searchBy.slice(-1) === ')')) {
|
|
344
|
-
searchBy = `(${searchBy})`;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
else if (operator === '*' || operator === 'a*' || operator === '*z' || lastValueChar === '*' || operator === OperatorType.startsWith || operator === OperatorType.endsWith) {
|
|
348
|
-
// first/last character is a '*' will be a startsWith or endsWith
|
|
349
|
-
searchBy = (operator === '*' || operator === '*z' || operator === OperatorType.endsWith) ? `endswith(${fieldName}, ${searchValue})` : `startswith(${fieldName}, ${searchValue})`;
|
|
350
|
-
}
|
|
351
|
-
else if (operator === OperatorType.rangeExclusive || operator === OperatorType.rangeInclusive) {
|
|
352
|
-
// example:: (Name >= 'Bob' and Name <= 'Jane')
|
|
353
|
-
searchBy = this.filterBySearchTermRange(fieldName, operator, searchTerms);
|
|
354
|
-
}
|
|
355
|
-
else if ((operator === '' || operator === OperatorType.contains || operator === OperatorType.notContains) &&
|
|
356
|
-
(fieldType === FieldType.string || fieldType === FieldType.text || fieldType === FieldType.readonly)) {
|
|
357
|
-
searchBy = odataVersion >= 4 ? `contains(${fieldName}, ${searchValue})` : `substringof(${searchValue}, ${fieldName})`;
|
|
358
|
-
if (operator === OperatorType.notContains) {
|
|
359
|
-
searchBy = `not ${searchBy}`;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
// any other field type (or undefined type)
|
|
364
|
-
searchBy = `${fieldName} ${this.mapOdataOperator(operator)} ${searchValue}`;
|
|
365
|
-
}
|
|
366
|
-
// push to our temp array and also trim white spaces
|
|
367
|
-
if (searchBy !== '') {
|
|
368
|
-
searchByArray.push(searchBy.trim());
|
|
369
|
-
this.saveColumnFilter(fieldName || '', fieldSearchValue, searchValue);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// update the service options with filters for the buildQuery() to work later
|
|
375
|
-
this._odataService.updateOptions({
|
|
376
|
-
filter: (searchByArray.length > 0) ? searchByArray.join(' and ') : '',
|
|
377
|
-
skip: undefined
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Update the pagination component with it's new page number and size
|
|
382
|
-
* @param newPage
|
|
383
|
-
* @param pageSize
|
|
384
|
-
*/
|
|
385
|
-
updatePagination(newPage, pageSize) {
|
|
386
|
-
this._currentPagination = {
|
|
387
|
-
pageNumber: newPage,
|
|
388
|
-
pageSize,
|
|
389
|
-
};
|
|
390
|
-
// unless user specifically set "enablePagination" to False, we'll update pagination options in every other cases
|
|
391
|
-
if (this._gridOptions && (this._gridOptions.enablePagination || !this._gridOptions.hasOwnProperty('enablePagination'))) {
|
|
392
|
-
this._odataService.updateOptions({
|
|
393
|
-
top: pageSize,
|
|
394
|
-
skip: (newPage - 1) * pageSize
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* loop through all columns to inspect sorters & update backend service orderBy
|
|
400
|
-
* @param columnFilters
|
|
401
|
-
*/
|
|
402
|
-
updateSorters(sortColumns, presetSorters) {
|
|
403
|
-
let currentSorters = [];
|
|
404
|
-
const odataSorters = [];
|
|
405
|
-
if (!sortColumns && presetSorters) {
|
|
406
|
-
// make the presets the current sorters, also make sure that all direction are in lowercase for OData
|
|
407
|
-
currentSorters = presetSorters;
|
|
408
|
-
currentSorters.forEach((sorter) => sorter.direction = sorter.direction.toLowerCase());
|
|
409
|
-
// display the correct sorting icons on the UI, for that it requires (columnId, sortAsc) properties
|
|
410
|
-
const tmpSorterArray = currentSorters.map((sorter) => {
|
|
411
|
-
const columnDef = this._columnDefinitions.find((column) => column.id === sorter.columnId);
|
|
412
|
-
odataSorters.push({
|
|
413
|
-
field: columnDef ? ((columnDef.queryFieldSorter || columnDef.queryField || columnDef.field) + '') : (sorter.columnId + ''),
|
|
414
|
-
direction: sorter.direction
|
|
415
|
-
});
|
|
416
|
-
// return only the column(s) found in the Column Definitions ELSE null
|
|
417
|
-
if (columnDef) {
|
|
418
|
-
return {
|
|
419
|
-
columnId: sorter.columnId,
|
|
420
|
-
sortAsc: sorter.direction.toUpperCase() === SortDirection.ASC
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
return null;
|
|
424
|
-
});
|
|
425
|
-
// set the sort icons, but also make sure to filter out null values (that happens when columnDef is not found)
|
|
426
|
-
if (Array.isArray(tmpSorterArray) && this._grid) {
|
|
427
|
-
this._grid.setSortColumns(tmpSorterArray);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
else if (sortColumns && !presetSorters) {
|
|
431
|
-
// build the SortBy string, it could be multisort, example: customerNo asc, purchaserName desc
|
|
432
|
-
if ((sortColumns === null || sortColumns === void 0 ? void 0 : sortColumns.length) === 0) {
|
|
433
|
-
// TODO fix this line
|
|
434
|
-
// currentSorters = new Array(this.defaultOptions.orderBy); // when empty, use the default sort
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
if (sortColumns) {
|
|
438
|
-
for (const columnDef of sortColumns) {
|
|
439
|
-
if (columnDef.sortCol) {
|
|
440
|
-
let fieldName = (columnDef.sortCol.queryFieldSorter || columnDef.sortCol.queryField || columnDef.sortCol.field) + '';
|
|
441
|
-
let columnFieldName = (columnDef.sortCol.field || columnDef.sortCol.id) + '';
|
|
442
|
-
let queryField = (columnDef.sortCol.queryFieldSorter || columnDef.sortCol.queryField || columnDef.sortCol.field || '') + '';
|
|
443
|
-
if (this._odataService.options.caseType === CaseType.pascalCase) {
|
|
444
|
-
fieldName = titleCase(fieldName);
|
|
445
|
-
columnFieldName = titleCase(columnFieldName);
|
|
446
|
-
queryField = titleCase(queryField);
|
|
447
|
-
}
|
|
448
|
-
if (columnFieldName !== '') {
|
|
449
|
-
currentSorters.push({
|
|
450
|
-
columnId: columnFieldName,
|
|
451
|
-
direction: columnDef.sortAsc ? 'asc' : 'desc'
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
if (queryField !== '') {
|
|
455
|
-
odataSorters.push({
|
|
456
|
-
field: queryField,
|
|
457
|
-
direction: columnDef.sortAsc ? SortDirection.ASC : SortDirection.DESC
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
// transform the sortby array into a CSV string for OData
|
|
466
|
-
currentSorters = currentSorters || [];
|
|
467
|
-
const csvString = odataSorters.map((sorter) => {
|
|
468
|
-
let str = '';
|
|
469
|
-
if (sorter && sorter.field) {
|
|
470
|
-
const sortField = (this._odataService.options.caseType === CaseType.pascalCase) ? titleCase(sorter.field) : sorter.field;
|
|
471
|
-
str = `${sortField} ${sorter && sorter.direction && sorter.direction.toLowerCase() || ''}`;
|
|
472
|
-
}
|
|
473
|
-
return str;
|
|
474
|
-
}).join(',');
|
|
475
|
-
this._odataService.updateOptions({
|
|
476
|
-
orderBy: csvString
|
|
477
|
-
});
|
|
478
|
-
// keep current Sorters and update the service options with the new sorting
|
|
479
|
-
this._currentSorters = currentSorters;
|
|
480
|
-
// build the OData query which we will use in the WebAPI callback
|
|
481
|
-
return this._odataService.buildQuery();
|
|
482
|
-
}
|
|
483
|
-
//
|
|
484
|
-
// protected functions
|
|
485
|
-
// -------------------
|
|
486
|
-
/**
|
|
487
|
-
* Cast provided filters (could be in multiple format) into an array of ColumnFilter
|
|
488
|
-
* @param columnFilters
|
|
489
|
-
*/
|
|
490
|
-
castFilterToColumnFilters(columnFilters) {
|
|
491
|
-
// keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
|
|
492
|
-
const filtersArray = (typeof columnFilters === 'object') ? Object.keys(columnFilters).map(key => columnFilters[key]) : columnFilters;
|
|
493
|
-
if (!Array.isArray(filtersArray)) {
|
|
494
|
-
return [];
|
|
495
|
-
}
|
|
496
|
-
return filtersArray.map((filter) => {
|
|
497
|
-
const tmpFilter = { columnId: filter.columnId || '' };
|
|
498
|
-
if (filter.operator) {
|
|
499
|
-
tmpFilter.operator = filter.operator;
|
|
500
|
-
}
|
|
501
|
-
if (filter.targetSelector) {
|
|
502
|
-
tmpFilter.targetSelector = filter.targetSelector;
|
|
503
|
-
}
|
|
504
|
-
if (Array.isArray(filter.searchTerms)) {
|
|
505
|
-
tmpFilter.searchTerms = filter.searchTerms;
|
|
506
|
-
}
|
|
507
|
-
return tmpFilter;
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Filter by a range of searchTerms (2 searchTerms OR 1 string separated by 2 dots "value1..value2")
|
|
512
|
-
*/
|
|
513
|
-
filterBySearchTermRange(fieldName, operator, searchTerms) {
|
|
514
|
-
let query = '';
|
|
515
|
-
if (Array.isArray(searchTerms) && searchTerms.length === 2) {
|
|
516
|
-
if (operator === OperatorType.rangeInclusive) {
|
|
517
|
-
// example:: (Duration >= 5 and Duration <= 10)
|
|
518
|
-
query = `(${fieldName} ge ${searchTerms[0]}`;
|
|
519
|
-
if (searchTerms[1] !== '') {
|
|
520
|
-
query += ` and ${fieldName} le ${searchTerms[1]}`;
|
|
521
|
-
}
|
|
522
|
-
query += ')';
|
|
523
|
-
}
|
|
524
|
-
else if (operator === OperatorType.rangeExclusive) {
|
|
525
|
-
// example:: (Duration > 5 and Duration < 10)
|
|
526
|
-
query = `(${fieldName} gt ${searchTerms[0]}`;
|
|
527
|
-
if (searchTerms[1] !== '') {
|
|
528
|
-
query += ` and ${fieldName} lt ${searchTerms[1]}`;
|
|
529
|
-
}
|
|
530
|
-
query += ')';
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return query;
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Normalizes the search value according to field type and oData version.
|
|
537
|
-
*/
|
|
538
|
-
normalizeSearchValue(fieldType, searchValue, version) {
|
|
539
|
-
switch (fieldType) {
|
|
540
|
-
case FieldType.date:
|
|
541
|
-
searchValue = parseUtcDate(searchValue, true);
|
|
542
|
-
searchValue = version >= 4 ? searchValue : `DateTime'${searchValue}'`;
|
|
543
|
-
break;
|
|
544
|
-
case FieldType.string:
|
|
545
|
-
case FieldType.text:
|
|
546
|
-
case FieldType.readonly:
|
|
547
|
-
if (typeof searchValue === 'string') {
|
|
548
|
-
// escape single quotes by doubling them
|
|
549
|
-
searchValue = searchValue.replace(/'/g, `''`);
|
|
550
|
-
// encode URI of the final search value
|
|
551
|
-
searchValue = encodeURIComponent(searchValue);
|
|
552
|
-
// strings need to be quoted.
|
|
553
|
-
searchValue = `'${searchValue}'`;
|
|
554
|
-
}
|
|
555
|
-
break;
|
|
556
|
-
case FieldType.integer:
|
|
557
|
-
case FieldType.number:
|
|
558
|
-
case FieldType.float:
|
|
559
|
-
if (typeof searchValue === 'string') {
|
|
560
|
-
// Parse a valid decimal from the string.
|
|
561
|
-
// Replace double dots with single dots
|
|
562
|
-
searchValue = searchValue.replace(/\.\./g, '.');
|
|
563
|
-
// Remove a trailing dot
|
|
564
|
-
searchValue = searchValue.replace(/\.+$/g, '');
|
|
565
|
-
// Prefix a leading dot with 0
|
|
566
|
-
searchValue = searchValue.replace(/^\.+/g, '0.');
|
|
567
|
-
// Prefix leading dash dot with -0.
|
|
568
|
-
searchValue = searchValue.replace(/^\-+\.+/g, '-0.');
|
|
569
|
-
// Remove any non valid decimal characters from the search string
|
|
570
|
-
searchValue = searchValue.replace(/(?!^\-)[^\d\.]/g, '');
|
|
571
|
-
// if nothing left, search for 0
|
|
572
|
-
if (searchValue === '' || searchValue === '-') {
|
|
573
|
-
searchValue = '0';
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
break;
|
|
577
|
-
}
|
|
578
|
-
return searchValue;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
// utilities
|
|
3
|
+
parseUtcDate, mapOperatorByFieldType, CaseType, FieldType, SortDirection, OperatorType, } from '@slickgrid-universal/common';
|
|
4
|
+
import { titleCase } from '@slickgrid-universal/utils';
|
|
5
|
+
import { OdataQueryBuilderService } from './odataQueryBuilder.service';
|
|
6
|
+
const DEFAULT_ITEMS_PER_PAGE = 25;
|
|
7
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
8
|
+
export class GridOdataService {
|
|
9
|
+
/** Getter for the Column Definitions */
|
|
10
|
+
get columnDefinitions() {
|
|
11
|
+
return this._columnDefinitions;
|
|
12
|
+
}
|
|
13
|
+
/** Getter for the Odata Service */
|
|
14
|
+
get odataService() {
|
|
15
|
+
return this._odataService;
|
|
16
|
+
}
|
|
17
|
+
/** Getter for the Grid Options pulled through the Grid Object */
|
|
18
|
+
get _gridOptions() {
|
|
19
|
+
var _a;
|
|
20
|
+
return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getOptions) ? this._grid.getOptions() : {};
|
|
21
|
+
}
|
|
22
|
+
constructor() {
|
|
23
|
+
this._currentFilters = [];
|
|
24
|
+
this._currentPagination = null;
|
|
25
|
+
this._currentSorters = [];
|
|
26
|
+
this._columnDefinitions = [];
|
|
27
|
+
this.defaultOptions = {
|
|
28
|
+
top: DEFAULT_ITEMS_PER_PAGE,
|
|
29
|
+
orderBy: '',
|
|
30
|
+
caseType: CaseType.pascalCase
|
|
31
|
+
};
|
|
32
|
+
this._odataService = new OdataQueryBuilderService();
|
|
33
|
+
}
|
|
34
|
+
init(serviceOptions, pagination, grid, sharedService) {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
this._grid = grid;
|
|
37
|
+
const mergedOptions = { ...this.defaultOptions, ...serviceOptions };
|
|
38
|
+
// unless user specifically set "enablePagination" to False, we'll add "top" property for the pagination in every other cases
|
|
39
|
+
if (this._gridOptions && !this._gridOptions.enablePagination) {
|
|
40
|
+
// save current pagination as Page 1 and page size as "top"
|
|
41
|
+
this._odataService.options = { ...mergedOptions, top: undefined };
|
|
42
|
+
this._currentPagination = null;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const topOption = (pagination && pagination.pageSize) ? pagination.pageSize : this.defaultOptions.top;
|
|
46
|
+
this._odataService.options = { ...mergedOptions, top: topOption };
|
|
47
|
+
this._currentPagination = {
|
|
48
|
+
pageNumber: 1,
|
|
49
|
+
pageSize: this._odataService.options.top || this.defaultOptions.top || DEFAULT_PAGE_SIZE
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
this.options = this._odataService.options;
|
|
53
|
+
this.pagination = pagination;
|
|
54
|
+
if (grid === null || grid === void 0 ? void 0 : grid.getColumns) {
|
|
55
|
+
const tmpColumnDefinitions = (_b = (_a = sharedService === null || sharedService === void 0 ? void 0 : sharedService.allColumns) !== null && _a !== void 0 ? _a : grid.getColumns()) !== null && _b !== void 0 ? _b : [];
|
|
56
|
+
this._columnDefinitions = tmpColumnDefinitions.filter((column) => !column.excludeFromQuery);
|
|
57
|
+
}
|
|
58
|
+
this._odataService.columnDefinitions = this._columnDefinitions;
|
|
59
|
+
this._odataService.datasetIdPropName = this._gridOptions.datasetIdPropertyName || 'id';
|
|
60
|
+
}
|
|
61
|
+
buildQuery() {
|
|
62
|
+
return this._odataService.buildQuery();
|
|
63
|
+
}
|
|
64
|
+
postProcess(processResult) {
|
|
65
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
66
|
+
const odataVersion = (_c = (_b = (_a = this._odataService) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.version) !== null && _c !== void 0 ? _c : 2;
|
|
67
|
+
if (this.pagination && ((_e = (_d = this._odataService) === null || _d === void 0 ? void 0 : _d.options) === null || _e === void 0 ? void 0 : _e.enableCount)) {
|
|
68
|
+
const countExtractor = ((_h = (_g = (_f = this._odataService) === null || _f === void 0 ? void 0 : _f.options) === null || _g === void 0 ? void 0 : _g.countExtractor) !== null && _h !== void 0 ? _h : odataVersion >= 4) ? (r) => r === null || r === void 0 ? void 0 : r['@odata.count'] :
|
|
69
|
+
odataVersion === 3 ? (r) => r === null || r === void 0 ? void 0 : r['__count'] :
|
|
70
|
+
(r) => { var _a; return (_a = r === null || r === void 0 ? void 0 : r.d) === null || _a === void 0 ? void 0 : _a['__count']; };
|
|
71
|
+
const count = countExtractor(processResult);
|
|
72
|
+
if (typeof count === 'number') {
|
|
73
|
+
this.pagination.totalItems = count;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if ((_k = (_j = this._odataService) === null || _j === void 0 ? void 0 : _j.options) === null || _k === void 0 ? void 0 : _k.enableExpand) {
|
|
77
|
+
const datasetExtractor = ((_o = (_m = (_l = this._odataService) === null || _l === void 0 ? void 0 : _l.options) === null || _m === void 0 ? void 0 : _m.datasetExtractor) !== null && _o !== void 0 ? _o : odataVersion >= 4) ? (r) => r === null || r === void 0 ? void 0 : r.value :
|
|
78
|
+
odataVersion === 3 ? (r) => r === null || r === void 0 ? void 0 : r.results :
|
|
79
|
+
(r) => { var _a; return (_a = r === null || r === void 0 ? void 0 : r.d) === null || _a === void 0 ? void 0 : _a.results; };
|
|
80
|
+
const dataset = datasetExtractor(processResult);
|
|
81
|
+
if (Array.isArray(dataset)) {
|
|
82
|
+
// Flatten navigation fields (fields containing /) in the dataset (regardless of enableExpand).
|
|
83
|
+
// E.g. given columndefinition 'product/name' and dataset [{id: 1,product:{'name':'flowers'}}], then flattens to [{id:1,'product/name':'flowers'}]
|
|
84
|
+
const navigationFields = new Set(this._columnDefinitions.flatMap(x => { var _a; return (_a = x.fields) !== null && _a !== void 0 ? _a : [x.field]; }).filter(x => x.includes('/')));
|
|
85
|
+
if (navigationFields.size > 0) {
|
|
86
|
+
const navigations = new Set();
|
|
87
|
+
for (const item of dataset) {
|
|
88
|
+
for (const field of navigationFields) {
|
|
89
|
+
const names = field.split('/');
|
|
90
|
+
const navigation = names[0];
|
|
91
|
+
navigations.add(navigation);
|
|
92
|
+
let val = item[navigation];
|
|
93
|
+
for (let i = 1; i < names.length; i++) {
|
|
94
|
+
const mappedName = names[i];
|
|
95
|
+
if (val && typeof val === 'object' && mappedName in val) {
|
|
96
|
+
val = val[mappedName];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
item[field] = val;
|
|
100
|
+
}
|
|
101
|
+
// Remove navigation objects from the dataset to free memory and make sure we never work with them.
|
|
102
|
+
for (const navigation of navigations) {
|
|
103
|
+
if (typeof item[navigation] === 'object') {
|
|
104
|
+
delete item[navigation];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
clearFilters() {
|
|
113
|
+
this._currentFilters = [];
|
|
114
|
+
this.updateFilters([]);
|
|
115
|
+
}
|
|
116
|
+
clearSorters() {
|
|
117
|
+
this._currentSorters = [];
|
|
118
|
+
this.updateSorters([]);
|
|
119
|
+
}
|
|
120
|
+
updateOptions(serviceOptions) {
|
|
121
|
+
this.options = { ...this.options, ...serviceOptions };
|
|
122
|
+
this._odataService.options = this.options;
|
|
123
|
+
}
|
|
124
|
+
removeColumnFilter(fieldName) {
|
|
125
|
+
this._odataService.removeColumnFilter(fieldName);
|
|
126
|
+
}
|
|
127
|
+
/** Get the Filters that are currently used by the grid */
|
|
128
|
+
getCurrentFilters() {
|
|
129
|
+
return this._currentFilters;
|
|
130
|
+
}
|
|
131
|
+
/** Get the Pagination that is currently used by the grid */
|
|
132
|
+
getCurrentPagination() {
|
|
133
|
+
return this._currentPagination;
|
|
134
|
+
}
|
|
135
|
+
/** Get the Sorters that are currently used by the grid */
|
|
136
|
+
getCurrentSorters() {
|
|
137
|
+
return this._currentSorters;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Mapper for mathematical operators (ex.: <= is "le", > is "gt")
|
|
141
|
+
* @param string operator
|
|
142
|
+
* @returns string map
|
|
143
|
+
*/
|
|
144
|
+
mapOdataOperator(operator) {
|
|
145
|
+
let map = '';
|
|
146
|
+
switch (operator) {
|
|
147
|
+
case '<':
|
|
148
|
+
map = 'lt';
|
|
149
|
+
break;
|
|
150
|
+
case '<=':
|
|
151
|
+
map = 'le';
|
|
152
|
+
break;
|
|
153
|
+
case '>':
|
|
154
|
+
map = 'gt';
|
|
155
|
+
break;
|
|
156
|
+
case '>=':
|
|
157
|
+
map = 'ge';
|
|
158
|
+
break;
|
|
159
|
+
case '<>':
|
|
160
|
+
case '!=':
|
|
161
|
+
map = 'ne';
|
|
162
|
+
break;
|
|
163
|
+
case '=':
|
|
164
|
+
case '==':
|
|
165
|
+
default:
|
|
166
|
+
map = 'eq';
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
return map;
|
|
170
|
+
}
|
|
171
|
+
/*
|
|
172
|
+
* Reset the pagination options
|
|
173
|
+
*/
|
|
174
|
+
resetPaginationOptions() {
|
|
175
|
+
this._odataService.updateOptions({
|
|
176
|
+
skip: 0
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
saveColumnFilter(fieldName, value, terms) {
|
|
180
|
+
this._odataService.saveColumnFilter(fieldName, value, terms);
|
|
181
|
+
}
|
|
182
|
+
/*
|
|
183
|
+
* FILTERING
|
|
184
|
+
*/
|
|
185
|
+
processOnFilterChanged(_event, args) {
|
|
186
|
+
const gridOptions = this._gridOptions;
|
|
187
|
+
const backendApi = gridOptions.backendServiceApi;
|
|
188
|
+
if (backendApi === undefined) {
|
|
189
|
+
throw new Error('Something went wrong in the GridOdataService, "backendServiceApi" is not initialized');
|
|
190
|
+
}
|
|
191
|
+
// keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
|
|
192
|
+
this._currentFilters = this.castFilterToColumnFilters(args.columnFilters);
|
|
193
|
+
if (!args || !args.grid) {
|
|
194
|
+
throw new Error('Something went wrong when trying create the GridOdataService, it seems that "args" is not populated correctly');
|
|
195
|
+
}
|
|
196
|
+
// loop through all columns to inspect filters & set the query
|
|
197
|
+
this.updateFilters(args.columnFilters);
|
|
198
|
+
this.resetPaginationOptions();
|
|
199
|
+
return this._odataService.buildQuery();
|
|
200
|
+
}
|
|
201
|
+
/*
|
|
202
|
+
* PAGINATION
|
|
203
|
+
*/
|
|
204
|
+
processOnPaginationChanged(_event, args) {
|
|
205
|
+
const pageSize = +(args.pageSize || ((this.pagination) ? this.pagination.pageSize : DEFAULT_PAGE_SIZE));
|
|
206
|
+
this.updatePagination(args.newPage, pageSize);
|
|
207
|
+
// build the OData query which we will use in the WebAPI callback
|
|
208
|
+
return this._odataService.buildQuery();
|
|
209
|
+
}
|
|
210
|
+
/*
|
|
211
|
+
* SORTING
|
|
212
|
+
*/
|
|
213
|
+
processOnSortChanged(_event, args) {
|
|
214
|
+
const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ columnId: args.sortCol.id, sortCol: args.sortCol, sortAsc: args.sortAsc });
|
|
215
|
+
// loop through all columns to inspect sorters & set the query
|
|
216
|
+
this.updateSorters(sortColumns);
|
|
217
|
+
// build the OData query which we will use in the WebAPI callback
|
|
218
|
+
return this._odataService.buildQuery();
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* loop through all columns to inspect filters & update backend service filters
|
|
222
|
+
* @param columnFilters
|
|
223
|
+
*/
|
|
224
|
+
updateFilters(columnFilters, isUpdatedByPresetOrDynamically) {
|
|
225
|
+
var _a, _b, _c, _d, _e, _f;
|
|
226
|
+
let searchBy = '';
|
|
227
|
+
const searchByArray = [];
|
|
228
|
+
const odataVersion = (_c = (_b = (_a = this._odataService) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.version) !== null && _c !== void 0 ? _c : 2;
|
|
229
|
+
// on filter preset load, we need to keep current filters
|
|
230
|
+
if (isUpdatedByPresetOrDynamically) {
|
|
231
|
+
this._currentFilters = this.castFilterToColumnFilters(columnFilters);
|
|
232
|
+
}
|
|
233
|
+
// loop through all columns to inspect filters
|
|
234
|
+
for (const columnId in columnFilters) {
|
|
235
|
+
if (columnFilters.hasOwnProperty(columnId)) {
|
|
236
|
+
const columnFilter = columnFilters[columnId];
|
|
237
|
+
// if user defined some "presets", then we need to find the filters from the column definitions instead
|
|
238
|
+
let columnDef;
|
|
239
|
+
if (isUpdatedByPresetOrDynamically && Array.isArray(this._columnDefinitions)) {
|
|
240
|
+
columnDef = this._columnDefinitions.find((column) => column.id === columnFilter.columnId);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
columnDef = columnFilter.columnDef;
|
|
244
|
+
}
|
|
245
|
+
if (!columnDef) {
|
|
246
|
+
throw new Error('[GridOData Service]: Something went wrong in trying to get the column definition of the specified filter (or preset filters). Did you make a typo on the filter columnId?');
|
|
247
|
+
}
|
|
248
|
+
let fieldName = ((_d = columnDef.filter) === null || _d === void 0 ? void 0 : _d.queryField) || columnDef.queryFieldFilter || columnDef.queryField || columnDef.field || columnDef.name || '';
|
|
249
|
+
const fieldType = columnDef.type || FieldType.string;
|
|
250
|
+
let searchTerms = (columnFilter && columnFilter.searchTerms ? [...columnFilter.searchTerms] : null) || [];
|
|
251
|
+
let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : '';
|
|
252
|
+
if (typeof fieldSearchValue === 'undefined') {
|
|
253
|
+
fieldSearchValue = '';
|
|
254
|
+
}
|
|
255
|
+
if (!fieldName) {
|
|
256
|
+
throw new Error(`GridOData filter could not find the field name to query the search, your column definition must include a valid "field" or "name" (optionally you can also use the "queryfield").`);
|
|
257
|
+
}
|
|
258
|
+
fieldSearchValue = (fieldSearchValue === undefined || fieldSearchValue === null) ? '' : `${fieldSearchValue}`; // make sure it's a string
|
|
259
|
+
// run regex to find possible filter operators unless the user disabled the feature
|
|
260
|
+
const autoParseInputFilterOperator = (_e = columnDef.autoParseInputFilterOperator) !== null && _e !== void 0 ? _e : this._gridOptions.autoParseInputFilterOperator;
|
|
261
|
+
const matches = autoParseInputFilterOperator !== false
|
|
262
|
+
? fieldSearchValue.match(/^([<>!=\*]{0,2})(.*[^<>!=\*])([\*]?)$/) // group 1: Operator, 2: searchValue, 3: last char is '*' (meaning starts with, ex.: abc*)
|
|
263
|
+
: [fieldSearchValue, '', fieldSearchValue, '']; // when parsing is disabled, we'll only keep the search value in the index 2 to make it easy for code reuse
|
|
264
|
+
let operator = columnFilter.operator || (matches === null || matches === void 0 ? void 0 : matches[1]);
|
|
265
|
+
let searchValue = (matches === null || matches === void 0 ? void 0 : matches[2]) || '';
|
|
266
|
+
const lastValueChar = (matches === null || matches === void 0 ? void 0 : matches[3]) || (operator === '*z' || operator === OperatorType.endsWith) ? '*' : '';
|
|
267
|
+
const bypassOdataQuery = columnFilter.bypassBackendQuery || false;
|
|
268
|
+
// no need to query if search value is empty
|
|
269
|
+
if (fieldName && searchValue === '' && searchTerms.length <= 1) {
|
|
270
|
+
this.removeColumnFilter(fieldName);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (Array.isArray(searchTerms) && searchTerms.length === 1 && typeof searchTerms[0] === 'string' && searchTerms[0].indexOf('..') >= 0) {
|
|
274
|
+
if (operator !== OperatorType.rangeInclusive && operator !== OperatorType.rangeExclusive) {
|
|
275
|
+
operator = (_f = this._gridOptions.defaultFilterRangeOperator) !== null && _f !== void 0 ? _f : OperatorType.rangeInclusive;
|
|
276
|
+
}
|
|
277
|
+
searchTerms = searchTerms[0].split('..', 2);
|
|
278
|
+
if (searchTerms[0] === '') {
|
|
279
|
+
operator = operator === OperatorType.rangeInclusive ? '<=' : operator === OperatorType.rangeExclusive ? '<' : operator;
|
|
280
|
+
searchTerms = searchTerms.slice(1);
|
|
281
|
+
searchValue = searchTerms[0];
|
|
282
|
+
}
|
|
283
|
+
else if (searchTerms[1] === '') {
|
|
284
|
+
operator = operator === OperatorType.rangeInclusive ? '>=' : operator === OperatorType.rangeExclusive ? '>' : operator;
|
|
285
|
+
searchTerms = searchTerms.slice(0, 1);
|
|
286
|
+
searchValue = searchTerms[0];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// if we didn't find an Operator but we have a Column Operator inside the Filter (DOM Element), we should use its default Operator
|
|
290
|
+
// multipleSelect is "IN", while singleSelect is "EQ", else don't map any operator
|
|
291
|
+
if (!operator && columnDef.filter) {
|
|
292
|
+
operator = columnDef.filter.operator;
|
|
293
|
+
}
|
|
294
|
+
// No operator and 2 search terms should lead to default range operator.
|
|
295
|
+
if (!operator && Array.isArray(searchTerms) && searchTerms.length === 2 && searchTerms[0] && searchTerms[1]) {
|
|
296
|
+
operator = this._gridOptions.defaultFilterRangeOperator;
|
|
297
|
+
}
|
|
298
|
+
// Range with 1 searchterm should lead to equals for a date field.
|
|
299
|
+
if ((operator === OperatorType.rangeInclusive || OperatorType.rangeExclusive) && Array.isArray(searchTerms) && searchTerms.length === 1 && fieldType === FieldType.date) {
|
|
300
|
+
operator = OperatorType.equal;
|
|
301
|
+
}
|
|
302
|
+
// if we still don't have an operator find the proper Operator to use by it's field type
|
|
303
|
+
if (!operator) {
|
|
304
|
+
operator = mapOperatorByFieldType(fieldType);
|
|
305
|
+
}
|
|
306
|
+
// extra query arguments
|
|
307
|
+
if (bypassOdataQuery) {
|
|
308
|
+
// push to our temp array and also trim white spaces
|
|
309
|
+
if (fieldName) {
|
|
310
|
+
this.saveColumnFilter(fieldName, fieldSearchValue, searchTerms);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// Normalize all search values
|
|
315
|
+
searchValue = this.normalizeSearchValue(fieldType, searchValue, odataVersion);
|
|
316
|
+
if (Array.isArray(searchTerms)) {
|
|
317
|
+
searchTerms.forEach((_part, index) => {
|
|
318
|
+
searchTerms[index] = this.normalizeSearchValue(fieldType, searchTerms[index], odataVersion);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
searchBy = '';
|
|
322
|
+
// titleCase the fieldName so that it matches the WebApi names
|
|
323
|
+
if (this._odataService.options.caseType === CaseType.pascalCase) {
|
|
324
|
+
fieldName = titleCase(fieldName || '');
|
|
325
|
+
}
|
|
326
|
+
if (searchTerms && searchTerms.length > 1 && (operator === 'IN' || operator === 'NIN' || operator === 'NOTIN' || operator === 'NOT IN' || operator === 'NOT_IN')) {
|
|
327
|
+
// when having more than 1 search term (then check if we have a "IN" or "NOT IN" filter search)
|
|
328
|
+
const tmpSearchTerms = [];
|
|
329
|
+
if (operator === 'IN') {
|
|
330
|
+
// example:: (Stage eq "Expired" or Stage eq "Renewal")
|
|
331
|
+
for (let j = 0, lnj = searchTerms.length; j < lnj; j++) {
|
|
332
|
+
tmpSearchTerms.push(`${fieldName} eq ${searchTerms[j]}`);
|
|
333
|
+
}
|
|
334
|
+
searchBy = tmpSearchTerms.join(' or ');
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// example:: (Stage ne "Expired" and Stage ne "Renewal")
|
|
338
|
+
for (let k = 0, lnk = searchTerms.length; k < lnk; k++) {
|
|
339
|
+
tmpSearchTerms.push(`${fieldName} ne ${searchTerms[k]}`);
|
|
340
|
+
}
|
|
341
|
+
searchBy = tmpSearchTerms.join(' and ');
|
|
342
|
+
}
|
|
343
|
+
if (!(typeof searchBy === 'string' && searchBy[0] === '(' && searchBy.slice(-1) === ')')) {
|
|
344
|
+
searchBy = `(${searchBy})`;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else if (operator === '*' || operator === 'a*' || operator === '*z' || lastValueChar === '*' || operator === OperatorType.startsWith || operator === OperatorType.endsWith) {
|
|
348
|
+
// first/last character is a '*' will be a startsWith or endsWith
|
|
349
|
+
searchBy = (operator === '*' || operator === '*z' || operator === OperatorType.endsWith) ? `endswith(${fieldName}, ${searchValue})` : `startswith(${fieldName}, ${searchValue})`;
|
|
350
|
+
}
|
|
351
|
+
else if (operator === OperatorType.rangeExclusive || operator === OperatorType.rangeInclusive) {
|
|
352
|
+
// example:: (Name >= 'Bob' and Name <= 'Jane')
|
|
353
|
+
searchBy = this.filterBySearchTermRange(fieldName, operator, searchTerms);
|
|
354
|
+
}
|
|
355
|
+
else if ((operator === '' || operator === OperatorType.contains || operator === OperatorType.notContains) &&
|
|
356
|
+
(fieldType === FieldType.string || fieldType === FieldType.text || fieldType === FieldType.readonly)) {
|
|
357
|
+
searchBy = odataVersion >= 4 ? `contains(${fieldName}, ${searchValue})` : `substringof(${searchValue}, ${fieldName})`;
|
|
358
|
+
if (operator === OperatorType.notContains) {
|
|
359
|
+
searchBy = `not ${searchBy}`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// any other field type (or undefined type)
|
|
364
|
+
searchBy = `${fieldName} ${this.mapOdataOperator(operator)} ${searchValue}`;
|
|
365
|
+
}
|
|
366
|
+
// push to our temp array and also trim white spaces
|
|
367
|
+
if (searchBy !== '') {
|
|
368
|
+
searchByArray.push(searchBy.trim());
|
|
369
|
+
this.saveColumnFilter(fieldName || '', fieldSearchValue, searchValue);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// update the service options with filters for the buildQuery() to work later
|
|
375
|
+
this._odataService.updateOptions({
|
|
376
|
+
filter: (searchByArray.length > 0) ? searchByArray.join(' and ') : '',
|
|
377
|
+
skip: undefined
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Update the pagination component with it's new page number and size
|
|
382
|
+
* @param newPage
|
|
383
|
+
* @param pageSize
|
|
384
|
+
*/
|
|
385
|
+
updatePagination(newPage, pageSize) {
|
|
386
|
+
this._currentPagination = {
|
|
387
|
+
pageNumber: newPage,
|
|
388
|
+
pageSize,
|
|
389
|
+
};
|
|
390
|
+
// unless user specifically set "enablePagination" to False, we'll update pagination options in every other cases
|
|
391
|
+
if (this._gridOptions && (this._gridOptions.enablePagination || !this._gridOptions.hasOwnProperty('enablePagination'))) {
|
|
392
|
+
this._odataService.updateOptions({
|
|
393
|
+
top: pageSize,
|
|
394
|
+
skip: (newPage - 1) * pageSize
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* loop through all columns to inspect sorters & update backend service orderBy
|
|
400
|
+
* @param columnFilters
|
|
401
|
+
*/
|
|
402
|
+
updateSorters(sortColumns, presetSorters) {
|
|
403
|
+
let currentSorters = [];
|
|
404
|
+
const odataSorters = [];
|
|
405
|
+
if (!sortColumns && presetSorters) {
|
|
406
|
+
// make the presets the current sorters, also make sure that all direction are in lowercase for OData
|
|
407
|
+
currentSorters = presetSorters;
|
|
408
|
+
currentSorters.forEach((sorter) => sorter.direction = sorter.direction.toLowerCase());
|
|
409
|
+
// display the correct sorting icons on the UI, for that it requires (columnId, sortAsc) properties
|
|
410
|
+
const tmpSorterArray = currentSorters.map((sorter) => {
|
|
411
|
+
const columnDef = this._columnDefinitions.find((column) => column.id === sorter.columnId);
|
|
412
|
+
odataSorters.push({
|
|
413
|
+
field: columnDef ? ((columnDef.queryFieldSorter || columnDef.queryField || columnDef.field) + '') : (sorter.columnId + ''),
|
|
414
|
+
direction: sorter.direction
|
|
415
|
+
});
|
|
416
|
+
// return only the column(s) found in the Column Definitions ELSE null
|
|
417
|
+
if (columnDef) {
|
|
418
|
+
return {
|
|
419
|
+
columnId: sorter.columnId,
|
|
420
|
+
sortAsc: sorter.direction.toUpperCase() === SortDirection.ASC
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
});
|
|
425
|
+
// set the sort icons, but also make sure to filter out null values (that happens when columnDef is not found)
|
|
426
|
+
if (Array.isArray(tmpSorterArray) && this._grid) {
|
|
427
|
+
this._grid.setSortColumns(tmpSorterArray);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else if (sortColumns && !presetSorters) {
|
|
431
|
+
// build the SortBy string, it could be multisort, example: customerNo asc, purchaserName desc
|
|
432
|
+
if ((sortColumns === null || sortColumns === void 0 ? void 0 : sortColumns.length) === 0) {
|
|
433
|
+
// TODO fix this line
|
|
434
|
+
// currentSorters = new Array(this.defaultOptions.orderBy); // when empty, use the default sort
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
if (sortColumns) {
|
|
438
|
+
for (const columnDef of sortColumns) {
|
|
439
|
+
if (columnDef.sortCol) {
|
|
440
|
+
let fieldName = (columnDef.sortCol.queryFieldSorter || columnDef.sortCol.queryField || columnDef.sortCol.field) + '';
|
|
441
|
+
let columnFieldName = (columnDef.sortCol.field || columnDef.sortCol.id) + '';
|
|
442
|
+
let queryField = (columnDef.sortCol.queryFieldSorter || columnDef.sortCol.queryField || columnDef.sortCol.field || '') + '';
|
|
443
|
+
if (this._odataService.options.caseType === CaseType.pascalCase) {
|
|
444
|
+
fieldName = titleCase(fieldName);
|
|
445
|
+
columnFieldName = titleCase(columnFieldName);
|
|
446
|
+
queryField = titleCase(queryField);
|
|
447
|
+
}
|
|
448
|
+
if (columnFieldName !== '') {
|
|
449
|
+
currentSorters.push({
|
|
450
|
+
columnId: columnFieldName,
|
|
451
|
+
direction: columnDef.sortAsc ? 'asc' : 'desc'
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (queryField !== '') {
|
|
455
|
+
odataSorters.push({
|
|
456
|
+
field: queryField,
|
|
457
|
+
direction: columnDef.sortAsc ? SortDirection.ASC : SortDirection.DESC
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// transform the sortby array into a CSV string for OData
|
|
466
|
+
currentSorters = currentSorters || [];
|
|
467
|
+
const csvString = odataSorters.map((sorter) => {
|
|
468
|
+
let str = '';
|
|
469
|
+
if (sorter && sorter.field) {
|
|
470
|
+
const sortField = (this._odataService.options.caseType === CaseType.pascalCase) ? titleCase(sorter.field) : sorter.field;
|
|
471
|
+
str = `${sortField} ${sorter && sorter.direction && sorter.direction.toLowerCase() || ''}`;
|
|
472
|
+
}
|
|
473
|
+
return str;
|
|
474
|
+
}).join(',');
|
|
475
|
+
this._odataService.updateOptions({
|
|
476
|
+
orderBy: csvString
|
|
477
|
+
});
|
|
478
|
+
// keep current Sorters and update the service options with the new sorting
|
|
479
|
+
this._currentSorters = currentSorters;
|
|
480
|
+
// build the OData query which we will use in the WebAPI callback
|
|
481
|
+
return this._odataService.buildQuery();
|
|
482
|
+
}
|
|
483
|
+
//
|
|
484
|
+
// protected functions
|
|
485
|
+
// -------------------
|
|
486
|
+
/**
|
|
487
|
+
* Cast provided filters (could be in multiple format) into an array of ColumnFilter
|
|
488
|
+
* @param columnFilters
|
|
489
|
+
*/
|
|
490
|
+
castFilterToColumnFilters(columnFilters) {
|
|
491
|
+
// keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
|
|
492
|
+
const filtersArray = (typeof columnFilters === 'object') ? Object.keys(columnFilters).map(key => columnFilters[key]) : columnFilters;
|
|
493
|
+
if (!Array.isArray(filtersArray)) {
|
|
494
|
+
return [];
|
|
495
|
+
}
|
|
496
|
+
return filtersArray.map((filter) => {
|
|
497
|
+
const tmpFilter = { columnId: filter.columnId || '' };
|
|
498
|
+
if (filter.operator) {
|
|
499
|
+
tmpFilter.operator = filter.operator;
|
|
500
|
+
}
|
|
501
|
+
if (filter.targetSelector) {
|
|
502
|
+
tmpFilter.targetSelector = filter.targetSelector;
|
|
503
|
+
}
|
|
504
|
+
if (Array.isArray(filter.searchTerms)) {
|
|
505
|
+
tmpFilter.searchTerms = filter.searchTerms;
|
|
506
|
+
}
|
|
507
|
+
return tmpFilter;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Filter by a range of searchTerms (2 searchTerms OR 1 string separated by 2 dots "value1..value2")
|
|
512
|
+
*/
|
|
513
|
+
filterBySearchTermRange(fieldName, operator, searchTerms) {
|
|
514
|
+
let query = '';
|
|
515
|
+
if (Array.isArray(searchTerms) && searchTerms.length === 2) {
|
|
516
|
+
if (operator === OperatorType.rangeInclusive) {
|
|
517
|
+
// example:: (Duration >= 5 and Duration <= 10)
|
|
518
|
+
query = `(${fieldName} ge ${searchTerms[0]}`;
|
|
519
|
+
if (searchTerms[1] !== '') {
|
|
520
|
+
query += ` and ${fieldName} le ${searchTerms[1]}`;
|
|
521
|
+
}
|
|
522
|
+
query += ')';
|
|
523
|
+
}
|
|
524
|
+
else if (operator === OperatorType.rangeExclusive) {
|
|
525
|
+
// example:: (Duration > 5 and Duration < 10)
|
|
526
|
+
query = `(${fieldName} gt ${searchTerms[0]}`;
|
|
527
|
+
if (searchTerms[1] !== '') {
|
|
528
|
+
query += ` and ${fieldName} lt ${searchTerms[1]}`;
|
|
529
|
+
}
|
|
530
|
+
query += ')';
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return query;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Normalizes the search value according to field type and oData version.
|
|
537
|
+
*/
|
|
538
|
+
normalizeSearchValue(fieldType, searchValue, version) {
|
|
539
|
+
switch (fieldType) {
|
|
540
|
+
case FieldType.date:
|
|
541
|
+
searchValue = parseUtcDate(searchValue, true);
|
|
542
|
+
searchValue = version >= 4 ? searchValue : `DateTime'${searchValue}'`;
|
|
543
|
+
break;
|
|
544
|
+
case FieldType.string:
|
|
545
|
+
case FieldType.text:
|
|
546
|
+
case FieldType.readonly:
|
|
547
|
+
if (typeof searchValue === 'string') {
|
|
548
|
+
// escape single quotes by doubling them
|
|
549
|
+
searchValue = searchValue.replace(/'/g, `''`);
|
|
550
|
+
// encode URI of the final search value
|
|
551
|
+
searchValue = encodeURIComponent(searchValue);
|
|
552
|
+
// strings need to be quoted.
|
|
553
|
+
searchValue = `'${searchValue}'`;
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
case FieldType.integer:
|
|
557
|
+
case FieldType.number:
|
|
558
|
+
case FieldType.float:
|
|
559
|
+
if (typeof searchValue === 'string') {
|
|
560
|
+
// Parse a valid decimal from the string.
|
|
561
|
+
// Replace double dots with single dots
|
|
562
|
+
searchValue = searchValue.replace(/\.\./g, '.');
|
|
563
|
+
// Remove a trailing dot
|
|
564
|
+
searchValue = searchValue.replace(/\.+$/g, '');
|
|
565
|
+
// Prefix a leading dot with 0
|
|
566
|
+
searchValue = searchValue.replace(/^\.+/g, '0.');
|
|
567
|
+
// Prefix leading dash dot with -0.
|
|
568
|
+
searchValue = searchValue.replace(/^\-+\.+/g, '-0.');
|
|
569
|
+
// Remove any non valid decimal characters from the search string
|
|
570
|
+
searchValue = searchValue.replace(/(?!^\-)[^\d\.]/g, '');
|
|
571
|
+
// if nothing left, search for 0
|
|
572
|
+
if (searchValue === '' || searchValue === '-') {
|
|
573
|
+
searchValue = '0';
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
return searchValue;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
581
|
//# sourceMappingURL=grid-odata.service.js.map
|