@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.
@@ -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
+ }