@genesislcap/grid-pro 15.30.0 → 15.30.2
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 +8196 -7794
- package/dist/dts/column-selection/column-selection.d.ts +4 -3
- package/dist/dts/column-selection/column-selection.d.ts.map +1 -1
- package/dist/dts/column-selection/column-selection.styles.d.ts.map +1 -1
- package/dist/dts/datasource/base.datasource.d.ts +69 -8
- package/dist/dts/datasource/base.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/base.types.d.ts +10 -0
- package/dist/dts/datasource/base.types.d.ts.map +1 -1
- package/dist/dts/datasource/dataserver-result.filter.d.ts +17 -0
- package/dist/dts/datasource/dataserver-result.filter.d.ts.map +1 -0
- package/dist/dts/datasource/delivered-block.ledger.d.ts +37 -0
- package/dist/dts/datasource/delivered-block.ledger.d.ts.map +1 -0
- package/dist/dts/datasource/infinite.datasource.d.ts +65 -1
- package/dist/dts/datasource/infinite.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/infinite.resource.d.ts +136 -6
- package/dist/dts/datasource/infinite.resource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.datasource.d.ts +8 -4
- package/dist/dts/datasource/server-side.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts +23 -0
- package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-dataserver.d.ts +28 -0
- package/dist/dts/datasource/server-side.resource-dataserver.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-reqrep.d.ts +1 -1
- package/dist/dts/datasource/server-side.resource-reqrep.d.ts.map +1 -1
- package/dist/dts/grid-pro-beta.d.ts +6 -0
- package/dist/dts/grid-pro-beta.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts +4 -0
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts.map +1 -1
- package/dist/dts/grid-pro.d.ts.map +1 -1
- package/dist/dts/react.d.ts +20 -20
- package/dist/esm/column-selection/column-selection.js +11 -9
- package/dist/esm/column-selection/column-selection.styles.js +0 -2
- package/dist/esm/column-selection/column-selection.template.js +1 -1
- package/dist/esm/datasource/base.datasource.js +182 -27
- package/dist/esm/datasource/dataserver-result.filter.js +28 -0
- package/dist/esm/datasource/delivered-block.ledger.js +52 -0
- package/dist/esm/datasource/infinite.datasource.js +131 -20
- package/dist/esm/datasource/infinite.resource.js +221 -16
- package/dist/esm/datasource/server-side.datasource.js +80 -29
- package/dist/esm/datasource/server-side.resource-base.js +39 -0
- package/dist/esm/datasource/server-side.resource-dataserver.js +110 -23
- package/dist/esm/datasource/server-side.resource-reqrep.js +25 -3
- package/dist/esm/grid-pro-beta.js +20 -2
- package/dist/esm/grid-pro.js +4 -1
- package/dist/grid-pro.api.json +66 -66
- package/dist/grid-pro.d.ts +294 -21
- package/dist/react.cjs +23 -23
- package/dist/react.mjs +21 -21
- package/package.json +13 -13
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __awaiter, __decorate } from "tslib";
|
|
2
2
|
import { Connect, Datasource, DatasourceDefaults, DatasourceEventHandler, FieldTypeEnum, logger, normaliseCriteria, ResourceType, } from '@genesislcap/foundation-comms';
|
|
3
|
+
import { JSONSerializer } from '@genesislcap/foundation-utils';
|
|
3
4
|
import { attr, nullableNumberConverter, observable, volatile } from '@microsoft/fast-element';
|
|
4
5
|
import { FoundationElement } from '@microsoft/fast-foundation';
|
|
5
6
|
import { BehaviorSubject } from 'rxjs';
|
|
@@ -141,11 +142,23 @@ export class GenesisGridDatasourceElement extends DatasourceEventHandler(Foundat
|
|
|
141
142
|
requestAutoSetup: this.requestAutoSetup,
|
|
142
143
|
};
|
|
143
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* The criteria the resource should be read with: the `criteria` attribute plus anything added
|
|
147
|
+
* through `setFilter()`.
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
144
150
|
buildCriteria() {
|
|
145
|
-
var _a;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
var _a, _b;
|
|
152
|
+
// Blank fragments are dropped before joining. `criteria` is often bound to an empty string
|
|
153
|
+
// rather than left unset, and ''.split(';') is [''], so without this a single setFilter()
|
|
154
|
+
// produced " && FIELD == 'x'" - a leading operator the server rejects as invalid Groovy.
|
|
155
|
+
const parts = [
|
|
156
|
+
...((_b = (_a = this.criteria) === null || _a === void 0 ? void 0 : _a.split(criteriaDelimiter)) !== null && _b !== void 0 ? _b : []),
|
|
157
|
+
...this.criteriaFromFilters.values(),
|
|
158
|
+
]
|
|
159
|
+
.map((part) => part === null || part === void 0 ? void 0 : part.trim())
|
|
160
|
+
.filter(Boolean);
|
|
161
|
+
const normalisedCriteria = normaliseCriteria(parts.join(criteriaJoin), criteriaDelimiter);
|
|
149
162
|
return normalisedCriteria ? normalisedCriteria : undefined;
|
|
150
163
|
}
|
|
151
164
|
setFilter(fieldName, newFilter) {
|
|
@@ -360,11 +373,9 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
|
|
|
360
373
|
this.dataSubWasLoggedOff = false;
|
|
361
374
|
this.keepColDefsOnClearRowData = false;
|
|
362
375
|
this.rowData = new Map();
|
|
363
|
-
this.transactionData = {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
update: [],
|
|
367
|
-
};
|
|
376
|
+
this.transactionData = { add: [], remove: [], update: [] };
|
|
377
|
+
/** So rows without a row id are warned about once per element, not once per block. @internal */
|
|
378
|
+
this.missingRowIdWarned = false;
|
|
368
379
|
this._isDisconnected = false;
|
|
369
380
|
}
|
|
370
381
|
subscribeToConnection() {
|
|
@@ -467,32 +478,60 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
|
|
|
467
478
|
}
|
|
468
479
|
});
|
|
469
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Resolves the id of the held row a stream row refers to.
|
|
483
|
+
* @remarks Under a custom `row-id` a DATASERVER push may carry only the default id
|
|
484
|
+
* (`ROW_REF`), so the held row with that default id supplies the custom one. Returns
|
|
485
|
+
* `undefined` when neither id is present - callers skip the row rather than matching
|
|
486
|
+
* `undefined === undefined` against the first held row.
|
|
487
|
+
* @internal
|
|
488
|
+
*/
|
|
489
|
+
resolveRowId(row) {
|
|
490
|
+
const rowId = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
491
|
+
if (rowId !== undefined && rowId !== null) {
|
|
492
|
+
return rowId;
|
|
493
|
+
}
|
|
494
|
+
if (this.hasDefaultRowId || this.isRequestServer) {
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
const defaultRowId = this.defaultRowIdByResourceType;
|
|
498
|
+
const defaultKey = row === null || row === void 0 ? void 0 : row[defaultRowId];
|
|
499
|
+
if (defaultKey === undefined || defaultKey === null) {
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
for (const held of this.rowData.values()) {
|
|
503
|
+
if ((held === null || held === void 0 ? void 0 : held[defaultRowId]) === defaultKey) {
|
|
504
|
+
return held[this.rowId];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
470
509
|
handleStreamDeletes(deletedRows) {
|
|
471
510
|
deletedRows === null || deletedRows === void 0 ? void 0 : deletedRows.forEach((deleteData) => {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const rowToBeDeleted = Object.values(Object.fromEntries(this.rowData)).find((obj) => obj[this.defaultRowIdByResourceType] === deleteData[this.defaultRowIdByResourceType]);
|
|
477
|
-
this.transactionData.remove.push({ [this.rowId]: rowToBeDeleted[this.rowId] });
|
|
511
|
+
const rowId = this.resolveRowId(deleteData);
|
|
512
|
+
if (rowId === undefined) {
|
|
513
|
+
logger.warn('Attempted to delete row without a resolvable rowId', deleteData);
|
|
514
|
+
return;
|
|
478
515
|
}
|
|
479
|
-
this.
|
|
516
|
+
this.transactionData.remove.push({ [this.rowId]: rowId });
|
|
517
|
+
this.rowData.delete(rowId);
|
|
480
518
|
});
|
|
481
519
|
}
|
|
482
520
|
handleStreamUpdates(updatedRows) {
|
|
483
521
|
updatedRows === null || updatedRows === void 0 ? void 0 : updatedRows.forEach((updateData) => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const rowToBeUpdated = Object.values(Object.fromEntries(this.rowData)).find((obj) => obj[this.defaultRowIdByResourceType] === updateData[this.defaultRowIdByResourceType]);
|
|
492
|
-
updatedRow = Object.assign(Object.assign({}, rowToBeUpdated), updateData);
|
|
493
|
-
this.transactionData.update.push(updatedRow);
|
|
522
|
+
const rowId = this.resolveRowId(updateData);
|
|
523
|
+
const rowToBeUpdated = rowId === undefined ? undefined : this.rowData.get(rowId);
|
|
524
|
+
if (!rowToBeUpdated) {
|
|
525
|
+
// Neither the grid nor consumers hold the row, so there is nothing to update; the row is
|
|
526
|
+
// reported as `add` if and when a block delivers it.
|
|
527
|
+
logger.debug('Ignoring update for a row that is not held', updateData);
|
|
528
|
+
return;
|
|
494
529
|
}
|
|
495
|
-
|
|
530
|
+
// A partial MODIFY resolved through the default id carries the custom id as undefined;
|
|
531
|
+
// keep the resolved one.
|
|
532
|
+
const updatedRow = Object.assign(Object.assign(Object.assign({}, rowToBeUpdated), updateData), { [this.rowId]: rowId });
|
|
533
|
+
this.transactionData.update.push(updatedRow);
|
|
534
|
+
this.rowData.set(rowId, updatedRow);
|
|
496
535
|
});
|
|
497
536
|
}
|
|
498
537
|
applyAllTransactions() {
|
|
@@ -525,6 +564,119 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
|
|
|
525
564
|
changes: mappedTransaction,
|
|
526
565
|
});
|
|
527
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Reports rows a block-based row model (server-side, infinite) has just handed to the grid.
|
|
569
|
+
* Rows not held before are reported as `add`; rows already held (a block re-read after a
|
|
570
|
+
* refresh, or a req/rep re-read after a commit or poll) are reported as `update` only when
|
|
571
|
+
* their content changed, so a plain re-read is silent.
|
|
572
|
+
* @remarks `rowData` holds the rows delivered since the last cache reset: the resource reports
|
|
573
|
+
* rows a re-read no longer returned (`reportWithdrawnRows`), and `clearRowData` empties it when
|
|
574
|
+
* the grid drops its cache. Block eviction is not mirrored - see `DeliveredBlockLedger` - so
|
|
575
|
+
* this is a superset of what the grid holds.
|
|
576
|
+
* @internal
|
|
577
|
+
*/
|
|
578
|
+
reportDeliveredRows(rows) {
|
|
579
|
+
const transaction = this.resetTransaction();
|
|
580
|
+
rows.forEach((row) => {
|
|
581
|
+
const rowId = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
582
|
+
if (rowId === undefined || rowId === null) {
|
|
583
|
+
this.warnMissingRowIdOnce(row);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const known = this.rowData.get(rowId);
|
|
587
|
+
if (!known) {
|
|
588
|
+
transaction.add.push(row);
|
|
589
|
+
}
|
|
590
|
+
else if (this.hasRowContentChanged(known, row)) {
|
|
591
|
+
transaction.update.push(row);
|
|
592
|
+
}
|
|
593
|
+
this.rowData.set(rowId, row);
|
|
594
|
+
});
|
|
595
|
+
this.emitTransaction(transaction);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Reports rows a block re-read no longer returned: they left the grid (deleted on the server,
|
|
599
|
+
* or moved into a block that reports them again). Rows already reported removed - a DATASERVER
|
|
600
|
+
* push, say - are skipped.
|
|
601
|
+
* @internal
|
|
602
|
+
*/
|
|
603
|
+
reportWithdrawnRows(rowIds) {
|
|
604
|
+
const transaction = this.resetTransaction();
|
|
605
|
+
rowIds.forEach((rowId) => {
|
|
606
|
+
if (!this.rowData.has(rowId)) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
this.rowData.delete(rowId);
|
|
610
|
+
transaction.remove.push({ [this.rowId]: rowId });
|
|
611
|
+
});
|
|
612
|
+
this.emitTransaction(transaction);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Starts a fresh transaction and returns it, so a caller can keep working on the object it
|
|
616
|
+
* built even if a consumer's `datasource-data-changed` handler resets the element meanwhile.
|
|
617
|
+
* @internal
|
|
618
|
+
*/
|
|
619
|
+
resetTransaction() {
|
|
620
|
+
const transaction = { add: [], remove: [], update: [] };
|
|
621
|
+
this.transactionData = transaction;
|
|
622
|
+
return transaction;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Whether two versions of a row differ in content. Identity and a key-order-insensitive
|
|
626
|
+
* field comparison come first, so a re-read of unchanged rows costs no serialization; nested
|
|
627
|
+
* values fall back to the serializer.
|
|
628
|
+
* @internal
|
|
629
|
+
*/
|
|
630
|
+
hasRowContentChanged(known, row) {
|
|
631
|
+
if (known === row) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
if (!known || !row || typeof known !== 'object' || typeof row !== 'object') {
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
const knownKeys = Object.keys(known);
|
|
638
|
+
const rowKeys = Object.keys(row);
|
|
639
|
+
if (knownKeys.length !== rowKeys.length) {
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
for (const key of rowKeys) {
|
|
643
|
+
const before = known[key];
|
|
644
|
+
const after = row[key];
|
|
645
|
+
if (before === after) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (before !== null &&
|
|
649
|
+
after !== null &&
|
|
650
|
+
typeof before === 'object' &&
|
|
651
|
+
typeof after === 'object' &&
|
|
652
|
+
this.serializer.serialize(before) === this.serializer.serialize(after)) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
/** Emits `datasource-data-changed` for `transaction` when it is non-empty. @internal */
|
|
660
|
+
emitTransaction(transaction) {
|
|
661
|
+
if (!transaction) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const { add, update, remove } = transaction;
|
|
665
|
+
if (!(add === null || add === void 0 ? void 0 : add.length) && !(update === null || update === void 0 ? void 0 : update.length) && !(remove === null || remove === void 0 ? void 0 : remove.length)) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
this.$emit(datasourceEventNames.dataChanged, {
|
|
669
|
+
changes: transaction,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
/** @internal */
|
|
673
|
+
warnMissingRowIdOnce(row) {
|
|
674
|
+
if (this.missingRowIdWarned) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
this.missingRowIdWarned = true;
|
|
678
|
+
logger.warn(`Rows delivered by ${this.resourceName} carry no '${this.rowId}' value and are not reported in datasource-data-changed; set row-id to the field that identifies them.`, row);
|
|
679
|
+
}
|
|
528
680
|
/**
|
|
529
681
|
* Builds the index map used for server-side sorting from the resource's metadata.
|
|
530
682
|
* @remarks Real DATASERVER indexes keep their names; when the resource also (or only) reports
|
|
@@ -707,3 +859,6 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
|
|
|
707
859
|
return additionalPanels;
|
|
708
860
|
}
|
|
709
861
|
}
|
|
862
|
+
__decorate([
|
|
863
|
+
JSONSerializer
|
|
864
|
+
], GridProBaseDatasource.prototype, "serializer", void 0);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DatasourceDefaults, dataServerResultFilter, } from '@genesislcap/foundation-comms';
|
|
2
|
+
/**
|
|
3
|
+
* Filters a DATASERVER batch into inserts/updates/deletes, keeping the default row id
|
|
4
|
+
* (`ROW_REF`) on every row alongside the configured one.
|
|
5
|
+
* @remarks `dataServerResultFilter` resolves the configured `rowId` out of `DETAILS` and then
|
|
6
|
+
* drops `DETAILS` entirely. Under a custom `row-id` that loses the only id a pushed MODIFY or
|
|
7
|
+
* DELETE carries: Genesis sends those as `DETAILS: { OPERATION, ROW_REF }` plus the changed
|
|
8
|
+
* fields, so the custom id is absent and the row arrives with no resolvable identity at all.
|
|
9
|
+
* Hoisting `ROW_REF` before the filter runs keeps it on both the rows a block delivers and the
|
|
10
|
+
* rows a push carries, which is what lets `resolveRowId` match one to the other.
|
|
11
|
+
*
|
|
12
|
+
* Columns come from the resource's field metadata, not from row keys, so the extra field does
|
|
13
|
+
* not add a column.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export function filterDataserverResult(result, rowId) {
|
|
17
|
+
var _a;
|
|
18
|
+
const defaultRowId = DatasourceDefaults.DATASERVER_ROW_ID;
|
|
19
|
+
if (rowId === defaultRowId) {
|
|
20
|
+
return dataServerResultFilter(result, rowId);
|
|
21
|
+
}
|
|
22
|
+
const withRowRef = Object.assign(Object.assign({}, result), { ROW: (_a = result.ROW) === null || _a === void 0 ? void 0 : _a.map((row) => {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
const rowRef = (_b = (_a = row === null || row === void 0 ? void 0 : row.DETAILS) === null || _a === void 0 ? void 0 : _a[defaultRowId]) !== null && _b !== void 0 ? _b : row === null || row === void 0 ? void 0 : row[defaultRowId];
|
|
25
|
+
return rowRef === undefined || rowRef === null ? row : Object.assign(Object.assign({}, row), { [defaultRowId]: rowRef });
|
|
26
|
+
}) });
|
|
27
|
+
return dataServerResultFilter(withRowRef, rowId);
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks which row ids each block has handed to AG Grid, so the host can learn when a re-read of
|
|
3
|
+
* a block no longer returns a row (it left the grid).
|
|
4
|
+
* @remarks Plain class shared by the infinite and SSRM resources - AG Grid does not report that
|
|
5
|
+
* event.
|
|
6
|
+
*
|
|
7
|
+
* Block eviction is deliberately **not** mirrored here. AG Grid evicts by last access
|
|
8
|
+
* (`InfiniteCache.purgeBlocksIfNeeded` sorts on `lastAccessed` and never evicts a displayed or
|
|
9
|
+
* focused block; the SSRM `LazyCache` purges by distance from the viewport), and a datasource is
|
|
10
|
+
* told about neither the accesses nor the evictions. Any eviction model built from delivery order
|
|
11
|
+
* alone diverges from the grid's as soon as the user scrolls back, which would drop a visible
|
|
12
|
+
* row from the held set and silence the pushes that follow. So the held set means "rows delivered
|
|
13
|
+
* since the last cache reset": a superset of what the grid holds, which keeps every push for a
|
|
14
|
+
* possibly-visible row flowing and costs only a re-delivered row being reported as unchanged
|
|
15
|
+
* rather than as an `add`.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export class DeliveredBlockLedger {
|
|
19
|
+
constructor(rowId) {
|
|
20
|
+
this.rowId = rowId;
|
|
21
|
+
/** Row ids per block start row. */
|
|
22
|
+
this.blocks = new Map();
|
|
23
|
+
}
|
|
24
|
+
/** Records the rows handed to the grid for the block at `startRow`. */
|
|
25
|
+
record(startRow, rows) {
|
|
26
|
+
const ids = new Set();
|
|
27
|
+
rows.forEach((row) => {
|
|
28
|
+
const id = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
29
|
+
if (id !== undefined && id !== null) {
|
|
30
|
+
ids.add(id);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const previous = this.blocks.get(startRow);
|
|
34
|
+
this.blocks.set(startRow, ids);
|
|
35
|
+
const withdrawnRowIds = previous
|
|
36
|
+
? [...previous].filter((id) => !ids.has(id) && !this.isHeld(id))
|
|
37
|
+
: [];
|
|
38
|
+
return { withdrawnRowIds };
|
|
39
|
+
}
|
|
40
|
+
/** Forgets every block, for when the grid drops its whole cache (filter/sort change, destroy). */
|
|
41
|
+
clear() {
|
|
42
|
+
this.blocks.clear();
|
|
43
|
+
}
|
|
44
|
+
isHeld(id) {
|
|
45
|
+
for (const ids of this.blocks.values()) {
|
|
46
|
+
if (ids.has(id)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -2,6 +2,7 @@ import { __awaiter, __decorate } from "tslib";
|
|
|
2
2
|
import { Connect, ConnectEvents, Datasource, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { LifecycleMixin } from '@genesislcap/foundation-utils';
|
|
4
4
|
import { customElement, DOM, observable } from '@microsoft/fast-element';
|
|
5
|
+
import { debounceTime, skip } from 'rxjs/operators';
|
|
5
6
|
import { gridProGenesisDatasourceEventNames } from '../grid-pro-genesis-datasource';
|
|
6
7
|
import { datasourceEventNames, } from '../grid-pro-genesis-datasource/datasource-events.types';
|
|
7
8
|
import { logger } from '../utils';
|
|
@@ -9,6 +10,8 @@ import { GridProBaseDatasource } from './base.datasource';
|
|
|
9
10
|
import { GenesisInfiniteDatasource } from './infinite.resource';
|
|
10
11
|
import { getFilterByFieldType, getServerSideFilterParamsByFieldType, } from './server-side.grid-definitions';
|
|
11
12
|
const criteriaDelimiter = ';';
|
|
13
|
+
/** How long a burst of `setFilter()` calls is collapsed for, matching the client-side model. */
|
|
14
|
+
const FILTER_DEBOUNCE_MS = 600;
|
|
12
15
|
/**
|
|
13
16
|
* A Genesis Datasource element for AG Grid's Infinite Row Model.
|
|
14
17
|
* @remarks
|
|
@@ -40,12 +43,15 @@ const criteriaDelimiter = ';';
|
|
|
40
43
|
* @fires datasource-initialize - Fired to hand off infinite row model grid options. detail: `InitializeEventDetail`
|
|
41
44
|
* @fires datasource-init - Fired when the infinite grid model should initialize data
|
|
42
45
|
* @fires datasource-schema-updated - Fired when column metadata or defs are updated. detail: `SchemaUpdatedEventDetail`
|
|
46
|
+
* @fires datasource-data-changed - Fired when rows are delivered to the grid, a DATASERVER push changes them, or a re-read no longer returns them; `add` are rows the grid did not hold before, `update` are held rows whose content changed, `remove` carries the row id. detail: `DataChangedEventDetail`
|
|
43
47
|
* @fires set-infinite-datasource - Fired to attach or clear the infinite row model datasource. detail: `SetInfiniteDatasourceEventDetail`
|
|
44
48
|
* @fires refresh-infinite-cache - Fired to request an infinite row model refresh; `purge` drops the block cache and re-reads. detail: `RefreshInfiniteCacheEventDetail`
|
|
45
49
|
* @fires add-grid-css-class - Fired to add a CSS class on the grid host (hover sort indicators). detail: `GridCssClassEventDetail`
|
|
46
50
|
* @fires remove-grid-css-class - Fired to remove that CSS class from the grid host. detail: `GridCssClassEventDetail`
|
|
47
51
|
* @fires cache-filter-config - Fired to persist filter configuration for the grid
|
|
48
|
-
* @fires datasource-data-cleared - Fired when
|
|
52
|
+
* @fires datasource-data-cleared - Fired when the grid drops every row it held (reload, filter or sort change, destroy); `includeSchema` says whether the columns went too. detail: `DataClearedEventDetail`
|
|
53
|
+
* @fires datasource-loading-finished - Fired when the first block comes back with rows
|
|
54
|
+
* @fires datasource-no-data-available - Fired when the first block comes back empty
|
|
49
55
|
* @fires datasource-filters-restored - Fired when persisted filters are reapplied
|
|
50
56
|
*/
|
|
51
57
|
let GridProInfiniteDatasource = class GridProInfiniteDatasource extends LifecycleMixin(GridProBaseDatasource) {
|
|
@@ -61,6 +67,10 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
61
67
|
this.setDisconnected(true);
|
|
62
68
|
return;
|
|
63
69
|
}
|
|
70
|
+
if (!this.isInfiniteSupportedForResource()) {
|
|
71
|
+
this.clearRowData();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
64
74
|
this.$emit(gridProGenesisDatasourceEventNames.dataInit);
|
|
65
75
|
this.setDisconnected(false);
|
|
66
76
|
this.indexes = this.getResourceIndexes(this.datasource.availableIndexes, this.datasource.availableSortableFields);
|
|
@@ -78,15 +88,14 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
78
88
|
// VIEW_NUMBER 0 means "pagination disabled": the server continues from the cursor it
|
|
79
89
|
// holds on the last row it delivered, rather than re-seeking by offset.
|
|
80
90
|
getMoreRowsFunc: (sourceRef) => this.connect.getMoreRows(sourceRef, 0),
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}),
|
|
91
|
+
onRowsDeliveredFunc: this.reportDeliveredRows.bind(this),
|
|
92
|
+
onRowsWithdrawnFunc: this.reportWithdrawnRows.bind(this),
|
|
93
|
+
// A filter or sort change drops every block: forget the rows and tell consumers.
|
|
94
|
+
onRowCacheResetFunc: () => this.clearRowData(false),
|
|
95
|
+
onRowsInvalidatedFunc: this.handleLiveRowsInvalidated.bind(this),
|
|
96
|
+
onRowsUpdatedFunc: this.handleLiveRowsUpdated.bind(this),
|
|
97
|
+
onNoDataAvailableFunc: () => this.$emit(datasourceEventNames.noDataAvailable),
|
|
98
|
+
onDataAvailableFunc: () => this.$emit(datasourceEventNames.loadingFinished),
|
|
90
99
|
errorHandlerFunc: this.handleErrors.bind(this),
|
|
91
100
|
resourceName: this.resourceName,
|
|
92
101
|
resourceParams: this.isRequestServer
|
|
@@ -96,8 +105,12 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
96
105
|
resourceColDefs: this.datasource.originalFieldDef,
|
|
97
106
|
maxRows: +this.maxRows,
|
|
98
107
|
rowId: this.rowId,
|
|
99
|
-
|
|
108
|
+
// The merged criteria, so a criteria added through setFilter() is read with the rows
|
|
109
|
+
// rather than dropped: the logon builder replaces CRITERIA_MATCH with what it is given.
|
|
110
|
+
baseCriteria: this.buildCriteria(),
|
|
100
111
|
isRequestServer: this.isRequestServer,
|
|
112
|
+
defaultOrderBy: this.orderBy,
|
|
113
|
+
defaultReverse: this.reverse,
|
|
101
114
|
});
|
|
102
115
|
// Emit event to set infinite datasource
|
|
103
116
|
this.$emit(datasourceEventNames.setInfiniteDatasource, {
|
|
@@ -140,6 +153,7 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
140
153
|
return;
|
|
141
154
|
this.init();
|
|
142
155
|
this.subscribeToConnection();
|
|
156
|
+
this.subscribeToFilterChanges();
|
|
143
157
|
});
|
|
144
158
|
}
|
|
145
159
|
disconnectedCallback() {
|
|
@@ -150,8 +164,34 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
150
164
|
return;
|
|
151
165
|
yield this.destroy();
|
|
152
166
|
this.unsubscribeFromConnection();
|
|
167
|
+
this.unsubscribeFromFilterChanges();
|
|
153
168
|
}));
|
|
154
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Reloads when `setFilter()` or `removeFilter()` changes the criteria.
|
|
172
|
+
* @remarks The criteria a block is read with is captured when the resource is built, so a
|
|
173
|
+
* filter added later reaches nothing until the rows are re-read. The `criteria` attribute
|
|
174
|
+
* already reloads through `criteriaChanged`; this gives the programmatic API the same
|
|
175
|
+
* behaviour instead of silently doing nothing.
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
subscribeToFilterChanges() {
|
|
179
|
+
this.unsubscribeFromFilterChanges();
|
|
180
|
+
// Same shape as the client-side datasource: `update` is a BehaviorSubject, so skip(1) drops
|
|
181
|
+
// the value it replays on subscribe (the state init() is already reading), and the debounce
|
|
182
|
+
// collapses a burst of setFilter calls into one reload.
|
|
183
|
+
this.filterSub = this.update.pipe(skip(1), debounceTime(FILTER_DEBOUNCE_MS)).subscribe(() => {
|
|
184
|
+
if (!this.infiniteDatasource)
|
|
185
|
+
return;
|
|
186
|
+
this.reloadResourceData();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** @internal */
|
|
190
|
+
unsubscribeFromFilterChanges() {
|
|
191
|
+
var _a;
|
|
192
|
+
(_a = this.filterSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
193
|
+
this.filterSub = undefined;
|
|
194
|
+
}
|
|
155
195
|
deepClone() {
|
|
156
196
|
const copy = super.deepClone();
|
|
157
197
|
copy.deferredGridOptions = structuredClone(this.deferredGridOptions);
|
|
@@ -169,7 +209,10 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
169
209
|
// The paging DATA_LOGON is opened through connect.stream, not this.datasource, so destroying
|
|
170
210
|
// the comms datasource below does not close it. Dispose the IDatasource explicitly or every
|
|
171
211
|
// criteria change (e.g. a bound search box) leaks a live subscription and its block timers.
|
|
172
|
-
|
|
212
|
+
// detach(), not destroy(): AG Grid keeps calling this bean until the replacement is attached
|
|
213
|
+
// a round trip later, and the purge below re-requests blocks immediately. destroy() alone
|
|
214
|
+
// would leave it reading with the old criteria and reporting those rows as delivered.
|
|
215
|
+
(_b = (_a = this.infiniteDatasource) === null || _a === void 0 ? void 0 : _a.detach) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
173
216
|
this.infiniteDatasource = undefined;
|
|
174
217
|
this.datasource.destroy();
|
|
175
218
|
// Emit event to cache current filter model before clearing data
|
|
@@ -225,6 +268,69 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
225
268
|
clearReadyListener() {
|
|
226
269
|
this.removeEventListener(datasourceEventNames.ready, this.onDatasourceReady);
|
|
227
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Whether the infinite row model can drive this resource.
|
|
273
|
+
* @remarks It pages a req/rep by sending CRITERIA_MATCH, ORDER_BY and OFFSET in DETAILS, which
|
|
274
|
+
* a request reply only accepts when it is declared `criteriaOnlyRequest`
|
|
275
|
+
* (`CRITERIA_ONLY_REQUEST` in its metadata). Without it the server expects its inputs in
|
|
276
|
+
* `REQUEST` instead and rejects every block, which on this row model surfaces as a grid that
|
|
277
|
+
* simply never fills. Report it once, up front, as the server-side model does.
|
|
278
|
+
* @returns `true` when the infinite model can be used, `false` after reporting a
|
|
279
|
+
* `resource-type` error.
|
|
280
|
+
* @internal
|
|
281
|
+
*/
|
|
282
|
+
isInfiniteSupportedForResource() {
|
|
283
|
+
if (!this.isRequestServer || this.datasource.criteriaOnlyRequest) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
this.handleErrors(`Infinite row model is not supported for the REQUEST_SERVER resource '${this.resourceName}' because it does not support criteria-only requests. Set 'criteriaOnlyRequest = true' on the request reply definition, or use a client-side datasource for this resource.`, 'resource-type');
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* A pushed INSERT/DELETE changes which rows exist or their order, so the change is reported
|
|
291
|
+
* and then the rows are re-read (purge).
|
|
292
|
+
* @internal
|
|
293
|
+
*/
|
|
294
|
+
handleLiveRowsInvalidated(change) {
|
|
295
|
+
this.reportLiveChange(change);
|
|
296
|
+
this.$emit(datasourceEventNames.refreshInfiniteCache, {
|
|
297
|
+
purge: true,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* A pushed MODIFY is already patched into the paging cache, so the change is reported and the
|
|
302
|
+
* grid only needs to re-render its blocks.
|
|
303
|
+
* @internal
|
|
304
|
+
*/
|
|
305
|
+
handleLiveRowsUpdated(change) {
|
|
306
|
+
this.reportLiveChange(change);
|
|
307
|
+
this.$emit(datasourceEventNames.refreshInfiniteCache, {
|
|
308
|
+
purge: false,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Turns a DATASERVER push into the same transaction the client-side datasource reports:
|
|
313
|
+
* partial MODIFY rows are merged into the held row, and a remove carries just the row id.
|
|
314
|
+
* @remarks Unlike the client-side model, a pushed row need not be held here - it may sit
|
|
315
|
+
* beyond the loaded blocks, in which case the grid will never show it.
|
|
316
|
+
*
|
|
317
|
+
* So a pushed INSERT is deliberately not reported: nothing here says where the new row sorts,
|
|
318
|
+
* and reporting an `add` for a row that lands at position 40,000 of an unscrolled grid would
|
|
319
|
+
* contradict the event's contract and leave the row held forever, since no block re-read ever
|
|
320
|
+
* delivers (or withdraws) it. Every INSERT is followed by a purge and re-read, which reports
|
|
321
|
+
* the row as `add` if and when it actually reaches the grid.
|
|
322
|
+
*
|
|
323
|
+
* A MODIFY for a row that is not held is skipped for the same reason; a DELETE is reported by
|
|
324
|
+
* the id it carries, and one that cannot be resolved to an id is skipped with a warning rather
|
|
325
|
+
* than matched against the wrong row.
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
reportLiveChange(change) {
|
|
329
|
+
const transaction = this.resetTransaction();
|
|
330
|
+
this.handleStreamUpdates(change.updates);
|
|
331
|
+
this.handleStreamDeletes(change.deletes);
|
|
332
|
+
this.emitTransaction(transaction);
|
|
333
|
+
}
|
|
228
334
|
destroy() {
|
|
229
335
|
return __awaiter(this, void 0, void 0, function* () {
|
|
230
336
|
var _a, _b;
|
|
@@ -237,7 +343,9 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
237
343
|
this.datasource.destroy();
|
|
238
344
|
this.clearRowData();
|
|
239
345
|
if (this.infiniteDatasource) {
|
|
240
|
-
|
|
346
|
+
// The host is done with this bean for good, so detach rather than destroy - see
|
|
347
|
+
// reloadResourceData.
|
|
348
|
+
(_b = (_a = this.infiniteDatasource).detach) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
241
349
|
this.infiniteDatasource = undefined;
|
|
242
350
|
}
|
|
243
351
|
// Emit events instead of direct grid access
|
|
@@ -252,15 +360,18 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
|
|
|
252
360
|
yield this.init();
|
|
253
361
|
});
|
|
254
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Forgets every held row and tells consumers, as the client-side datasource does: the grid is
|
|
365
|
+
* dropping its rows (a reload, a filter or sort change, or teardown). `withColumnDefs` also
|
|
366
|
+
* clears the column definitions.
|
|
367
|
+
* @internal
|
|
368
|
+
*/
|
|
255
369
|
clearRowData(withColumnDefs = true) {
|
|
256
370
|
this.rowData = new Map();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
this.transactionData = { remove: [] };
|
|
371
|
+
this.resetTransaction();
|
|
372
|
+
this.$emit(datasourceEventNames.dataCleared, {
|
|
373
|
+
includeSchema: withColumnDefs,
|
|
374
|
+
});
|
|
264
375
|
}
|
|
265
376
|
/**
|
|
266
377
|
* Honours `pollTriggerEvents` without polling: a commit ACK for one of the listed events
|