@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.
Files changed (42) hide show
  1. package/dist/commonjs/index.js +22 -22
  2. package/dist/commonjs/interfaces/index.js +19 -19
  3. package/dist/commonjs/interfaces/odataOption.interface.js +2 -2
  4. package/dist/commonjs/interfaces/odataServiceApi.interface.js +2 -2
  5. package/dist/commonjs/interfaces/odataSortingOption.interface.js +2 -2
  6. package/dist/commonjs/services/grid-odata.service.js +582 -582
  7. package/dist/commonjs/services/index.js +18 -18
  8. package/dist/commonjs/services/odataQueryBuilder.service.js +208 -208
  9. package/dist/esm/index.js +3 -3
  10. package/dist/esm/interfaces/index.js +3 -3
  11. package/dist/esm/interfaces/odataOption.interface.js +1 -1
  12. package/dist/esm/interfaces/odataServiceApi.interface.js +1 -1
  13. package/dist/esm/interfaces/odataSortingOption.interface.js +1 -1
  14. package/dist/esm/services/grid-odata.service.js +580 -580
  15. package/dist/esm/services/index.js +2 -2
  16. package/dist/esm/services/odataQueryBuilder.service.js +204 -204
  17. package/dist/tsconfig.tsbuildinfo +1 -0
  18. package/dist/{commonjs → types}/index.d.ts +4 -3
  19. package/dist/types/index.d.ts.map +1 -0
  20. package/dist/{esm → types}/interfaces/index.d.ts +4 -3
  21. package/dist/types/interfaces/index.d.ts.map +1 -0
  22. package/dist/{commonjs → types}/interfaces/odataOption.interface.d.ts +38 -37
  23. package/dist/types/interfaces/odataOption.interface.d.ts.map +1 -0
  24. package/dist/{esm → types}/interfaces/odataServiceApi.interface.d.ts +10 -9
  25. package/dist/types/interfaces/odataServiceApi.interface.d.ts.map +1 -0
  26. package/dist/{commonjs → types}/interfaces/odataSortingOption.interface.d.ts +6 -5
  27. package/dist/types/interfaces/odataSortingOption.interface.d.ts.map +1 -0
  28. package/dist/{commonjs → types}/services/grid-odata.service.d.ts +75 -74
  29. package/dist/types/services/grid-odata.service.d.ts.map +1 -0
  30. package/dist/{esm → types}/services/index.d.ts +3 -2
  31. package/dist/types/services/index.d.ts.map +1 -0
  32. package/dist/{commonjs → types}/services/odataQueryBuilder.service.d.ts +29 -28
  33. package/dist/types/services/odataQueryBuilder.service.d.ts.map +1 -0
  34. package/package.json +9 -9
  35. package/dist/commonjs/interfaces/index.d.ts +0 -3
  36. package/dist/commonjs/interfaces/odataServiceApi.interface.d.ts +0 -9
  37. package/dist/commonjs/services/index.d.ts +0 -2
  38. package/dist/esm/index.d.ts +0 -3
  39. package/dist/esm/interfaces/odataOption.interface.d.ts +0 -37
  40. package/dist/esm/interfaces/odataSortingOption.interface.d.ts +0 -5
  41. package/dist/esm/services/grid-odata.service.d.ts +0 -74
  42. 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