@acorex/platform 20.9.37 → 20.9.38
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/fesm2022/acorex-platform-layout-entity.mjs +149 -32
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +10 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-layout-entity.d.ts +28 -2
- package/types/acorex-platform-layout-widgets.d.ts +5 -1
|
@@ -12818,6 +12818,12 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
12818
12818
|
this.selectedItems = signal([], ...(ngDevMode ? [{ debugName: "selectedItems" }] : /* istanbul ignore next */ []));
|
|
12819
12819
|
this.toolbarNode = signal(null, ...(ngDevMode ? [{ debugName: "toolbarNode" }] : /* istanbul ignore next */ []));
|
|
12820
12820
|
this.destroyed = new Subject();
|
|
12821
|
+
/** Invalidates in-flight filter/refresh work on remount, rapid page switches, or destroy. */
|
|
12822
|
+
this.opGeneration = 0;
|
|
12823
|
+
this.isViewDestroyed = false;
|
|
12824
|
+
this.initListTimeoutId = null;
|
|
12825
|
+
/** Unpatched data-list refresh (hits the grid). Prefer this over the scoped patch to avoid recursion. */
|
|
12826
|
+
this.boundDataListRefresh = null;
|
|
12821
12827
|
//options
|
|
12822
12828
|
this.entitySource = computed(() => this.options()['entity'], ...(ngDevMode ? [{ debugName: "entitySource" }] : /* istanbul ignore next */ []));
|
|
12823
12829
|
this.excludeColumns = computed(() => this.options()['excludeColumns'], ...(ngDevMode ? [{ debugName: "excludeColumns" }] : /* istanbul ignore next */ []));
|
|
@@ -12930,20 +12936,20 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
12930
12936
|
}
|
|
12931
12937
|
inst.__axpEntityListRefreshPatched = true;
|
|
12932
12938
|
const originalRefresh = inst.refresh.bind(inst);
|
|
12939
|
+
this.boundDataListRefresh = originalRefresh;
|
|
12933
12940
|
inst.refresh = () => {
|
|
12934
|
-
void (
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
|
|
12938
|
-
await this.applyRelatedFiltersFromContextAndDatasource(specs);
|
|
12939
|
-
}
|
|
12940
|
-
else {
|
|
12941
|
-
await this.reapplyScopedFiltersToDataSource();
|
|
12942
|
-
}
|
|
12943
|
-
originalRefresh();
|
|
12944
|
-
})();
|
|
12941
|
+
void this.runScopedDataListRefresh(originalRefresh);
|
|
12942
|
+
// Patched refresh is async; callers that need a boolean should use boundDataListRefresh.
|
|
12943
|
+
return false;
|
|
12945
12944
|
};
|
|
12945
|
+
// If the initial filter apply raced ahead of the grid, retry load now that refresh is bound.
|
|
12946
|
+
untracked(() => {
|
|
12947
|
+
if (!this.isMounted() && !this.isViewDestroyed) {
|
|
12948
|
+
void this.reapplyScopedFiltersToDataSource({ triggerInitialLoad: true });
|
|
12949
|
+
}
|
|
12950
|
+
});
|
|
12946
12951
|
}, ...(ngDevMode ? [{ debugName: "#patchDataListRefreshEffect" }] : /* istanbul ignore next */ []));
|
|
12952
|
+
//#endregion
|
|
12947
12953
|
//#region ---- Query Change Handler ----
|
|
12948
12954
|
this.queries = undefined;
|
|
12949
12955
|
this.#effect = effect(() => {
|
|
@@ -13156,14 +13162,29 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13156
13162
|
const routeFilters = this.parseFiltersFromRoute();
|
|
13157
13163
|
return routeFilters.length ? this.mergeToolbarFilters(base, routeFilters) : base;
|
|
13158
13164
|
}
|
|
13165
|
+
//#region ---- Scoped filter / initial load (race-safe) ----
|
|
13166
|
+
beginOp() {
|
|
13167
|
+
return ++this.opGeneration;
|
|
13168
|
+
}
|
|
13169
|
+
isOpStale(generation) {
|
|
13170
|
+
return this.isViewDestroyed || generation !== this.opGeneration;
|
|
13171
|
+
}
|
|
13172
|
+
delay(ms) {
|
|
13173
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13174
|
+
}
|
|
13159
13175
|
/**
|
|
13160
13176
|
* Applies merged route + parent-scope filters to the widget value and data source.
|
|
13161
13177
|
*/
|
|
13162
13178
|
applyMergedRouteFiltersToList() {
|
|
13163
|
-
void this.reapplyScopedFiltersToDataSource();
|
|
13179
|
+
void this.reapplyScopedFiltersToDataSource({ triggerInitialLoad: true });
|
|
13164
13180
|
}
|
|
13165
|
-
/**
|
|
13166
|
-
|
|
13181
|
+
/**
|
|
13182
|
+
* Syncs toolbar filters to context and AXDataSource (retries until data-list is mounted).
|
|
13183
|
+
* Stale generations (destroy / newer op) exit without touching the grid.
|
|
13184
|
+
*/
|
|
13185
|
+
async reapplyScopedFiltersToDataSource(options) {
|
|
13186
|
+
const generation = options?.generation ?? this.beginOp();
|
|
13187
|
+
const triggerInitialLoad = options?.triggerInitialLoad !== false;
|
|
13167
13188
|
const merged = this.getMergedToolbarFilters();
|
|
13168
13189
|
const current = this.getValue();
|
|
13169
13190
|
if (!isEqual(current?.toolbar?.filters, merged)) {
|
|
@@ -13172,26 +13193,64 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13172
13193
|
toolbar: { ...(current?.toolbar ?? {}), filters: merged },
|
|
13173
13194
|
});
|
|
13174
13195
|
}
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13196
|
+
if (this.isOpStale(generation)) {
|
|
13197
|
+
return;
|
|
13198
|
+
}
|
|
13199
|
+
const pushed = await this.ensureToolbarFiltersOnDataSource(generation);
|
|
13200
|
+
if (!pushed || this.isOpStale(generation)) {
|
|
13201
|
+
return;
|
|
13202
|
+
}
|
|
13203
|
+
if (triggerInitialLoad) {
|
|
13204
|
+
await this.triggerInitialLoadIfNeeded(generation);
|
|
13178
13205
|
}
|
|
13179
13206
|
}
|
|
13180
13207
|
/**
|
|
13181
13208
|
* Entity lists use `fetchDataMode: 'manual'`; after scoped filters reach the data source,
|
|
13182
|
-
* the grid must be refreshed once
|
|
13209
|
+
* the grid must be refreshed once. Retries until the data-table view child accepts refresh
|
|
13210
|
+
* so rapid relatedEntity page switches cannot leave the table on "no data loaded yet".
|
|
13183
13211
|
*/
|
|
13184
|
-
triggerInitialLoadIfNeeded() {
|
|
13185
|
-
if (this.isMounted()) {
|
|
13212
|
+
async triggerInitialLoadIfNeeded(generation) {
|
|
13213
|
+
if (this.isMounted() || this.isOpStale(generation)) {
|
|
13186
13214
|
return;
|
|
13187
13215
|
}
|
|
13188
|
-
const
|
|
13189
|
-
if (!
|
|
13190
|
-
|
|
13216
|
+
const refreshed = await this.refreshWhenGridReady(generation);
|
|
13217
|
+
if (refreshed && !this.isOpStale(generation)) {
|
|
13218
|
+
this.isMounted.set(true);
|
|
13219
|
+
}
|
|
13220
|
+
}
|
|
13221
|
+
/**
|
|
13222
|
+
* Calls the unpatched data-list refresh until the grid is ready or the op is stale.
|
|
13223
|
+
*/
|
|
13224
|
+
async refreshWhenGridReady(generation) {
|
|
13225
|
+
return this.refreshWhenGridReadyWith(generation, () => this.resolveDataListRefresh()?.() ?? false);
|
|
13226
|
+
}
|
|
13227
|
+
/**
|
|
13228
|
+
* Prefer the bound original refresh (pre-patch). Falls back to an unpatched instance refresh.
|
|
13229
|
+
*/
|
|
13230
|
+
resolveDataListRefresh() {
|
|
13231
|
+
if (this.boundDataListRefresh) {
|
|
13232
|
+
return this.boundDataListRefresh;
|
|
13233
|
+
}
|
|
13234
|
+
const inst = this.listWidget()?.instance;
|
|
13235
|
+
if (!inst?.refresh || inst.__axpEntityListRefreshPatched) {
|
|
13236
|
+
return null;
|
|
13237
|
+
}
|
|
13238
|
+
return () => inst.refresh?.() ?? false;
|
|
13239
|
+
}
|
|
13240
|
+
async refreshWhenGridReadyWith(generation, refreshFn) {
|
|
13241
|
+
const maxAttempts = 60;
|
|
13242
|
+
const delayMs = 50;
|
|
13243
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
13244
|
+
if (this.isOpStale(generation)) {
|
|
13245
|
+
return false;
|
|
13246
|
+
}
|
|
13247
|
+
this.pushToolbarFiltersToDataSource();
|
|
13248
|
+
if (refreshFn()) {
|
|
13249
|
+
return true;
|
|
13250
|
+
}
|
|
13251
|
+
await this.delay(delayMs);
|
|
13191
13252
|
}
|
|
13192
|
-
|
|
13193
|
-
listInstance.call('refresh');
|
|
13194
|
-
this.isMounted.set(true);
|
|
13253
|
+
return false;
|
|
13195
13254
|
}
|
|
13196
13255
|
/**
|
|
13197
13256
|
* Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
|
|
@@ -13209,7 +13268,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13209
13268
|
value: await this.expressionEvaluator.evaluate(c.value, scope),
|
|
13210
13269
|
hidden: true,
|
|
13211
13270
|
})));
|
|
13212
|
-
if (!this.fullPath()) {
|
|
13271
|
+
if (this.isViewDestroyed || !this.fullPath()) {
|
|
13213
13272
|
return;
|
|
13214
13273
|
}
|
|
13215
13274
|
const current = this.getValue();
|
|
@@ -13241,14 +13300,18 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13241
13300
|
* Pushes toolbar filters once the embedded data-list exposes its data source
|
|
13242
13301
|
* (listNode is created via deferred setTimeout in ngOnInit).
|
|
13243
13302
|
*/
|
|
13244
|
-
async ensureToolbarFiltersOnDataSource() {
|
|
13303
|
+
async ensureToolbarFiltersOnDataSource(generation) {
|
|
13304
|
+
const opGeneration = generation ?? this.opGeneration;
|
|
13245
13305
|
if (this.pushToolbarFiltersToDataSource()) {
|
|
13246
13306
|
return true;
|
|
13247
13307
|
}
|
|
13248
13308
|
const maxAttempts = 40;
|
|
13249
13309
|
const delayMs = 50;
|
|
13250
13310
|
for (let i = 0; i < maxAttempts; i++) {
|
|
13251
|
-
await
|
|
13311
|
+
await this.delay(delayMs);
|
|
13312
|
+
if (this.isOpStale(opGeneration)) {
|
|
13313
|
+
return false;
|
|
13314
|
+
}
|
|
13252
13315
|
if (this.pushToolbarFiltersToDataSource()) {
|
|
13253
13316
|
return true;
|
|
13254
13317
|
}
|
|
@@ -13259,8 +13322,12 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13259
13322
|
* Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
|
|
13260
13323
|
*/
|
|
13261
13324
|
async applyRelatedFiltersFromContextAndDatasource(specs) {
|
|
13325
|
+
const generation = this.beginOp();
|
|
13262
13326
|
await this.applyRelatedFiltersFromContext(specs);
|
|
13263
|
-
|
|
13327
|
+
if (this.isOpStale(generation)) {
|
|
13328
|
+
return;
|
|
13329
|
+
}
|
|
13330
|
+
await this.ensureToolbarFiltersOnDataSource(generation);
|
|
13264
13331
|
}
|
|
13265
13332
|
/**
|
|
13266
13333
|
* Refreshes the embedded data list (toolbar / workflow). In wizard mode, `refresh` is patched to re-apply scoped filters first.
|
|
@@ -13270,6 +13337,31 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13270
13337
|
}
|
|
13271
13338
|
/** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
|
|
13272
13339
|
#patchDataListRefreshEffect;
|
|
13340
|
+
/**
|
|
13341
|
+
* Re-applies parent-scoped filters then refreshes the grid, ignoring stale overlapping calls.
|
|
13342
|
+
*/
|
|
13343
|
+
async runScopedDataListRefresh(originalRefresh) {
|
|
13344
|
+
const generation = this.beginOp();
|
|
13345
|
+
const o = this.options();
|
|
13346
|
+
const specs = o['relatedFilterConditionSpecs'];
|
|
13347
|
+
if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
|
|
13348
|
+
await this.applyRelatedFiltersFromContext(specs);
|
|
13349
|
+
if (this.isOpStale(generation)) {
|
|
13350
|
+
return;
|
|
13351
|
+
}
|
|
13352
|
+
await this.ensureToolbarFiltersOnDataSource(generation);
|
|
13353
|
+
}
|
|
13354
|
+
else {
|
|
13355
|
+
await this.reapplyScopedFiltersToDataSource({ triggerInitialLoad: false, generation });
|
|
13356
|
+
}
|
|
13357
|
+
if (this.isOpStale(generation)) {
|
|
13358
|
+
return;
|
|
13359
|
+
}
|
|
13360
|
+
const refreshed = await this.refreshWhenGridReadyWith(generation, originalRefresh);
|
|
13361
|
+
if (refreshed && !this.isOpStale(generation) && !this.isMounted()) {
|
|
13362
|
+
this.isMounted.set(true);
|
|
13363
|
+
}
|
|
13364
|
+
}
|
|
13273
13365
|
#effect;
|
|
13274
13366
|
/**
|
|
13275
13367
|
* Validates that all required dependencies are available
|
|
@@ -13375,6 +13467,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13375
13467
|
}
|
|
13376
13468
|
// Resolve entity and set it in the signal
|
|
13377
13469
|
const resolvedEntity = await this.entityService.resolve(moduleName, entityName);
|
|
13470
|
+
if (this.isViewDestroyed) {
|
|
13471
|
+
return;
|
|
13472
|
+
}
|
|
13378
13473
|
if (!resolvedEntity) {
|
|
13379
13474
|
throw new Error(`Entity not found: ${this.entitySource()}`);
|
|
13380
13475
|
}
|
|
@@ -13387,8 +13482,18 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13387
13482
|
excludeProperties: this.options()['excludeProperties'],
|
|
13388
13483
|
};
|
|
13389
13484
|
const listOptions = await this.entityListTableService.convertEntityToListOptions(resolvedEntity, options, this.allActions());
|
|
13485
|
+
if (this.isViewDestroyed) {
|
|
13486
|
+
return;
|
|
13487
|
+
}
|
|
13390
13488
|
const toolbarOptions = await this.entityListToolbarService.convertEntityToolbarOptions(resolvedEntity, options);
|
|
13391
|
-
|
|
13489
|
+
if (this.isViewDestroyed) {
|
|
13490
|
+
return;
|
|
13491
|
+
}
|
|
13492
|
+
this.initListTimeoutId = setTimeout(() => {
|
|
13493
|
+
this.initListTimeoutId = null;
|
|
13494
|
+
if (this.isViewDestroyed) {
|
|
13495
|
+
return;
|
|
13496
|
+
}
|
|
13392
13497
|
this.listNode.set({
|
|
13393
13498
|
type: AXPWidgetsCatalog.dataList,
|
|
13394
13499
|
options: listOptions,
|
|
@@ -13410,7 +13515,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13410
13515
|
},
|
|
13411
13516
|
});
|
|
13412
13517
|
queueMicrotask(() => {
|
|
13413
|
-
this.
|
|
13518
|
+
if (!this.isViewDestroyed) {
|
|
13519
|
+
this.applyMergedRouteFiltersToList();
|
|
13520
|
+
}
|
|
13414
13521
|
});
|
|
13415
13522
|
}, 100);
|
|
13416
13523
|
}
|
|
@@ -13435,6 +13542,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13435
13542
|
}
|
|
13436
13543
|
});
|
|
13437
13544
|
const listWidget = (await this.layoutService.waitForWidget(`${this.entitySource()}-tab-list_table`, 500));
|
|
13545
|
+
if (this.isViewDestroyed) {
|
|
13546
|
+
return;
|
|
13547
|
+
}
|
|
13438
13548
|
if (listWidget?.api && typeof listWidget.api === 'function') {
|
|
13439
13549
|
const onSelectionChange = listWidget.api()['onSelectionChange'];
|
|
13440
13550
|
if (onSelectionChange) {
|
|
@@ -13445,6 +13555,13 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13445
13555
|
}
|
|
13446
13556
|
}
|
|
13447
13557
|
ngOnDestroy() {
|
|
13558
|
+
this.isViewDestroyed = true;
|
|
13559
|
+
this.beginOp();
|
|
13560
|
+
if (this.initListTimeoutId != null) {
|
|
13561
|
+
clearTimeout(this.initListTimeoutId);
|
|
13562
|
+
this.initListTimeoutId = null;
|
|
13563
|
+
}
|
|
13564
|
+
this.boundDataListRefresh = null;
|
|
13448
13565
|
this.listWidget.set(undefined);
|
|
13449
13566
|
this.destroyed.next();
|
|
13450
13567
|
this.destroyed.complete();
|