@genesislcap/grid-pro 14.483.1-alpha-aaddab2.0 → 14.483.1

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.
@@ -24,6 +24,12 @@ export class BaseServerSideDatasource {
24
24
  this.moreRows = false;
25
25
  this.calculatedRowsCount = 0;
26
26
  this.currentSequenceId = null;
27
+ /**
28
+ * Whether the corrective ROWS_COUNT snapshot() has resolved for the current CRITERIA_MATCH.
29
+ * Reset in destroy() so it re-resolves on filter/sort changes.
30
+ * @internal
31
+ */
32
+ this.criteriaRowCountResolved = false;
27
33
  this.reloadResourceDataFunc = options.reloadResourceDataFunc;
28
34
  this.errorHandlerFunc = options.errorHandlerFunc;
29
35
  this.onNoDataAvailableFunc = options.onNoDataAvailableFunc;
@@ -79,6 +85,9 @@ export class BaseServerSideDatasource {
79
85
  /**
80
86
  * Handles filtering setup for server-side datasources.
81
87
  * Common logic used by both dataserver and req-rep implementations.
88
+ *
89
+ * @returns `true` when a filter change triggered {@link refreshDatasource} - the current
90
+ * getRows() request is then superseded and the caller must stop processing it.
82
91
  */
83
92
  setupFiltering(params) {
84
93
  return __awaiter(this, void 0, void 0, function* () {
@@ -91,15 +100,18 @@ export class BaseServerSideDatasource {
91
100
  this.currentFilterModel = null;
92
101
  this.setResourceParam('CRITERIA_MATCH', this.originalCriteriaMatch);
93
102
  yield this.refreshDatasource(params);
94
- return;
103
+ // The refresh re-initialized the datasource and purged AG Grid's SSRM cache, so
104
+ // getRows() is re-invoked with fresh params - this invocation is superseded.
105
+ return true;
95
106
  }
96
107
  else if (filterModelBeingAppliedDiffersFromCurrent && filtersAreBeingApplied) {
97
108
  this.currentFilterModel = filterModelBeingApplied;
98
109
  this.setResourceParam('CRITERIA_MATCH', this.buildCriteriaMatchFromFilters());
99
110
  yield this.refreshDatasource(params);
100
- return;
111
+ return true;
101
112
  }
102
113
  }
114
+ return false;
103
115
  });
104
116
  }
105
117
  /**
@@ -122,6 +134,7 @@ export class BaseServerSideDatasource {
122
134
  this.moreRows = false;
123
135
  this.sourceRef = undefined;
124
136
  this.lastSuccessRowData = undefined;
137
+ this.criteriaRowCountResolved = false;
125
138
  }
126
139
  refreshDatasource(params) {
127
140
  return __awaiter(this, void 0, void 0, function* () {
@@ -173,142 +186,184 @@ export class BaseServerSideDatasource {
173
186
  criteriaFromFilters() {
174
187
  const filters = [];
175
188
  this.getFiltersByType('text').forEach((k) => {
176
- if (!this.currentFilterModel[k].filter &&
177
- (this.currentFilterModel[k].type === 'false' || this.currentFilterModel[k].type === 'true')) {
178
- filters.push(`${k} == ${this.currentFilterModel[k].type}`);
179
- }
180
- else {
181
- switch (this.currentFilterModel[k].type) {
182
- case 'blank':
183
- filters.push(`(${k} == '' || ${k} == null)`);
184
- break;
185
- case 'contains':
186
- filters.push(`Expr.containsIgnoreCase(${k}, '${this.currentFilterModel[k].filter}')`);
187
- break;
188
- case 'equals':
189
- filters.push(`${k} == '${this.currentFilterModel[k].filter}'`);
190
- break;
191
- case 'notBlank':
192
- filters.push(`(${k} != '' && ${k} != null)`);
193
- break;
194
- case 'notEqual':
195
- filters.push(`${k} != '${this.currentFilterModel[k].filter}'`);
196
- break;
197
- case 'wordStartsWith':
198
- filters.push(`Expr.containsWordsStartingWithIgnoreCase(${k}, '${this.currentFilterModel[k].filter}')`);
199
- break;
200
- }
189
+ const fragment = this.criteriaForSingleModel(k, this.currentFilterModel[k]);
190
+ if (fragment) {
191
+ filters.push(fragment);
201
192
  }
202
193
  });
203
- // TODO: Handle multi filters if we decide we want to use them in SSRM
204
194
  // Handle set filters (typically used for enum fields with ag-set-column-filter)
205
195
  this.getFiltersByType('set').forEach((k) => {
206
- var _a, _b, _c;
207
- const model = (_a = this.currentFilterModel) === null || _a === void 0 ? void 0 : _a[k];
208
- const selectedValues = ((_b = model === null || model === void 0 ? void 0 : model.values) !== null && _b !== void 0 ? _b : []);
209
- if (!selectedValues || selectedValues.length === 0) {
210
- // Nothing selected means no additional criteria
211
- return;
212
- }
213
- // Try to detect "all values selected" using VALID_VALUES from metadata.
214
- // In that case we don't want to send any criteria, as it would be equivalent
215
- // to no filter at all.
216
- const colMeta = (_c = this.resourceColDefs) === null || _c === void 0 ? void 0 : _c.find((o) => o.NAME === k);
217
- let allValues;
218
- if (colMeta === null || colMeta === void 0 ? void 0 : colMeta.VALID_VALUES) {
219
- try {
220
- allValues = Array.isArray(colMeta.VALID_VALUES)
221
- ? colMeta.VALID_VALUES
222
- : colMeta.VALID_VALUES.split('|');
223
- }
224
- catch (_d) {
225
- allValues = undefined;
226
- }
227
- }
228
- if (allValues &&
229
- allValues.length === selectedValues.length &&
230
- allValues.every((v) => selectedValues.includes(v))) {
231
- // All enum options are selected – skip adding criteria for this column
232
- return;
233
- }
234
- const orConditions = selectedValues.map((val) => {
235
- const safeVal = String(val).replace(/'/g, "\\'");
236
- return `${k} == '${safeVal}'`;
237
- });
238
- if (orConditions.length > 0) {
239
- filters.push(`(${orConditions.join(' || ')})`);
196
+ const fragment = this.criteriaForSingleModel(k, this.currentFilterModel[k]);
197
+ if (fragment) {
198
+ filters.push(fragment);
240
199
  }
241
200
  });
242
201
  this.getFiltersByType('number').forEach((k) => {
243
- const value = this.currentFilterModel[k].filter;
244
- const valueTwo = this.currentFilterModel[k].filterTo;
245
- switch (this.currentFilterModel[k].type) {
246
- case 'equals':
247
- !isNaN(value) && filters.push(`${k} == ${value}`);
248
- break;
249
- case 'notEqual':
250
- !isNaN(value) && filters.push(`${k} != ${value}`);
251
- break;
252
- case 'greaterThan':
253
- !isNaN(value) && filters.push(`${k} > ${value}`);
254
- break;
255
- case 'greaterThanOrEqual':
256
- !isNaN(value) && filters.push(`${k} >= ${value}`);
257
- break;
258
- case 'lessThan':
259
- !isNaN(value) && filters.push(`${k} < ${value}`);
260
- break;
261
- case 'lessThanOrEqual':
262
- !isNaN(value) && filters.push(`${k} <= ${value}`);
263
- break;
264
- case 'inRange':
265
- !isNaN(value) &&
266
- !isNaN(valueTwo) &&
267
- filters.push(`${k} >= ${value} && ${k} <= ${valueTwo}`);
268
- break;
269
- case 'blank':
270
- filters.push(`${k} == 0`);
271
- break;
272
- case 'notBlank':
273
- filters.push(`${k} != 0`);
274
- break;
202
+ const fragment = this.criteriaForSingleModel(k, this.currentFilterModel[k]);
203
+ if (fragment) {
204
+ filters.push(fragment);
275
205
  }
276
206
  });
277
207
  this.getFiltersByType('date').forEach((k) => {
278
- var _a, _b;
279
- const dateFrom = (_a = this.currentFilterModel[k].dateFrom) === null || _a === void 0 ? void 0 : _a.replace(/-/g, '').replace('T', '-').split(' ')[0];
280
- const dateTo = (_b = this.currentFilterModel[k].dateTo) === null || _b === void 0 ? void 0 : _b.replace(/-/g, '').replace('T', '-').split(' ')[0];
281
- switch (this.currentFilterModel[k].type) {
282
- case 'equals':
283
- filters.push(`Expr.dateIsEqual(${k}, '${dateFrom}')`);
284
- break;
285
- case 'lessThan':
286
- filters.push(`Expr.dateIsBefore(${k}, '${dateFrom}')`);
287
- break;
288
- case 'greaterThan':
289
- filters.push(`Expr.dateIsAfter(${k}, '${dateFrom}')`);
290
- break;
291
- case 'inRange':
292
- filters.push(`Expr.dateIsAfter(${k}, '${dateFrom}') && Expr.dateIsBefore(${k}, '${dateTo}')`);
293
- break;
294
- case 'isToday':
295
- const now = new Date();
296
- const year = now.getFullYear();
297
- const month = (now.getMonth() + 1).toString().padStart(2, '0');
298
- const day = now.getDate().toString().padStart(2, '0');
299
- const todayStr = `${year}-${month}-${day} 00:00:00`;
300
- filters.push(`Expr.dateIsEqual(${k}, '${todayStr}')`);
301
- break;
302
- case 'blank':
303
- filters.push(`${k} == null`);
304
- break;
305
- case 'notBlank':
306
- filters.push(`${k} != null`);
307
- break;
208
+ const fragment = this.criteriaForSingleModel(k, this.currentFilterModel[k]);
209
+ if (fragment) {
210
+ filters.push(fragment);
211
+ }
212
+ });
213
+ // Multi filters (ag-multi-column-filter) wrap sub-filters as { filterType: 'multi',
214
+ // filterModels: [...] } - unwrap them so they contribute to CRITERIA_MATCH instead of
215
+ // being silently dropped.
216
+ this.getFiltersByType('multi').forEach((k) => {
217
+ var _a;
218
+ const subModels = (_a = this.currentFilterModel[k].filterModels) !== null && _a !== void 0 ? _a : [];
219
+ const operator = (this.currentFilterModel[k].operator || 'AND').toUpperCase();
220
+ const joiner = operator === 'OR' ? ' || ' : ' && ';
221
+ const fragments = subModels
222
+ .map((subModel) => this.criteriaForSingleModel(k, subModel))
223
+ .filter((f) => !!f);
224
+ if (fragments.length === 1) {
225
+ filters.push(fragments[0]);
226
+ }
227
+ else if (fragments.length > 1) {
228
+ filters.push(`(${fragments.join(joiner)})`);
308
229
  }
309
230
  });
310
231
  return filters;
311
232
  }
233
+ /**
234
+ * Builds a single criteria fragment for one filter model (flat per-column model or a
235
+ * multi-filter sub-model).
236
+ * @internal
237
+ */
238
+ criteriaForSingleModel(k, model) {
239
+ if (!model)
240
+ return null;
241
+ switch (model.filterType) {
242
+ case 'text':
243
+ return this.criteriaForTextModel(k, model);
244
+ case 'set':
245
+ return this.criteriaForSetModel(k, model);
246
+ case 'number':
247
+ return this.criteriaForNumberModel(k, model);
248
+ case 'date':
249
+ return this.criteriaForDateModel(k, model);
250
+ default:
251
+ return null;
252
+ }
253
+ }
254
+ criteriaForTextModel(k, model) {
255
+ const type = model.type;
256
+ const val = model.filter;
257
+ if (!val && (type === 'false' || type === 'true')) {
258
+ return `${k} == ${type}`;
259
+ }
260
+ const safeVal = val ? String(val).replace(/'/g, "\\'") : '';
261
+ switch (type) {
262
+ case 'blank':
263
+ return `(${k} == '' || ${k} == null)`;
264
+ case 'contains':
265
+ return `Expr.containsIgnoreCase(${k}, '${safeVal}')`;
266
+ case 'equals':
267
+ return `${k} == '${safeVal}'`;
268
+ case 'notBlank':
269
+ return `(${k} != '' && ${k} != null)`;
270
+ case 'notEqual':
271
+ return `${k} != '${safeVal}'`;
272
+ case 'wordStartsWith':
273
+ return `Expr.containsWordsStartingWithIgnoreCase(${k}, '${safeVal}')`;
274
+ default:
275
+ return null;
276
+ }
277
+ }
278
+ criteriaForSetModel(k, model) {
279
+ var _a, _b;
280
+ const selectedValues = ((_a = model.values) !== null && _a !== void 0 ? _a : []);
281
+ if (!selectedValues.length) {
282
+ // Nothing selected means no additional criteria
283
+ return null;
284
+ }
285
+ // Try to detect "all values selected" using VALID_VALUES from metadata.
286
+ // In that case we don't want to send any criteria, as it would be equivalent
287
+ // to no filter at all.
288
+ const colMeta = (_b = this.resourceColDefs) === null || _b === void 0 ? void 0 : _b.find((o) => o.NAME === k);
289
+ let allValues;
290
+ if (colMeta === null || colMeta === void 0 ? void 0 : colMeta.VALID_VALUES) {
291
+ try {
292
+ allValues = Array.isArray(colMeta.VALID_VALUES)
293
+ ? colMeta.VALID_VALUES
294
+ : colMeta.VALID_VALUES.split('|');
295
+ }
296
+ catch (_c) {
297
+ allValues = undefined;
298
+ }
299
+ }
300
+ if (allValues &&
301
+ allValues.length === selectedValues.length &&
302
+ allValues.every((v) => selectedValues.includes(v))) {
303
+ // All enum options are selected – skip adding criteria for this column
304
+ return null;
305
+ }
306
+ const orConditions = selectedValues.map((val) => {
307
+ const safeVal = String(val).replace(/'/g, "\\'");
308
+ return `${k} == '${safeVal}'`;
309
+ });
310
+ return orConditions.length ? `(${orConditions.join(' || ')})` : null;
311
+ }
312
+ criteriaForNumberModel(k, model) {
313
+ const value = model.filter;
314
+ const valueTwo = model.filterTo;
315
+ const isNum = (v) => v !== null && v !== '' && !isNaN(Number(v));
316
+ switch (model.type) {
317
+ case 'equals':
318
+ return isNum(value) ? `${k} == ${value}` : null;
319
+ case 'notEqual':
320
+ return isNum(value) ? `${k} != ${value}` : null;
321
+ case 'greaterThan':
322
+ return isNum(value) ? `${k} > ${value}` : null;
323
+ case 'greaterThanOrEqual':
324
+ return isNum(value) ? `${k} >= ${value}` : null;
325
+ case 'lessThan':
326
+ return isNum(value) ? `${k} < ${value}` : null;
327
+ case 'lessThanOrEqual':
328
+ return isNum(value) ? `${k} <= ${value}` : null;
329
+ case 'inRange':
330
+ return isNum(value) && isNum(valueTwo) ? `${k} >= ${value} && ${k} <= ${valueTwo}` : null;
331
+ case 'blank':
332
+ return `${k} == 0`;
333
+ case 'notBlank':
334
+ return `${k} != 0`;
335
+ default:
336
+ return null;
337
+ }
338
+ }
339
+ criteriaForDateModel(k, model) {
340
+ var _a, _b;
341
+ const dateFrom = (_a = model.dateFrom) === null || _a === void 0 ? void 0 : _a.replace(/-/g, '').replace('T', '-').split(' ')[0];
342
+ const dateTo = (_b = model.dateTo) === null || _b === void 0 ? void 0 : _b.replace(/-/g, '').replace('T', '-').split(' ')[0];
343
+ switch (model.type) {
344
+ case 'equals':
345
+ return `Expr.dateIsEqual(${k}, '${dateFrom}')`;
346
+ case 'lessThan':
347
+ return `Expr.dateIsBefore(${k}, '${dateFrom}')`;
348
+ case 'greaterThan':
349
+ return `Expr.dateIsAfter(${k}, '${dateFrom}')`;
350
+ case 'inRange':
351
+ return `Expr.dateIsAfter(${k}, '${dateFrom}') && Expr.dateIsBefore(${k}, '${dateTo}')`;
352
+ case 'isToday': {
353
+ const now = new Date();
354
+ const year = now.getFullYear();
355
+ const month = (now.getMonth() + 1).toString().padStart(2, '0');
356
+ const day = now.getDate().toString().padStart(2, '0');
357
+ return `Expr.dateIsEqual(${k}, '${year}-${month}-${day} 00:00:00')`;
358
+ }
359
+ case 'blank':
360
+ return `${k} == null`;
361
+ case 'notBlank':
362
+ return `${k} != null`;
363
+ default:
364
+ return null;
365
+ }
366
+ }
312
367
  getFiltersByType(filterType) {
313
368
  var _a;
314
369
  return Object.keys((_a = this.currentFilterModel) !== null && _a !== void 0 ? _a : {})
@@ -336,19 +391,22 @@ export class BaseServerSideDatasource {
336
391
  }
337
392
  return rowCount;
338
393
  }
339
- if (currentLastRowNumber === this.serverRowsCount && !this.moreRows) {
340
- rowCount = this.serverRowsCount;
394
+ // Infinite scroll: getRows() only resolves a block once the cumulative row cache covers
395
+ // its full extent or the stream reported no more data, so blocks are either full or final.
396
+ if (!this.moreRows) {
397
+ // Dataset ends here: rowData.size is the deduplicated loaded count. Deliberately not
398
+ // serverRowsCount (GSF's ROWS_COUNT ignores CRITERIA_MATCH) nor clientRowsCount
399
+ // (non-deduplicated running sum, overcounts on live streams).
400
+ rowCount = Math.min(this.rowData.size, this.maxView);
341
401
  }
342
- else if (currentLastRowNumber > this.maxView) {
343
- rowCount = Math.min(defaultCount, this.calculatedRowsCount);
402
+ else if (this.rowData.size >= this.maxView) {
403
+ // Hard cap - the view can't grow past maxView.
404
+ rowCount = this.maxView;
344
405
  }
345
- else if (!this.moreRows && currentLastRowNumber > this.calculatedRowsCount) {
346
- // No more data from server and request is beyond what has been counted.
347
- // Return the actual loaded count so AG Grid knows the dataset ends here.
348
- rowCount = this.clientRowsCount || this.calculatedRowsCount;
349
- }
350
- else if (!this.moreRows && this.serverRowsCount > this.calculatedRowsCount) {
351
- rowCount = this.calculatedRowsCount;
406
+ else {
407
+ // Full block, more may follow: leave undefined so AG Grid keeps requesting blocks
408
+ // and shows an open-ended "100+"-style count.
409
+ rowCount = undefined;
352
410
  }
353
411
  return rowCount;
354
412
  }