@genesislcap/grid-pro 15.20.2 → 15.21.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.
- package/dist/custom-elements.json +2161 -163
- package/dist/dts/datasource/base.datasource.d.ts +17 -3
- package/dist/dts/datasource/base.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/base.types.d.ts +1 -1
- package/dist/dts/datasource/base.types.d.ts.map +1 -1
- package/dist/dts/datasource/datasource.types.d.ts +5 -2
- package/dist/dts/datasource/datasource.types.d.ts.map +1 -1
- package/dist/dts/datasource/filter.utils.d.ts +40 -0
- package/dist/dts/datasource/filter.utils.d.ts.map +1 -0
- package/dist/dts/datasource/index.d.ts +2 -0
- package/dist/dts/datasource/index.d.ts.map +1 -1
- package/dist/dts/datasource/infinite.datasource.d.ts +464 -0
- package/dist/dts/datasource/infinite.datasource.d.ts.map +1 -0
- package/dist/dts/datasource/infinite.resource.d.ts +231 -0
- package/dist/dts/datasource/infinite.resource.d.ts.map +1 -0
- package/dist/dts/datasource/server-side.datasource.d.ts +0 -2
- package/dist/dts/datasource/server-side.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
- package/dist/dts/grid-pro-beta.d.ts +37 -2
- package/dist/dts/grid-pro-beta.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts +20 -1
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts +0 -1
- package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts.map +1 -1
- package/dist/dts/grid-pro.d.ts +6 -0
- package/dist/dts/grid-pro.d.ts.map +1 -1
- package/dist/dts/react.d.ts +25 -1
- package/dist/dts/utils/map.d.ts +2 -2
- package/dist/dts/utils/map.d.ts.map +1 -1
- package/dist/esm/datasource/base.datasource.js +39 -2
- package/dist/esm/datasource/filter.utils.js +241 -0
- package/dist/esm/datasource/index.js +2 -0
- package/dist/esm/datasource/infinite.datasource.js +373 -0
- package/dist/esm/datasource/infinite.resource.js +581 -0
- package/dist/esm/datasource/server-side.datasource.js +0 -38
- package/dist/esm/datasource/server-side.grid-definitions.js +1 -1
- package/dist/esm/datasource/server-side.resource-base.js +7 -105
- package/dist/esm/grid-pro-beta.js +73 -6
- package/dist/esm/grid-pro-genesis-datasource/datasource-events.types.js +3 -0
- package/dist/esm/grid-pro-genesis-datasource/grid-pro-genesis-datasource.js +0 -3
- package/dist/esm/grid-pro.js +12 -1
- package/dist/grid-pro.api.json +1355 -477
- package/dist/grid-pro.d.ts +962 -165
- package/dist/react.cjs +84 -0
- package/dist/react.mjs +83 -0
- package/package.json +13 -13
|
@@ -525,8 +525,45 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
|
|
|
525
525
|
changes: mappedTransaction,
|
|
526
526
|
});
|
|
527
527
|
}
|
|
528
|
-
|
|
529
|
-
|
|
528
|
+
/**
|
|
529
|
+
* Builds the index map used for server-side sorting from the resource's metadata.
|
|
530
|
+
* @remarks Real DATASERVER indexes keep their names; when the resource also (or only) reports
|
|
531
|
+
* SORTABLE_FIELDS - the synthetic bucket a req/rep resource uses for its sortable columns -
|
|
532
|
+
* those fields are merged in under the synthetic `SORTABLE_FIELDS` key. Note an index with no
|
|
533
|
+
* NAME lands under `undefined`: its fields count as indexed, but it cannot be named in an
|
|
534
|
+
* ORDER_BY, so sorting-related consumers must consider named entries only.
|
|
535
|
+
* @internal
|
|
536
|
+
*/
|
|
537
|
+
getResourceIndexes(availableIndexes, availableSortableFields) {
|
|
538
|
+
const resourceIndexesMap = new Map();
|
|
539
|
+
const allSortableFields = new Set();
|
|
540
|
+
// Add indexes from INDEXES metadata
|
|
541
|
+
availableIndexes === null || availableIndexes === void 0 ? void 0 : availableIndexes.forEach((index) => {
|
|
542
|
+
const fields = index.FIELDS.split(' ');
|
|
543
|
+
resourceIndexesMap.set(index.NAME, fields);
|
|
544
|
+
fields.forEach((field) => allSortableFields.add(field));
|
|
545
|
+
});
|
|
546
|
+
// Add fields from SORTABLE_FIELDS metadata if available
|
|
547
|
+
if (availableSortableFields && availableSortableFields.length > 0) {
|
|
548
|
+
availableSortableFields.forEach((field) => allSortableFields.add(field));
|
|
549
|
+
}
|
|
550
|
+
// If we have SORTABLE_FIELDS but no indexes, create a synthetic index entry
|
|
551
|
+
if (resourceIndexesMap.size === 0 && allSortableFields.size > 0) {
|
|
552
|
+
resourceIndexesMap.set('SORTABLE_FIELDS', Array.from(allSortableFields));
|
|
553
|
+
}
|
|
554
|
+
else if (availableSortableFields &&
|
|
555
|
+
availableSortableFields.length > 0 &&
|
|
556
|
+
resourceIndexesMap.size > 0) {
|
|
557
|
+
const fieldsFromIndexes = new Set();
|
|
558
|
+
resourceIndexesMap.forEach((fields) => {
|
|
559
|
+
fields.forEach((field) => fieldsFromIndexes.add(field));
|
|
560
|
+
});
|
|
561
|
+
const additionalFields = availableSortableFields.filter((field) => !fieldsFromIndexes.has(field));
|
|
562
|
+
if (additionalFields.length > 0) {
|
|
563
|
+
resourceIndexesMap.set('SORTABLE_FIELDS', additionalFields);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return resourceIndexesMap;
|
|
530
567
|
}
|
|
531
568
|
/**
|
|
532
569
|
* Maps the transaction data to the row data mapper function, if it exists.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filter utilities for converting AG Grid filter models to Genesis criteria strings.
|
|
3
|
+
* The single implementation of the filter-model grammar: the Infinite Row Model converts whole
|
|
4
|
+
* models through {@link convertFilterModelToCriteria}, and the Server-Side Row Model's
|
|
5
|
+
* `BaseServerSideDatasource.criteriaFor*Model` methods delegate to the per-type converters, so
|
|
6
|
+
* both row models send identical CRITERIA_MATCH for the same gesture.
|
|
7
|
+
* @beta
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Escapes a value for interpolation into a criteria string literal.
|
|
11
|
+
* @remarks Backslashes first, then quotes: an unescaped quote would end the literal early, and
|
|
12
|
+
* an unescaped trailing backslash (or a literal `\'` in the value) would escape the closing
|
|
13
|
+
* quote - either way the server rejects the whole expression.
|
|
14
|
+
*/
|
|
15
|
+
function escapeCriteriaValue(value) {
|
|
16
|
+
return String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Converts text filters to Genesis criteria strings.
|
|
20
|
+
* @param filterModel - The AG Grid filter model
|
|
21
|
+
* @param fieldName - The field name being filtered
|
|
22
|
+
* @returns Genesis criteria string or null
|
|
23
|
+
*/
|
|
24
|
+
export function convertTextFilterToCriteria(filterModel, fieldName) {
|
|
25
|
+
if (!filterModel.filter && (filterModel.type === 'false' || filterModel.type === 'true')) {
|
|
26
|
+
return `${fieldName} == ${filterModel.type}`;
|
|
27
|
+
}
|
|
28
|
+
const safeVal = filterModel.filter != null ? escapeCriteriaValue(filterModel.filter) : '';
|
|
29
|
+
switch (filterModel.type) {
|
|
30
|
+
case 'blank':
|
|
31
|
+
// Parenthesised: fragments are joined with ' && ', and && binds tighter than ||, so a
|
|
32
|
+
// bare `a == '' || a == null && b == 'x'` would let empty-a rows bypass b's filter.
|
|
33
|
+
return `(${fieldName} == '' || ${fieldName} == null)`;
|
|
34
|
+
case 'contains':
|
|
35
|
+
return `Expr.containsIgnoreCase(${fieldName}, '${safeVal}')`;
|
|
36
|
+
case 'equals':
|
|
37
|
+
return `${fieldName} == '${safeVal}'`;
|
|
38
|
+
case 'notBlank':
|
|
39
|
+
return `(${fieldName} != '' && ${fieldName} != null)`;
|
|
40
|
+
case 'notEqual':
|
|
41
|
+
return `${fieldName} != '${safeVal}'`;
|
|
42
|
+
case 'wordStartsWith':
|
|
43
|
+
return `Expr.containsWordsStartingWithIgnoreCase(${fieldName}, '${safeVal}')`;
|
|
44
|
+
default:
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Converts set filters (for enum fields) to Genesis criteria strings.
|
|
50
|
+
* @param filterModel - The AG Grid filter model
|
|
51
|
+
* @param fieldName - The field name being filtered
|
|
52
|
+
* @param colMeta - Column metadata for detecting all values
|
|
53
|
+
* @returns Genesis criteria string or null
|
|
54
|
+
*/
|
|
55
|
+
export function convertSetFilterToCriteria(filterModel, fieldName, colMeta) {
|
|
56
|
+
var _a;
|
|
57
|
+
const selectedValues = ((_a = filterModel === null || filterModel === void 0 ? void 0 : filterModel.values) !== null && _a !== void 0 ? _a : []);
|
|
58
|
+
if (!selectedValues || selectedValues.length === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Try to detect "all values selected" using VALID_VALUES from metadata.
|
|
62
|
+
// In that case we don't want to send any criteria, as it would be equivalent
|
|
63
|
+
// to no filter at all.
|
|
64
|
+
let allValues;
|
|
65
|
+
if (colMeta === null || colMeta === void 0 ? void 0 : colMeta.VALID_VALUES) {
|
|
66
|
+
try {
|
|
67
|
+
allValues = Array.isArray(colMeta.VALID_VALUES)
|
|
68
|
+
? colMeta.VALID_VALUES
|
|
69
|
+
: colMeta.VALID_VALUES.split('|');
|
|
70
|
+
}
|
|
71
|
+
catch (_b) {
|
|
72
|
+
allValues = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (allValues &&
|
|
76
|
+
allValues.length === selectedValues.length &&
|
|
77
|
+
allValues.every((v) => selectedValues.includes(v))) {
|
|
78
|
+
// All enum options are selected – skip adding criteria for this column
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const orConditions = selectedValues.map((val) => {
|
|
82
|
+
return `${fieldName} == '${escapeCriteriaValue(val)}'`;
|
|
83
|
+
});
|
|
84
|
+
if (orConditions.length > 0) {
|
|
85
|
+
return `(${orConditions.join(' || ')})`;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Converts number filters to Genesis criteria strings.
|
|
91
|
+
* @param filterModel - The AG Grid filter model
|
|
92
|
+
* @param fieldName - The field name being filtered
|
|
93
|
+
* @returns Genesis criteria string or null
|
|
94
|
+
*/
|
|
95
|
+
export function convertNumberFilterToCriteria(filterModel, fieldName) {
|
|
96
|
+
const value = filterModel.filter;
|
|
97
|
+
const valueTwo = filterModel.filterTo;
|
|
98
|
+
// Not bare isNaN: isNaN(null) and isNaN('') are both false, so an absent value would be
|
|
99
|
+
// interpolated as-is and produce criteria the server rejects.
|
|
100
|
+
const isNum = (v) => v !== null && v !== '' && !isNaN(Number(v));
|
|
101
|
+
switch (filterModel.type) {
|
|
102
|
+
case 'equals':
|
|
103
|
+
return isNum(value) ? `${fieldName} == ${value}` : null;
|
|
104
|
+
case 'notEqual':
|
|
105
|
+
return isNum(value) ? `${fieldName} != ${value}` : null;
|
|
106
|
+
case 'greaterThan':
|
|
107
|
+
return isNum(value) ? `${fieldName} > ${value}` : null;
|
|
108
|
+
case 'greaterThanOrEqual':
|
|
109
|
+
return isNum(value) ? `${fieldName} >= ${value}` : null;
|
|
110
|
+
case 'lessThan':
|
|
111
|
+
return isNum(value) ? `${fieldName} < ${value}` : null;
|
|
112
|
+
case 'lessThanOrEqual':
|
|
113
|
+
return isNum(value) ? `${fieldName} <= ${value}` : null;
|
|
114
|
+
case 'inRange':
|
|
115
|
+
return isNum(value) && isNum(valueTwo)
|
|
116
|
+
? `${fieldName} >= ${value} && ${fieldName} <= ${valueTwo}`
|
|
117
|
+
: null;
|
|
118
|
+
case 'blank':
|
|
119
|
+
// Blank means absent, not zero: a legitimate 0 must not report as blank, and a null
|
|
120
|
+
// value must. Matches the text and date converters.
|
|
121
|
+
return `${fieldName} == null`;
|
|
122
|
+
case 'notBlank':
|
|
123
|
+
return `${fieldName} != null`;
|
|
124
|
+
default:
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Converts date filters to Genesis criteria strings.
|
|
130
|
+
* @param filterModel - The AG Grid filter model
|
|
131
|
+
* @param fieldName - The field name being filtered
|
|
132
|
+
* @returns Genesis criteria string or null
|
|
133
|
+
*/
|
|
134
|
+
export function convertDateFilterToCriteria(filterModel, fieldName) {
|
|
135
|
+
// AG Grid supplies bounds as '2026-09-02 00:00:00'; normalise to the 'yyyyMMdd' form the
|
|
136
|
+
// Expr.dateIs* helpers take. An absent bound yields no criteria rather than interpolating
|
|
137
|
+
// the string 'undefined' - reachable through setFilterModel, e.g. a restored filter config.
|
|
138
|
+
const normalise = (bound) => bound ? String(bound).replace(/-/g, '').replace('T', '-').split(' ')[0] : null;
|
|
139
|
+
const dateFrom = normalise(filterModel.dateFrom);
|
|
140
|
+
const dateTo = normalise(filterModel.dateTo);
|
|
141
|
+
switch (filterModel.type) {
|
|
142
|
+
case 'equals':
|
|
143
|
+
return dateFrom ? `Expr.dateIsEqual(${fieldName}, '${dateFrom}')` : null;
|
|
144
|
+
case 'lessThan':
|
|
145
|
+
return dateFrom ? `Expr.dateIsBefore(${fieldName}, '${dateFrom}')` : null;
|
|
146
|
+
case 'greaterThan':
|
|
147
|
+
return dateFrom ? `Expr.dateIsAfter(${fieldName}, '${dateFrom}')` : null;
|
|
148
|
+
case 'inRange':
|
|
149
|
+
return dateFrom && dateTo
|
|
150
|
+
? `Expr.dateIsAfter(${fieldName}, '${dateFrom}') && Expr.dateIsBefore(${fieldName}, '${dateTo}')`
|
|
151
|
+
: null;
|
|
152
|
+
case 'isToday': {
|
|
153
|
+
// Same 'yyyyMMdd' shape as the normalised bounds above: 'equals' on today's date and
|
|
154
|
+
// 'isToday' must send identical criteria.
|
|
155
|
+
const now = new Date();
|
|
156
|
+
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
|
157
|
+
const day = now.getDate().toString().padStart(2, '0');
|
|
158
|
+
return `Expr.dateIsEqual(${fieldName}, '${now.getFullYear()}${month}${day}')`;
|
|
159
|
+
}
|
|
160
|
+
case 'blank':
|
|
161
|
+
return `${fieldName} == null`;
|
|
162
|
+
case 'notBlank':
|
|
163
|
+
return `${fieldName} != null`;
|
|
164
|
+
default:
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Converts one flat filter model (or one multi-filter sub-model) to a criteria fragment.
|
|
170
|
+
* @param filter - The filter model
|
|
171
|
+
* @param fieldName - The field name being filtered
|
|
172
|
+
* @param colDefs - Column definitions for metadata (set filters)
|
|
173
|
+
* @returns Genesis criteria string or null
|
|
174
|
+
*/
|
|
175
|
+
function convertSingleFilterToCriteria(filter, fieldName, colDefs) {
|
|
176
|
+
switch (filter === null || filter === void 0 ? void 0 : filter.filterType) {
|
|
177
|
+
case 'text':
|
|
178
|
+
return convertTextFilterToCriteria(filter, fieldName);
|
|
179
|
+
case 'set': {
|
|
180
|
+
const colMeta = colDefs === null || colDefs === void 0 ? void 0 : colDefs.find((o) => o.NAME === fieldName);
|
|
181
|
+
return convertSetFilterToCriteria(filter, fieldName, colMeta);
|
|
182
|
+
}
|
|
183
|
+
case 'number':
|
|
184
|
+
return convertNumberFilterToCriteria(filter, fieldName);
|
|
185
|
+
case 'date':
|
|
186
|
+
return convertDateFilterToCriteria(filter, fieldName);
|
|
187
|
+
default:
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Processes a single field's filter and returns its criteria fragment.
|
|
193
|
+
* @remarks Multi-filters ({ filterType: 'multi', filterModels: [...] }) are unwrapped honouring
|
|
194
|
+
* their `operator`: an OR multi-filter joins its sub-fragments with `||` and is parenthesised,
|
|
195
|
+
* so the outer ` && ` join between fields cannot rebind it.
|
|
196
|
+
* @param filterEntry - The filter entry for the field
|
|
197
|
+
* @param fieldName - The field name
|
|
198
|
+
* @param colDefs - Column definitions
|
|
199
|
+
* @returns The criteria fragment for the field, or null
|
|
200
|
+
*/
|
|
201
|
+
function processFieldFilter(filterEntry, fieldName, colDefs) {
|
|
202
|
+
if (!filterEntry)
|
|
203
|
+
return null;
|
|
204
|
+
if (filterEntry.filterType === 'multi' && Array.isArray(filterEntry.filterModels)) {
|
|
205
|
+
const operator = (filterEntry.operator || 'AND').toUpperCase();
|
|
206
|
+
const joiner = operator === 'OR' ? ' || ' : ' && ';
|
|
207
|
+
const fragments = filterEntry.filterModels
|
|
208
|
+
.map((subModel) => convertSingleFilterToCriteria(subModel, fieldName, colDefs))
|
|
209
|
+
.filter((fragment) => Boolean(fragment));
|
|
210
|
+
if (fragments.length === 0)
|
|
211
|
+
return null;
|
|
212
|
+
return fragments.length === 1 ? fragments[0] : `(${fragments.join(joiner)})`;
|
|
213
|
+
}
|
|
214
|
+
return convertSingleFilterToCriteria(filterEntry, fieldName, colDefs);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Converts AG Grid filter model to Genesis criteria string.
|
|
218
|
+
* This is the main entry point for converting filters.
|
|
219
|
+
* Handles both simple filters and multi-filters (filterType: "multi" with filterModels array).
|
|
220
|
+
* @param filterModel - The AG Grid filter model
|
|
221
|
+
* @param colDefs - Column definitions for metadata (optional but recommended)
|
|
222
|
+
* @returns Genesis criteria string
|
|
223
|
+
*/
|
|
224
|
+
export function convertFilterModelToCriteria(filterModel, colDefs) {
|
|
225
|
+
if (!filterModel || Object.keys(filterModel).length === 0) {
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
const filters = [];
|
|
229
|
+
// Process each field in the filter model
|
|
230
|
+
for (const fieldName of Object.keys(filterModel)) {
|
|
231
|
+
// Skip if column not in colDefs (when colDefs provided)
|
|
232
|
+
if (colDefs && colDefs.findIndex((o) => o.NAME === fieldName) === -1) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const fieldCriteria = processFieldFilter(filterModel[fieldName], fieldName, colDefs);
|
|
236
|
+
if (fieldCriteria) {
|
|
237
|
+
filters.push(fieldCriteria);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return filters.join(' && ');
|
|
241
|
+
}
|
|
@@ -3,6 +3,8 @@ export * from './base.types';
|
|
|
3
3
|
export * from './client-side.datasource';
|
|
4
4
|
export * from './error-handler.dialog';
|
|
5
5
|
export * from './datasource.types';
|
|
6
|
+
export * from './filter.utils';
|
|
7
|
+
export * from './infinite.datasource';
|
|
6
8
|
export * from './row-match.types';
|
|
7
9
|
export * from './row-match.tracker';
|
|
8
10
|
export * from './row-match.classes';
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { __awaiter, __decorate } from "tslib";
|
|
2
|
+
import { Connect, ConnectEvents, Datasource, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
|
|
3
|
+
import { LifecycleMixin } from '@genesislcap/foundation-utils';
|
|
4
|
+
import { customElement, DOM, observable } from '@microsoft/fast-element';
|
|
5
|
+
import { gridProGenesisDatasourceEventNames } from '../grid-pro-genesis-datasource';
|
|
6
|
+
import { datasourceEventNames, } from '../grid-pro-genesis-datasource/datasource-events.types';
|
|
7
|
+
import { logger } from '../utils';
|
|
8
|
+
import { GridProBaseDatasource } from './base.datasource';
|
|
9
|
+
import { GenesisInfiniteDatasource } from './infinite.resource';
|
|
10
|
+
import { getFilterByFieldType, getServerSideFilterParamsByFieldType, } from './server-side.grid-definitions';
|
|
11
|
+
const criteriaDelimiter = ';';
|
|
12
|
+
/**
|
|
13
|
+
* A Genesis Datasource element for AG Grid's Infinite Row Model.
|
|
14
|
+
* @remarks
|
|
15
|
+
* The Infinite Row Model is part of AG Grid Community (free) and provides:
|
|
16
|
+
* - Lazy loading of data as user scrolls
|
|
17
|
+
* - Server-side sorting and filtering
|
|
18
|
+
* - Simple infinite scroll without grouping/aggregation
|
|
19
|
+
* - Live updates for DATASERVER resources: the DATA_LOGON stream the grid pages over pushes
|
|
20
|
+
* every change, so no flag or second subscription is needed
|
|
21
|
+
*
|
|
22
|
+
* REQUEST_SERVER resources are read on demand only - a change on the server shows up when the
|
|
23
|
+
* grid re-reads (scroll, filter/sort change, or an explicit refresh), not live. The exception
|
|
24
|
+
* is a write made through this session: a commit ACK for an event listed in `pollTriggerEvents`
|
|
25
|
+
* re-reads the loaded blocks, so entity-management CRUD is reflected immediately.
|
|
26
|
+
*
|
|
27
|
+
* Unlike the other datasources, this element deliberately does not consume `deferredGridOptions`
|
|
28
|
+
* (the mechanism is being retired): grid options set there - including via entity-management's
|
|
29
|
+
* `gridOptions` with `datasource-type="infinite"` - are not applied. Set options on the grid
|
|
30
|
+
* element itself instead.
|
|
31
|
+
*
|
|
32
|
+
* Use this instead of Server-Side Row Model when you don't need Enterprise features
|
|
33
|
+
* like row grouping, aggregation, or pivoting.
|
|
34
|
+
*
|
|
35
|
+
* @see https://www.ag-grid.com/javascript-data-grid/infinite-scrolling/
|
|
36
|
+
* @beta
|
|
37
|
+
*
|
|
38
|
+
* @fires base-datasource-error - Fired when a datasource error is reported. detail: `BaseDatasourceErrorEventDetail`
|
|
39
|
+
* @fires datasource-error - Fired when a datasource error occurs (for grid integration). detail: `DatasourceErrorEventDetail`
|
|
40
|
+
* @fires base-datasource-connected - Fired when error state is cleared after connection succeeds
|
|
41
|
+
* @fires datasource-initialize - Fired to hand off infinite row model grid options. detail: `InitializeEventDetail`
|
|
42
|
+
* @fires datasource-init - Fired when the infinite grid model should initialize data
|
|
43
|
+
* @fires datasource-schema-updated - Fired when column metadata or defs are updated. detail: `SchemaUpdatedEventDetail`
|
|
44
|
+
* @fires set-infinite-datasource - Fired to attach or clear the infinite row model datasource. detail: `SetInfiniteDatasourceEventDetail`
|
|
45
|
+
* @fires refresh-infinite-cache - Fired to request an infinite row model refresh; `purge` drops the block cache and re-reads. detail: `RefreshInfiniteCacheEventDetail`
|
|
46
|
+
* @fires cache-filter-config - Fired to persist filter configuration for the grid
|
|
47
|
+
* @fires datasource-data-cleared - Fired when infinite row data is cleared. detail: `DataClearedEventDetail`
|
|
48
|
+
* @fires datasource-filters-restored - Fired when persisted filters are reapplied
|
|
49
|
+
*/
|
|
50
|
+
let GridProInfiniteDatasource = class GridProInfiniteDatasource extends LifecycleMixin(GridProBaseDatasource) {
|
|
51
|
+
constructor() {
|
|
52
|
+
super(...arguments);
|
|
53
|
+
this.request = {};
|
|
54
|
+
/** @internal */
|
|
55
|
+
this.onDatasourceReady = (_event) => __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
const initOK = yield this.initializeDatasource(this.datasourceOptions(), true, false);
|
|
57
|
+
if (!initOK) {
|
|
58
|
+
logger.debug(`Genesis Datasource init failed for ${this.resourceName}`);
|
|
59
|
+
this.clearRowData();
|
|
60
|
+
this.setDisconnected(true);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.$emit(gridProGenesisDatasourceEventNames.dataInit);
|
|
64
|
+
this.setDisconnected(false);
|
|
65
|
+
this.indexes = this.getResourceIndexes(this.datasource.availableIndexes, this.datasource.availableSortableFields);
|
|
66
|
+
const fieldMetadata = toFieldMetadata(this.datasource.originalFieldDef);
|
|
67
|
+
const agColumnDefs = yield this.getAgColumnDefs(fieldMetadata);
|
|
68
|
+
// Emit event to set column definitions
|
|
69
|
+
this.$emit(datasourceEventNames.schemaUpdated, {
|
|
70
|
+
schema: agColumnDefs,
|
|
71
|
+
metadata: fieldMetadata,
|
|
72
|
+
});
|
|
73
|
+
// Create the infinite datasource
|
|
74
|
+
this.infiniteDatasource = new GenesisInfiniteDatasource({
|
|
75
|
+
createSnapshotFunc: this.createSnapshot.bind(this),
|
|
76
|
+
createDataserverStreamFunc: this.createDataserverStream.bind(this),
|
|
77
|
+
// VIEW_NUMBER 0 means "pagination disabled": the server continues from the cursor it
|
|
78
|
+
// holds on the last row it delivered, rather than re-seeking by offset.
|
|
79
|
+
getMoreRowsFunc: (sourceRef) => this.connect.getMoreRows(sourceRef, 0),
|
|
80
|
+
// A pushed INSERT/DELETE changes which rows exist or their order, so the rows are
|
|
81
|
+
// re-read (purge); a pushed MODIFY is already patched into the cache, so the grid only
|
|
82
|
+
// needs to re-render its blocks.
|
|
83
|
+
onRowsInvalidatedFunc: () => this.$emit(datasourceEventNames.refreshInfiniteCache, {
|
|
84
|
+
purge: true,
|
|
85
|
+
}),
|
|
86
|
+
onRowsUpdatedFunc: () => this.$emit(datasourceEventNames.refreshInfiniteCache, {
|
|
87
|
+
purge: false,
|
|
88
|
+
}),
|
|
89
|
+
errorHandlerFunc: this.handleErrors.bind(this),
|
|
90
|
+
resourceName: this.resourceName,
|
|
91
|
+
resourceParams: this.isRequestServer
|
|
92
|
+
? this.datasource.requestOnlyParams
|
|
93
|
+
: this.datasource.dataserverOnlyParams,
|
|
94
|
+
resourceIndexes: this.indexes,
|
|
95
|
+
resourceColDefs: this.datasource.originalFieldDef,
|
|
96
|
+
maxRows: +this.maxRows,
|
|
97
|
+
rowId: this.rowId,
|
|
98
|
+
baseCriteria: this.criteria,
|
|
99
|
+
isRequestServer: this.isRequestServer,
|
|
100
|
+
});
|
|
101
|
+
// Emit event to set infinite datasource
|
|
102
|
+
this.$emit(datasourceEventNames.setInfiniteDatasource, {
|
|
103
|
+
datasource: this.infiniteDatasource,
|
|
104
|
+
});
|
|
105
|
+
// DATASERVER needs no live-update setup: it is live through the paging DATA_LOGON stream
|
|
106
|
+
// itself (GenesisInfiniteDatasource reports pushes via onRowsInvalidated/onRowsUpdated).
|
|
107
|
+
// REQUEST_SERVER is read on demand, but a write committed through this session should not
|
|
108
|
+
// wait for the user to scroll or reload:
|
|
109
|
+
this.setupCommitTriggeredRefresh();
|
|
110
|
+
// Emit event to restore cached filter config
|
|
111
|
+
this.$emit(datasourceEventNames.filtersRestored);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
resourceNameChanged(oldValue, newValue) {
|
|
115
|
+
if (!oldValue || oldValue === newValue)
|
|
116
|
+
return;
|
|
117
|
+
DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
yield this.restart();
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
criteriaChanged(oldCriteria, newCriteria) {
|
|
122
|
+
const criteriaIsNotDuplicate = oldCriteria !== normaliseCriteria(newCriteria, criteriaDelimiter);
|
|
123
|
+
if (this.infiniteDatasource && criteriaIsNotDuplicate) {
|
|
124
|
+
DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
yield this.reloadResourceData();
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
connectedCallback() {
|
|
130
|
+
super.connectedCallback();
|
|
131
|
+
const shouldRunConnect = this.shouldRunConnect;
|
|
132
|
+
DOM.queueUpdate(() => {
|
|
133
|
+
if (!shouldRunConnect)
|
|
134
|
+
return;
|
|
135
|
+
this.init();
|
|
136
|
+
this.subscribeToConnection();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
disconnectedCallback() {
|
|
140
|
+
super.disconnectedCallback();
|
|
141
|
+
const shouldRunDisconnect = this.shouldRunDisconnect;
|
|
142
|
+
DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
|
|
143
|
+
if (!shouldRunDisconnect)
|
|
144
|
+
return;
|
|
145
|
+
yield this.destroy();
|
|
146
|
+
this.unsubscribeFromConnection();
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
deepClone() {
|
|
150
|
+
const copy = super.deepClone();
|
|
151
|
+
copy.deferredColumnStates = structuredClone(this.deferredColumnStates);
|
|
152
|
+
return copy;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resets the grid data while keeping columnDefs and reloads data.
|
|
156
|
+
* @remarks This is used when the grid is already initialized and we want to reload the data due to a criteria/filter change.
|
|
157
|
+
* @beta
|
|
158
|
+
*/
|
|
159
|
+
reloadResourceData() {
|
|
160
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
161
|
+
var _a, _b;
|
|
162
|
+
// The paging DATA_LOGON is opened through connect.stream, not this.datasource, so destroying
|
|
163
|
+
// the comms datasource below does not close it. Dispose the IDatasource explicitly or every
|
|
164
|
+
// criteria change (e.g. a bound search box) leaks a live subscription and its block timers.
|
|
165
|
+
(_b = (_a = this.infiniteDatasource) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
166
|
+
this.infiniteDatasource = undefined;
|
|
167
|
+
this.datasource.destroy();
|
|
168
|
+
// Emit event to cache current filter model before clearing data
|
|
169
|
+
this.$emit(datasourceEventNames.cacheFilterConfig);
|
|
170
|
+
this.clearRowData(false);
|
|
171
|
+
this.setDisconnected(false);
|
|
172
|
+
// Purge the infinite cache (clears all data and refetches)
|
|
173
|
+
this.$emit(datasourceEventNames.refreshInfiniteCache, {
|
|
174
|
+
purge: true,
|
|
175
|
+
});
|
|
176
|
+
yield this.init();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
init() {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
// Emit event to initialize grid with infinite row model options
|
|
182
|
+
const gridOptions = {
|
|
183
|
+
getRowId: (params) => {
|
|
184
|
+
if (!params.data) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return String(params.data[this.rowId]);
|
|
188
|
+
},
|
|
189
|
+
cacheBlockSize: this.maxRows,
|
|
190
|
+
defaultColDef: {
|
|
191
|
+
filter: true,
|
|
192
|
+
resizable: true,
|
|
193
|
+
// Off by default, like the SSRM options: getAgColumnDefs opts individual columns in, so
|
|
194
|
+
// columns from other sources (grid-pro-column templates, entity-management's action
|
|
195
|
+
// column) don't render a sort control the server cannot honour.
|
|
196
|
+
sortable: false,
|
|
197
|
+
unSortIcon: true,
|
|
198
|
+
},
|
|
199
|
+
rowBuffer: 0,
|
|
200
|
+
rowModelType: 'infinite',
|
|
201
|
+
// convertSortModelToOrderBy reads only sortModel[0], so a multi-sort's extra columns
|
|
202
|
+
// would paint indicators whose direction is silently discarded.
|
|
203
|
+
suppressMultiSort: true,
|
|
204
|
+
// Infinite Row Model specific options
|
|
205
|
+
cacheOverflowSize: 2,
|
|
206
|
+
maxConcurrentDatasourceRequests: 1,
|
|
207
|
+
infiniteInitialRowCount: 1,
|
|
208
|
+
maxBlocksInCache: 10,
|
|
209
|
+
};
|
|
210
|
+
this.$emit(datasourceEventNames.initialize, {
|
|
211
|
+
options: gridOptions,
|
|
212
|
+
keepColDefsOnClearRowData: this.keepColDefsOnClearRowData,
|
|
213
|
+
});
|
|
214
|
+
// Listen for grid ready event. Clear any listener a previous init() left pending first:
|
|
215
|
+
// two quick criteria changes would otherwise stack two once-listeners, and the single
|
|
216
|
+
// ready event would then run the whole initialization twice.
|
|
217
|
+
this.clearReadyListener();
|
|
218
|
+
this.addEventListener(datasourceEventNames.ready, this.onDatasourceReady, {
|
|
219
|
+
once: true,
|
|
220
|
+
});
|
|
221
|
+
if (!this.resourceName) {
|
|
222
|
+
this.handleErrors('Application not connected or invalid resource name, datasource will not work.', 'unknown');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/** @internal */
|
|
227
|
+
clearReadyListener() {
|
|
228
|
+
this.removeEventListener(datasourceEventNames.ready, this.onDatasourceReady);
|
|
229
|
+
}
|
|
230
|
+
destroy() {
|
|
231
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
var _a, _b;
|
|
233
|
+
// A ready event arriving after teardown must not re-run the initialization.
|
|
234
|
+
this.clearReadyListener();
|
|
235
|
+
this.teardownCommitTriggeredRefresh();
|
|
236
|
+
this.datasource.destroy();
|
|
237
|
+
this.clearRowData();
|
|
238
|
+
if (this.infiniteDatasource) {
|
|
239
|
+
(_b = (_a = this.infiniteDatasource).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
240
|
+
this.infiniteDatasource = undefined;
|
|
241
|
+
}
|
|
242
|
+
// Emit events instead of direct grid access
|
|
243
|
+
this.$emit(datasourceEventNames.setInfiniteDatasource, {
|
|
244
|
+
datasource: null,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
restart() {
|
|
249
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
+
yield this.destroy();
|
|
251
|
+
yield this.init();
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
clearRowData(withColumnDefs = true) {
|
|
255
|
+
this.rowData = new Map();
|
|
256
|
+
if (withColumnDefs) {
|
|
257
|
+
// Emit event to clear column definitions
|
|
258
|
+
this.$emit(datasourceEventNames.dataCleared, {
|
|
259
|
+
includeSchema: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
this.transactionData = { remove: [] };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Honours `pollTriggerEvents` without polling: a commit ACK for one of the listed events
|
|
266
|
+
* means a write went through this session, so the loaded blocks are re-read in place - the
|
|
267
|
+
* same action as `applyTransaction`, which keeps the grid's row count and scroll position.
|
|
268
|
+
* @remarks REQUEST_SERVER only. DATASERVER already receives every change on the paging
|
|
269
|
+
* subscription, so a commit-triggered re-read there would just duplicate the live push.
|
|
270
|
+
* @internal
|
|
271
|
+
*/
|
|
272
|
+
setupCommitTriggeredRefresh() {
|
|
273
|
+
var _a;
|
|
274
|
+
this.teardownCommitTriggeredRefresh();
|
|
275
|
+
if (this.isSnapshot || !this.isRequestServer || !((_a = this.pollTriggerEvents) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.commitResponseUnsubscribe = this.connectEvents.addCommitResponseListener((event) => {
|
|
279
|
+
var _a;
|
|
280
|
+
const { eventName, response } = event.detail;
|
|
281
|
+
if (this.pollTriggerEvents.includes(eventName) && ((_a = response.MESSAGE_TYPE) === null || _a === void 0 ? void 0 : _a.endsWith('_ACK'))) {
|
|
282
|
+
logger.debug(`Commit ACK for ${eventName}, re-reading the loaded blocks.`);
|
|
283
|
+
this.$emit(datasourceEventNames.refreshInfiniteCache, {
|
|
284
|
+
purge: false,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/** Safe to call when the listener was never registered. @internal */
|
|
290
|
+
teardownCommitTriggeredRefresh() {
|
|
291
|
+
var _a;
|
|
292
|
+
(_a = this.commitResponseUnsubscribe) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
293
|
+
this.commitResponseUnsubscribe = undefined;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Creates a snapshot request to the server.
|
|
297
|
+
* @param existingParams - Optional existing parameters to include
|
|
298
|
+
* @returns The server response
|
|
299
|
+
*/
|
|
300
|
+
createSnapshot() {
|
|
301
|
+
return __awaiter(this, arguments, void 0, function* (existingParams = null) {
|
|
302
|
+
const result = yield this.datasource.snapshot(existingParams);
|
|
303
|
+
return result;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Opens the paged DATASERVER subscription used to serve grid blocks.
|
|
308
|
+
* @remarks Deliberately a stream and not a snapshot: paging is a MORE_ROWS request against a
|
|
309
|
+
* live subscription, and a fresh DATA_LOGON would always come back with page 1.
|
|
310
|
+
* @internal
|
|
311
|
+
*/
|
|
312
|
+
createDataserverStream(params = null) {
|
|
313
|
+
const onError = (error) => {
|
|
314
|
+
var _a;
|
|
315
|
+
logger.error('Infinite DATASERVER paged stream error:', error);
|
|
316
|
+
this.handleErrors(((_a = error === null || error === void 0 ? void 0 : error.receivedMessage) === null || _a === void 0 ? void 0 : _a.ERROR) || (error === null || error === void 0 ? void 0 : error.message), 'stream');
|
|
317
|
+
};
|
|
318
|
+
return this.connect.stream(this.resourceName, () => { }, onError, params);
|
|
319
|
+
}
|
|
320
|
+
getAgColumnDefs(fieldsMetadata) {
|
|
321
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
322
|
+
// The same filter mapping as the server-side row model, enums included: enum columns ask
|
|
323
|
+
// for agSetColumnFilter, and the grid swaps in the community text filter when the app has
|
|
324
|
+
// not registered the Enterprise SetFilterModule (see processSchemaUpdate in grid-pro-beta).
|
|
325
|
+
const colDefsFromGenesisData = this.generateColumnDefsFromMetadata(fieldsMetadata, getServerSideFilterParamsByFieldType, getFilterByFieldType);
|
|
326
|
+
// Sortability differs by transport, matching convertSortModelToOrderBy:
|
|
327
|
+
// - REQUEST_SERVER orders any column (direction inline), so every column is sortable.
|
|
328
|
+
// - DATASERVER can only ORDER_BY a NAMED index, so a column is sortable only when it belongs
|
|
329
|
+
// to one. An unnamed index (stored under an undefined key) cannot be named, so its columns
|
|
330
|
+
// would render a sort control convertSortModelToOrderBy then drops - excluded here.
|
|
331
|
+
const namedIndexFields = new Set();
|
|
332
|
+
this.indexes.forEach((fields, indexName) => {
|
|
333
|
+
if (indexName) {
|
|
334
|
+
fields.forEach((field) => namedIndexFields.add(field));
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
colDefsFromGenesisData.forEach((colDef) => {
|
|
338
|
+
colDef.sortable = this.isRequestServer || namedIndexFields.has(colDef.field);
|
|
339
|
+
// A criteria-only req/rep accepts CRITERIA_MATCH on its declared criteria fields only;
|
|
340
|
+
// filtering any other column would send criteria the server rejects, which on this row
|
|
341
|
+
// model fails the block rather than surfacing a filter error.
|
|
342
|
+
if (this.datasource.criteriaOnlyRequest) {
|
|
343
|
+
colDef.filter = this.datasource.availableCriteriaFields.includes(colDef.field)
|
|
344
|
+
? colDef.filter
|
|
345
|
+
: null;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
return colDefsFromGenesisData;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
loadMore() {
|
|
352
|
+
// Infinite row model handles this automatically via scrolling
|
|
353
|
+
logger.warn('loadMore() is not needed for Infinite Row Model - scrolling triggers data loading automatically');
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
__decorate([
|
|
357
|
+
Connect
|
|
358
|
+
], GridProInfiniteDatasource.prototype, "connect", void 0);
|
|
359
|
+
__decorate([
|
|
360
|
+
ConnectEvents
|
|
361
|
+
], GridProInfiniteDatasource.prototype, "connectEvents", void 0);
|
|
362
|
+
__decorate([
|
|
363
|
+
Datasource
|
|
364
|
+
], GridProInfiniteDatasource.prototype, "datasource", void 0);
|
|
365
|
+
__decorate([
|
|
366
|
+
observable
|
|
367
|
+
], GridProInfiniteDatasource.prototype, "request", void 0);
|
|
368
|
+
GridProInfiniteDatasource = __decorate([
|
|
369
|
+
customElement({
|
|
370
|
+
name: 'grid-pro-infinite-datasource',
|
|
371
|
+
})
|
|
372
|
+
], GridProInfiniteDatasource);
|
|
373
|
+
export { GridProInfiniteDatasource };
|