@slickgrid-universal/odata 4.0.3 → 4.2.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/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/src/index.ts +3 -0
- package/src/interfaces/index.ts +3 -0
- package/src/interfaces/odataOption.interface.ts +57 -0
- package/src/interfaces/odataServiceApi.interface.ts +11 -0
- package/src/interfaces/odataSortingOption.interface.ts +6 -0
- package/src/services/grid-odata.service.ts +690 -0
- package/src/services/index.ts +2 -0
- package/src/services/odataQueryBuilder.service.ts +233 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { CaseType, type Column } from '@slickgrid-universal/common';
|
|
2
|
+
import { titleCase } from '@slickgrid-universal/utils';
|
|
3
|
+
import type { OdataOption } from '../interfaces/odataOption.interface';
|
|
4
|
+
|
|
5
|
+
export class OdataQueryBuilderService {
|
|
6
|
+
_columnFilters: any;
|
|
7
|
+
_defaultSortBy: string;
|
|
8
|
+
_filterCount = 0;
|
|
9
|
+
_odataOptions: Partial<OdataOption>;
|
|
10
|
+
|
|
11
|
+
protected _columnDefinitions: Column[] = [];
|
|
12
|
+
public set columnDefinitions(columnDefinitions: Column[]) {
|
|
13
|
+
this._columnDefinitions = columnDefinitions;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected _datasetIdPropName = 'id';
|
|
17
|
+
public set datasetIdPropName(datasetIdPropName: string) {
|
|
18
|
+
this._datasetIdPropName = datasetIdPropName;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this._odataOptions = {
|
|
23
|
+
filterQueue: [],
|
|
24
|
+
orderBy: ''
|
|
25
|
+
};
|
|
26
|
+
this._defaultSortBy = '';
|
|
27
|
+
this._columnFilters = {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Build the OData query string from all the options provided
|
|
32
|
+
* @return string OData query
|
|
33
|
+
*/
|
|
34
|
+
buildQuery(): string {
|
|
35
|
+
if (!this._odataOptions) {
|
|
36
|
+
throw new Error('Odata Service requires certain options like "top" for it to work');
|
|
37
|
+
}
|
|
38
|
+
this._odataOptions.filterQueue = [];
|
|
39
|
+
const queryTmpArray = [];
|
|
40
|
+
|
|
41
|
+
// When enableCount is set, add it to the OData query
|
|
42
|
+
if (this._odataOptions && this._odataOptions.enableCount === true) {
|
|
43
|
+
const countQuery = (this._odataOptions.version && this._odataOptions.version >= 4) ? '$count=true' : '$inlinecount=allpages';
|
|
44
|
+
queryTmpArray.push(countQuery);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this._odataOptions.top) {
|
|
48
|
+
queryTmpArray.push(`$top=${this._odataOptions.top}`);
|
|
49
|
+
}
|
|
50
|
+
if (this._odataOptions.skip) {
|
|
51
|
+
queryTmpArray.push(`$skip=${this._odataOptions.skip}`);
|
|
52
|
+
}
|
|
53
|
+
if (this._odataOptions.orderBy) {
|
|
54
|
+
let argument = '';
|
|
55
|
+
if (Array.isArray(this._odataOptions.orderBy)) {
|
|
56
|
+
argument = this._odataOptions.orderBy.join(','); // csv, that will form a query, for example: $orderby=RoleName asc, Id desc
|
|
57
|
+
} else {
|
|
58
|
+
argument = this._odataOptions.orderBy;
|
|
59
|
+
}
|
|
60
|
+
queryTmpArray.push(`$orderby=${argument}`);
|
|
61
|
+
}
|
|
62
|
+
if (this._odataOptions.filterBy || this._odataOptions.filter) {
|
|
63
|
+
const filterBy = this._odataOptions.filter || this._odataOptions.filterBy;
|
|
64
|
+
if (filterBy) {
|
|
65
|
+
this._filterCount = 1;
|
|
66
|
+
this._odataOptions.filterQueue = [];
|
|
67
|
+
let filterStr = filterBy;
|
|
68
|
+
if (Array.isArray(filterBy)) {
|
|
69
|
+
this._filterCount = filterBy.length;
|
|
70
|
+
filterStr = filterBy.join(` ${this._odataOptions.filterBySeparator || 'and'} `);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof filterStr === 'string') {
|
|
74
|
+
if (!(filterStr[0] === '(' && filterStr.slice(-1) === ')')) {
|
|
75
|
+
this.addToFilterQueueWhenNotExists(`(${filterStr})`);
|
|
76
|
+
} else {
|
|
77
|
+
this.addToFilterQueueWhenNotExists(filterStr);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (this._odataOptions.filterQueue.length > 0) {
|
|
83
|
+
const query = this._odataOptions.filterQueue.join(` ${this._odataOptions.filterBySeparator || 'and'} `);
|
|
84
|
+
this._odataOptions.filter = query; // overwrite with
|
|
85
|
+
queryTmpArray.push(`$filter=${query}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this._odataOptions.enableSelect || this._odataOptions.enableExpand) {
|
|
89
|
+
const fields = this._columnDefinitions.flatMap(x => x.fields ?? [x.field]);
|
|
90
|
+
fields.unshift(this._datasetIdPropName);
|
|
91
|
+
const selectExpand = this.buildSelectExpand([...new Set(fields)]);
|
|
92
|
+
if (this._odataOptions.enableSelect) {
|
|
93
|
+
const select = selectExpand.selectParts.join(',');
|
|
94
|
+
queryTmpArray.push(`$select=${select}`);
|
|
95
|
+
}
|
|
96
|
+
if (this._odataOptions.enableExpand) {
|
|
97
|
+
const expand = selectExpand.expandParts.join(',');
|
|
98
|
+
queryTmpArray.push(`$expand=${expand}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// join all the odata functions by a '&'
|
|
103
|
+
return queryTmpArray.join('&');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getFilterCount(): number {
|
|
107
|
+
return this._filterCount;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get columnFilters(): any[] {
|
|
111
|
+
return this._columnFilters;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get options(): Partial<OdataOption> {
|
|
115
|
+
return this._odataOptions;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
set options(options: Partial<OdataOption>) {
|
|
119
|
+
this._odataOptions = options;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
removeColumnFilter(fieldName: string) {
|
|
123
|
+
if (this._columnFilters && this._columnFilters.hasOwnProperty(fieldName)) {
|
|
124
|
+
delete this._columnFilters[fieldName];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
saveColumnFilter(fieldName: string, value: any, searchTerms?: any[]) {
|
|
129
|
+
this._columnFilters[fieldName] = {
|
|
130
|
+
search: searchTerms,
|
|
131
|
+
value
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Change any OData options that will be used to build the query
|
|
137
|
+
* @param object options
|
|
138
|
+
*/
|
|
139
|
+
updateOptions(options: Partial<OdataOption>) {
|
|
140
|
+
for (const property of Object.keys(options)) {
|
|
141
|
+
if (options.hasOwnProperty(property)) {
|
|
142
|
+
this._odataOptions[property as keyof OdataOption] = options[property as keyof OdataOption]; // replace of the property
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// we need to keep the defaultSortBy for references whenever the user removes his Sorting
|
|
146
|
+
// then we would revert to the defaultSortBy and the only way is to keep a hard copy here
|
|
147
|
+
if (property === 'orderBy' || property === 'sortBy') {
|
|
148
|
+
let sortBy = options[property as keyof OdataOption];
|
|
149
|
+
|
|
150
|
+
// make sure first char of each orderBy field is capitalize
|
|
151
|
+
if (this._odataOptions.caseType === CaseType.pascalCase) {
|
|
152
|
+
if (Array.isArray(sortBy)) {
|
|
153
|
+
sortBy.forEach((field, index, inputArray) => {
|
|
154
|
+
inputArray[index] = titleCase(field);
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
sortBy = titleCase(options[property as keyof OdataOption]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this._odataOptions.orderBy = sortBy;
|
|
161
|
+
this._defaultSortBy = sortBy;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//
|
|
167
|
+
// protected functions
|
|
168
|
+
// -------------------
|
|
169
|
+
|
|
170
|
+
protected addToFilterQueueWhenNotExists(filterStr: string) {
|
|
171
|
+
if (this._odataOptions.filterQueue && this._odataOptions.filterQueue.indexOf(filterStr) === -1) {
|
|
172
|
+
this._odataOptions.filterQueue.push(filterStr);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//
|
|
177
|
+
// private functions
|
|
178
|
+
// -------------------
|
|
179
|
+
|
|
180
|
+
private buildSelectExpand(selectFields: string[]): { selectParts: string[]; expandParts: string[] } {
|
|
181
|
+
const navigations: { [navigation: string]: string[] } = {};
|
|
182
|
+
const selectItems = new Set<string>();
|
|
183
|
+
|
|
184
|
+
for (const field of selectFields) {
|
|
185
|
+
const splits = field.split('/');
|
|
186
|
+
if (splits.length === 1) {
|
|
187
|
+
selectItems.add(field);
|
|
188
|
+
} else {
|
|
189
|
+
const navigation = splits[0];
|
|
190
|
+
const properties = splits.splice(1).join('/');
|
|
191
|
+
|
|
192
|
+
if (!navigations[navigation]) {
|
|
193
|
+
navigations[navigation] = [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
navigations[navigation].push(properties);
|
|
197
|
+
|
|
198
|
+
if (this._odataOptions.enableExpand && !(this._odataOptions.version && this._odataOptions.version >= 4)) {
|
|
199
|
+
selectItems.add(navigation);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
selectParts: [...selectItems],
|
|
206
|
+
expandParts: this._odataOptions.enableExpand ? this.buildExpand(navigations) : []
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private buildExpand(navigations: { [navigation: string]: string[] }): string[] {
|
|
211
|
+
const expandParts = [];
|
|
212
|
+
for (const navigation of Object.keys(navigations)) {
|
|
213
|
+
if (this._odataOptions.enableSelect && this._odataOptions.version && this._odataOptions.version >= 4) {
|
|
214
|
+
const subSelectExpand = this.buildSelectExpand(navigations[navigation]);
|
|
215
|
+
let subSelect = subSelectExpand.selectParts.join(',');
|
|
216
|
+
if (subSelect.length > 0) {
|
|
217
|
+
subSelect = '$select=' + subSelect;
|
|
218
|
+
}
|
|
219
|
+
if (this._odataOptions.enableExpand && subSelectExpand.expandParts.length > 0) {
|
|
220
|
+
subSelect += (subSelect.length > 0 ? ';' : '') + '$expand=' + subSelectExpand.expandParts.join(',');
|
|
221
|
+
}
|
|
222
|
+
if (subSelect.length > 0) {
|
|
223
|
+
subSelect = '(' + subSelect + ')';
|
|
224
|
+
}
|
|
225
|
+
expandParts.push(navigation + subSelect);
|
|
226
|
+
} else {
|
|
227
|
+
expandParts.push(navigation);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return expandParts;
|
|
232
|
+
}
|
|
233
|
+
}
|