@elderbyte/ngx-starter 21.11.0-beta.0 → 21.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,8 +6,8 @@ import * as i1 from '@angular/platform-browser';
|
|
|
6
6
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
7
7
|
import { Duration, Period, TemporalQueries, LocalTime, Instant, LocalDate, nativeJs, ZoneId, DateTimeFormatter, convert, ZonedDateTime, Temporal as Temporal$1 } from '@js-joda/core';
|
|
8
8
|
import { LoggerFactory } from '@elderbyte/ts-logger';
|
|
9
|
-
import { timer, defer, ReplaySubject, concat, finalize, exhaustMap, BehaviorSubject, Subject,
|
|
10
|
-
import { tap, takeUntil, takeWhile, map, filter, distinctUntilChanged, debounceTime, catchError, first, take, switchMap as switchMap$1, mergeMap, expand, reduce, combineLatestWith, startWith, skip, delay, share,
|
|
9
|
+
import { timer, defer, ReplaySubject, concat, finalize, exhaustMap, BehaviorSubject, Subject, of, throwError, switchMap, combineLatest, EMPTY, merge, forkJoin, mergeWith, Observable, from, toArray, zip, mergeMap as mergeMap$1, fromEvent, mergeAll, skipUntil, filter as filter$1, map as map$1, combineLatestWith as combineLatestWith$1, tap as tap$1, takeUntil as takeUntil$1, NEVER } from 'rxjs';
|
|
10
|
+
import { tap, takeUntil, takeWhile, map, filter, distinctUntilChanged, debounceTime, catchError, first, take, switchMap as switchMap$1, mergeMap, expand, reduce, combineLatestWith, startWith, skip, delay, share, debounce, timeout, skipWhile } from 'rxjs/operators';
|
|
11
11
|
import { Temporal } from '@js-temporal/polyfill';
|
|
12
12
|
import * as i1$1 from '@angular/common/http';
|
|
13
13
|
import { HttpParams, HttpEventType, HttpRequest, HttpClient, HttpErrorResponse, HTTP_INTERCEPTORS, HttpBackend } from '@angular/common/http';
|
|
@@ -4082,1814 +4082,1806 @@ class SortContext {
|
|
|
4082
4082
|
}
|
|
4083
4083
|
}
|
|
4084
4084
|
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
**************************************************************************/
|
|
4090
|
-
function isDataSource(object) {
|
|
4091
|
-
if (!object) {
|
|
4092
|
-
return false;
|
|
4085
|
+
class DataSourceEntityPatch {
|
|
4086
|
+
constructor(entityId, patch) {
|
|
4087
|
+
this.entityId = entityId;
|
|
4088
|
+
this.patch = patch;
|
|
4093
4089
|
}
|
|
4094
|
-
return (object.dataChanged !== undefined &&
|
|
4095
|
-
object.findById !== undefined &&
|
|
4096
|
-
object.getId !== undefined);
|
|
4097
|
-
}
|
|
4098
|
-
function isListDataSource(object) {
|
|
4099
|
-
return object.findAllFiltered !== undefined;
|
|
4100
|
-
}
|
|
4101
|
-
function isLocalListDataSource(object) {
|
|
4102
|
-
return (isListDataSource(object) &&
|
|
4103
|
-
object.delete !== undefined &&
|
|
4104
|
-
object.save !== undefined &&
|
|
4105
|
-
object.replaceAll !== undefined);
|
|
4106
4090
|
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
return
|
|
4091
|
+
/**
|
|
4092
|
+
* Notifies about changes in a DataSource.
|
|
4093
|
+
*/
|
|
4094
|
+
class DataSourceChangeEvent {
|
|
4095
|
+
static unknownChanges() {
|
|
4096
|
+
return new DataSourceChangeEvent(null, null, null, null);
|
|
4097
|
+
}
|
|
4098
|
+
static deleted(ids) {
|
|
4099
|
+
return new DataSourceChangeEvent(ids, null, null, null);
|
|
4100
|
+
}
|
|
4101
|
+
static modified(modified) {
|
|
4102
|
+
return new DataSourceChangeEvent(null, modified, null, null);
|
|
4103
|
+
}
|
|
4104
|
+
static created(created) {
|
|
4105
|
+
return new DataSourceChangeEvent(null, null, created, null);
|
|
4106
|
+
}
|
|
4107
|
+
static patched(patches) {
|
|
4108
|
+
return new DataSourceChangeEvent(null, null, null, patches);
|
|
4109
|
+
}
|
|
4110
|
+
constructor(deletedIds, modified, created, patches) {
|
|
4111
|
+
this.deletedIds = deletedIds;
|
|
4112
|
+
this.modified = modified;
|
|
4113
|
+
this.created = created;
|
|
4114
|
+
this.patches = patches;
|
|
4115
|
+
this.unknownChanges = !deletedIds && !modified && !created && !patches;
|
|
4116
4116
|
}
|
|
4117
|
-
return isLocalListDataSource(object) || isLocalPagedDataSource(object);
|
|
4118
|
-
}
|
|
4119
|
-
function isPagedDataSource(object) {
|
|
4120
|
-
return object.findAllPaged !== undefined;
|
|
4121
|
-
}
|
|
4122
|
-
function isContinuableDataSource(object) {
|
|
4123
|
-
return object.findAllContinuable !== undefined;
|
|
4124
4117
|
}
|
|
4125
4118
|
|
|
4126
|
-
class
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4119
|
+
class EntityIdUtil {
|
|
4120
|
+
static getId(entity, idProperty) {
|
|
4121
|
+
if (entity && idProperty) {
|
|
4122
|
+
if (typeof entity === 'object') {
|
|
4123
|
+
return entity?.[idProperty];
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
return entity;
|
|
4130
4127
|
}
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4128
|
+
static extractIdFnOrProperty(idPropertyOrExtractFn) {
|
|
4129
|
+
if (typeof idPropertyOrExtractFn === 'string') {
|
|
4130
|
+
return EntityIdUtil.extractIdFn(idPropertyOrExtractFn);
|
|
4131
|
+
}
|
|
4132
|
+
else if (typeof idPropertyOrExtractFn === 'function') {
|
|
4133
|
+
return idPropertyOrExtractFn;
|
|
4134
|
+
}
|
|
4135
|
+
else if (idPropertyOrExtractFn === null || idPropertyOrExtractFn === undefined) {
|
|
4136
|
+
return EntityIdUtil.extractIdFn(null);
|
|
4137
|
+
}
|
|
4138
|
+
else {
|
|
4139
|
+
throw new Error('Invalid idPropertyOrExtractFn');
|
|
4140
|
+
}
|
|
4136
4141
|
}
|
|
4137
|
-
|
|
4138
|
-
return
|
|
4142
|
+
static extractIdFn(idProperty) {
|
|
4143
|
+
return (entity) => EntityIdUtil.getId(entity, idProperty);
|
|
4139
4144
|
}
|
|
4140
4145
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
* *
|
|
4144
|
-
* Fields *
|
|
4145
|
-
* *
|
|
4146
|
-
**************************************************************************/
|
|
4147
|
-
static { this.DC_ID_COUNTER = 0; }
|
|
4146
|
+
|
|
4147
|
+
class DataSourceBase {
|
|
4148
4148
|
/***************************************************************************
|
|
4149
4149
|
* *
|
|
4150
4150
|
* Constructor *
|
|
4151
4151
|
* *
|
|
4152
4152
|
**************************************************************************/
|
|
4153
|
-
constructor(
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
this.
|
|
4160
|
-
this.
|
|
4161
|
-
this._total = new BehaviorSubject(undefined);
|
|
4162
|
-
this._status = new BehaviorSubject(DataContextStatus.idle());
|
|
4163
|
-
this._started = new BehaviorSubject(false);
|
|
4164
|
-
this._closed = new BehaviorSubject(false);
|
|
4165
|
-
this._customIndex = new Map();
|
|
4166
|
-
this._reloadCounter = 0;
|
|
4167
|
-
this._reloadQueue = new Subject();
|
|
4168
|
-
this._reloaded = new Subject();
|
|
4169
|
-
this.destroy$ = new Subject();
|
|
4170
|
-
this.id = 'DataContext#' + ++DataContextBase.DC_ID_COUNTER;
|
|
4171
|
-
this._dataSource = dataSource;
|
|
4172
|
-
this._data = new IndexedEntities((e) => dataSource.getId(e), _localSort, this._sort);
|
|
4173
|
-
this._loading = this._status.pipe(map((status) => status.loading));
|
|
4174
|
-
this._filter.filters
|
|
4175
|
-
.pipe(filter(() => this.started), takeUntil(this.destroy$))
|
|
4176
|
-
.subscribe((filters) => this.onFiltersChanged(filters));
|
|
4177
|
-
this._sort.sorts
|
|
4178
|
-
.pipe(filter(() => this.started), takeUntil(this.destroy$))
|
|
4179
|
-
.subscribe((sorts) => this.onSortsChanged(sorts));
|
|
4180
|
-
this._reloadQueue
|
|
4181
|
-
.pipe(takeUntil(this.destroy$), filter((request) => this.started), debounceTime(this.getDebounceTime()), switchMap((request) => this.reloadNow(request).pipe(map((result) => new AfterReload(request, result)), catchError((err) => {
|
|
4182
|
-
// Dont die on errors
|
|
4183
|
-
this.baselog.error(this.id + ': Reload queue detected error, bad!', err);
|
|
4184
|
-
return of(new AfterReload(request, err));
|
|
4185
|
-
}))))
|
|
4186
|
-
.subscribe((afterReload) => this._reloaded.next(afterReload));
|
|
4153
|
+
constructor(propertyOrIdExtractor) {
|
|
4154
|
+
/***************************************************************************
|
|
4155
|
+
* *
|
|
4156
|
+
* Fields *
|
|
4157
|
+
* *
|
|
4158
|
+
**************************************************************************/
|
|
4159
|
+
this.dataChangeEvents$ = new Subject();
|
|
4160
|
+
this.extractIdFn = EntityIdUtil.extractIdFnOrProperty(propertyOrIdExtractor);
|
|
4187
4161
|
}
|
|
4188
4162
|
/***************************************************************************
|
|
4189
4163
|
* *
|
|
4190
|
-
*
|
|
4164
|
+
* Properties *
|
|
4191
4165
|
* *
|
|
4192
4166
|
**************************************************************************/
|
|
4193
|
-
|
|
4194
|
-
return this.
|
|
4167
|
+
get dataChanged() {
|
|
4168
|
+
return this.dataChangeEvents$.asObservable();
|
|
4195
4169
|
}
|
|
4196
|
-
disconnect(collectionViewer) { }
|
|
4197
4170
|
/***************************************************************************
|
|
4198
4171
|
* *
|
|
4199
|
-
*
|
|
4172
|
+
* Public API *
|
|
4200
4173
|
* *
|
|
4201
4174
|
**************************************************************************/
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
}
|
|
4205
|
-
get snapshot() {
|
|
4206
|
-
return new DataContextSnapshot(this.dataSnapshot, this.isEmpty, this.loadingSnapshot, this.totalSnapshot, this.statusSnapshot);
|
|
4207
|
-
}
|
|
4208
|
-
get total() {
|
|
4209
|
-
return this._total.asObservable();
|
|
4210
|
-
}
|
|
4211
|
-
get totalSnapshot() {
|
|
4212
|
-
return this._total.getValue();
|
|
4175
|
+
publishChangeEvent(e) {
|
|
4176
|
+
this.dataChangeEvents$.next(e);
|
|
4213
4177
|
}
|
|
4214
|
-
|
|
4215
|
-
return this.
|
|
4178
|
+
getId(entity) {
|
|
4179
|
+
return this.extractIdFn(entity);
|
|
4216
4180
|
}
|
|
4217
|
-
|
|
4218
|
-
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
class SortUtil {
|
|
4184
|
+
static toggleDir(dir) {
|
|
4185
|
+
if (dir === 'asc') {
|
|
4186
|
+
return 'desc';
|
|
4187
|
+
}
|
|
4188
|
+
else {
|
|
4189
|
+
return 'asc';
|
|
4190
|
+
}
|
|
4219
4191
|
}
|
|
4220
|
-
|
|
4221
|
-
return
|
|
4192
|
+
static toggleSort(sort) {
|
|
4193
|
+
return new Sort(sort.prop, SortUtil.toggleDir(sort.dir));
|
|
4222
4194
|
}
|
|
4223
|
-
|
|
4224
|
-
|
|
4195
|
+
static sortData(data, sorts, prefix) {
|
|
4196
|
+
if (sorts && sorts.length > 0) {
|
|
4197
|
+
const copy = [...data];
|
|
4198
|
+
const sortFields = sorts.map((s) => (s.dir === 'desc' ? '-' : '') + SortUtil.propertyPath(s, prefix));
|
|
4199
|
+
return copy.sort(ComparatorBuilder.fieldSort(...sortFields));
|
|
4200
|
+
}
|
|
4201
|
+
else {
|
|
4202
|
+
return data;
|
|
4203
|
+
}
|
|
4225
4204
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4205
|
+
/**
|
|
4206
|
+
* Checks if the two arrays have all content references in the exact same order.
|
|
4207
|
+
* @param data
|
|
4208
|
+
* @param other
|
|
4209
|
+
*/
|
|
4210
|
+
static equalsExactRefs(data, other) {
|
|
4211
|
+
// eslint-disable-next-line eqeqeq
|
|
4212
|
+
if (data.length == other.length) {
|
|
4213
|
+
for (let i = 0; i < data.length; i++) {
|
|
4214
|
+
// eslint-disable-next-line eqeqeq
|
|
4215
|
+
if (data[i] != other[i]) {
|
|
4216
|
+
return false;
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
return true;
|
|
4220
|
+
}
|
|
4221
|
+
return false;
|
|
4228
4222
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4223
|
+
static propertyPath(sort, prefix) {
|
|
4224
|
+
if (prefix) {
|
|
4225
|
+
return prefix + '.' + sort.prop;
|
|
4226
|
+
}
|
|
4227
|
+
else {
|
|
4228
|
+
return sort.prop;
|
|
4229
|
+
}
|
|
4231
4230
|
}
|
|
4232
|
-
|
|
4233
|
-
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
class MapUtils {
|
|
4234
|
+
static toDistinctMap(values, keyFn) {
|
|
4235
|
+
return values.reduce((map, value) => {
|
|
4236
|
+
map.set(keyFn(value), value);
|
|
4237
|
+
return map;
|
|
4238
|
+
}, new Map());
|
|
4234
4239
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4240
|
+
static groupByKey(values, keyFn) {
|
|
4241
|
+
const groups = new Map();
|
|
4242
|
+
values.forEach((value) => {
|
|
4243
|
+
const key = keyFn(value);
|
|
4244
|
+
let group = groups.get(key);
|
|
4245
|
+
if (!group) {
|
|
4246
|
+
group = [];
|
|
4247
|
+
groups.set(key, group);
|
|
4248
|
+
}
|
|
4249
|
+
group.push(value);
|
|
4250
|
+
});
|
|
4251
|
+
return groups;
|
|
4237
4252
|
}
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
return this.started;
|
|
4243
|
-
}
|
|
4244
|
-
get isStarted$() {
|
|
4245
|
-
return this._started.asObservable();
|
|
4246
|
-
}
|
|
4247
|
-
get isClosed() {
|
|
4248
|
-
return this._closed.getValue();
|
|
4249
|
-
}
|
|
4250
|
-
get started() {
|
|
4251
|
-
return this._started.getValue();
|
|
4252
|
-
}
|
|
4253
|
-
set started(started) {
|
|
4254
|
-
this._started.next(started);
|
|
4255
|
-
}
|
|
4256
|
-
/***************************************************************************
|
|
4257
|
-
* *
|
|
4258
|
-
* Public API *
|
|
4259
|
-
* *
|
|
4260
|
-
**************************************************************************/
|
|
4261
|
-
start(sorts, filters) {
|
|
4262
|
-
if (this.isClosed) {
|
|
4263
|
-
throw new Error(this.id + ': Restarting a closed DataContext is not possible!'); // See unsubscribe$
|
|
4264
|
-
}
|
|
4265
|
-
this.started = false;
|
|
4266
|
-
this._sort.replaceSorts(sorts || this.sort.sortsSnapshot);
|
|
4267
|
-
this._filter.replaceFilters(filters || this.filter.filtersSnapshot);
|
|
4268
|
-
this.baselog.debug(this.id + ': Start ...', {
|
|
4269
|
-
dataSource: this.dataSource,
|
|
4270
|
-
sort: this._sort.sortsSnapshot,
|
|
4271
|
-
filter: this._filter.filtersSnapshot,
|
|
4272
|
-
});
|
|
4273
|
-
this.started = true;
|
|
4274
|
-
return this.reload('START');
|
|
4275
|
-
}
|
|
4276
|
-
reload(reason) {
|
|
4277
|
-
const request = new ReloadRequest(++this._reloadCounter, reason);
|
|
4278
|
-
this.baselog.debug(this.id + ': Enqueuing reload request #' + request.number + ' [' + reason + ']');
|
|
4279
|
-
this._reloadQueue.next(request);
|
|
4280
|
-
return this._reloaded.pipe(filter((reload) => reload.fulfills(request)), map((reload) => reload.result));
|
|
4281
|
-
}
|
|
4282
|
-
findByIndex(key) {
|
|
4283
|
-
if (!this._indexFn) {
|
|
4284
|
-
throw new Error(this.id + ': findByIndex requires you to pass a index function!');
|
|
4285
|
-
}
|
|
4286
|
-
return this._customIndex.get(key);
|
|
4287
|
-
}
|
|
4288
|
-
findById(id) {
|
|
4289
|
-
return this._data.getById(id);
|
|
4290
|
-
}
|
|
4291
|
-
/**
|
|
4292
|
-
* Closes this DataContext and cleans up all used resources.
|
|
4293
|
-
*/
|
|
4294
|
-
close() {
|
|
4295
|
-
this.started = false;
|
|
4296
|
-
this.destroy$.next();
|
|
4297
|
-
this.destroy$.complete();
|
|
4298
|
-
this._closed.next(true);
|
|
4299
|
-
this._data.destroy();
|
|
4300
|
-
this._total.complete();
|
|
4301
|
-
this._status.complete();
|
|
4302
|
-
this.clearAll();
|
|
4303
|
-
this.baselog.debug(this.id + ': Has been closed and resources cleaned up!');
|
|
4304
|
-
}
|
|
4305
|
-
refresh() {
|
|
4306
|
-
this._data.notify();
|
|
4307
|
-
}
|
|
4308
|
-
update(entities) {
|
|
4309
|
-
this._data.updateSome(entities);
|
|
4253
|
+
static mapValue(map, valueMapFn) {
|
|
4254
|
+
const newMap = new Map();
|
|
4255
|
+
Array.from(map.entries()).forEach(([key, value]) => newMap.set(key, valueMapFn(value)));
|
|
4256
|
+
return newMap;
|
|
4310
4257
|
}
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
class LocalListDataSource extends DataSourceBase {
|
|
4311
4261
|
/***************************************************************************
|
|
4312
4262
|
* *
|
|
4313
|
-
*
|
|
4263
|
+
* Static Builder *
|
|
4314
4264
|
* *
|
|
4315
4265
|
**************************************************************************/
|
|
4316
|
-
reloadNow(request) {
|
|
4317
|
-
this.baselog.debug(this.id + ': reloadNow triggered. Request #' + request.number + ' ' + request.reason, {
|
|
4318
|
-
filter: this._filter.filtersSnapshot,
|
|
4319
|
-
sort: this._sort.sortsSnapshot,
|
|
4320
|
-
dataSource: this.dataSource,
|
|
4321
|
-
request: request,
|
|
4322
|
-
});
|
|
4323
|
-
return this.reloadInternal(); // TODO This should actually return the real (http) request
|
|
4324
|
-
}
|
|
4325
|
-
/**
|
|
4326
|
-
* Append the given rows to the existing ones.
|
|
4327
|
-
*/
|
|
4328
|
-
appendData(additionalData) {
|
|
4329
|
-
additionalData = this.localApply(additionalData);
|
|
4330
|
-
this.indexAll(additionalData);
|
|
4331
|
-
this._data.append(additionalData);
|
|
4332
|
-
}
|
|
4333
|
-
/**
|
|
4334
|
-
* Insert the given rows at the given location.
|
|
4335
|
-
*/
|
|
4336
|
-
insertData(additionalData, offset) {
|
|
4337
|
-
additionalData = this.localApply(additionalData);
|
|
4338
|
-
this.indexAll(additionalData);
|
|
4339
|
-
this._data.insert(additionalData, offset);
|
|
4340
|
-
}
|
|
4341
|
-
/**
|
|
4342
|
-
* Replaces the existing data with the new set.
|
|
4343
|
-
* @param newData The new data set.
|
|
4344
|
-
* @param alreadyIndexed The rows will be indexed unless they already have been.
|
|
4345
|
-
*/
|
|
4346
|
-
setData(newData, alreadyIndexed = false) {
|
|
4347
|
-
newData = this.localApply(newData);
|
|
4348
|
-
if (!alreadyIndexed) {
|
|
4349
|
-
this.clearIndex();
|
|
4350
|
-
this.indexAll(newData);
|
|
4351
|
-
}
|
|
4352
|
-
this._data.replaceAll(newData);
|
|
4353
|
-
}
|
|
4354
|
-
clearData() {
|
|
4355
|
-
this.setData([]);
|
|
4356
|
-
}
|
|
4357
|
-
localApply(data) {
|
|
4358
|
-
if (this._localApply) {
|
|
4359
|
-
return this._localApply(data);
|
|
4360
|
-
}
|
|
4361
|
-
else {
|
|
4362
|
-
return data;
|
|
4363
|
-
}
|
|
4364
|
-
}
|
|
4365
|
-
setTotal(total) {
|
|
4366
|
-
this._total.next(total);
|
|
4367
|
-
}
|
|
4368
4266
|
/**
|
|
4369
|
-
*
|
|
4370
|
-
*
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
if (!silent) {
|
|
4375
|
-
this.setTotal(0);
|
|
4376
|
-
this.clearData();
|
|
4377
|
-
}
|
|
4378
|
-
this.onIdle();
|
|
4379
|
-
}
|
|
4380
|
-
updateIndex() {
|
|
4381
|
-
this.clearIndex();
|
|
4382
|
-
this.indexAll(this.dataSnapshot);
|
|
4383
|
-
}
|
|
4384
|
-
/**
|
|
4385
|
-
* Indexes the given item by key if there is an index function defined.
|
|
4386
|
-
*/
|
|
4387
|
-
indexItem(item) {
|
|
4388
|
-
const key = this.getItemKey(item);
|
|
4389
|
-
if (key) {
|
|
4390
|
-
this._customIndex.set(key, item);
|
|
4391
|
-
}
|
|
4392
|
-
}
|
|
4393
|
-
/**
|
|
4394
|
-
* Indexes all the given items by key if there is an index function defined.
|
|
4267
|
+
* Creates an empty local list data-source.
|
|
4268
|
+
* You can set / modify data by using the data property.
|
|
4269
|
+
* @param idPropertyOrExtractor
|
|
4270
|
+
* @param localSort
|
|
4271
|
+
* @param localFilter
|
|
4395
4272
|
*/
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
items.forEach((item) => this.indexItem(item));
|
|
4399
|
-
}
|
|
4400
|
-
}
|
|
4401
|
-
getItemKey(item) {
|
|
4402
|
-
if (this._indexFn) {
|
|
4403
|
-
return this._indexFn(item);
|
|
4404
|
-
}
|
|
4405
|
-
return null;
|
|
4406
|
-
}
|
|
4407
|
-
getItemId(item) {
|
|
4408
|
-
return this.dataSource.getId(item);
|
|
4409
|
-
}
|
|
4410
|
-
getDebounceTime() {
|
|
4411
|
-
if (isLocalDataSource(this.dataSource)) {
|
|
4412
|
-
return 0;
|
|
4413
|
-
}
|
|
4414
|
-
return 50; // Default debounce time
|
|
4273
|
+
static empty(idPropertyOrExtractor, localSort, localFilter) {
|
|
4274
|
+
return this.from([], idPropertyOrExtractor, localSort, localFilter);
|
|
4415
4275
|
}
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
* *
|
|
4420
|
-
**************************************************************************/
|
|
4421
|
-
/**
|
|
4422
|
-
* Occurs when the sort has changed.
|
|
4423
|
-
*/
|
|
4424
|
-
onSortsChanged(sorts) {
|
|
4425
|
-
if (!this._localSort) {
|
|
4426
|
-
this.reload('Sort Changed');
|
|
4427
|
-
}
|
|
4428
|
-
else {
|
|
4429
|
-
this.setData(this.dataSnapshot, true);
|
|
4276
|
+
static from(localData, idPropertyOrExtractor, localSort, localFilter) {
|
|
4277
|
+
if (idPropertyOrExtractor === null) {
|
|
4278
|
+
idPropertyOrExtractor = LocalListDataSource.guessIdProperty(localData);
|
|
4430
4279
|
}
|
|
4280
|
+
return new LocalListDataSource(localData, localSort, localFilter, idPropertyOrExtractor);
|
|
4431
4281
|
}
|
|
4432
|
-
/**
|
|
4433
|
-
* Occurs when the filter has changed.
|
|
4434
|
-
*/
|
|
4435
|
-
onFiltersChanged(filters) {
|
|
4436
|
-
this.reload('Filters Changed');
|
|
4437
|
-
}
|
|
4438
|
-
onError(err) {
|
|
4439
|
-
this.onStatus(DataContextStatus.error(err));
|
|
4440
|
-
}
|
|
4441
|
-
onIdle() {
|
|
4442
|
-
this.onStatus(DataContextStatus.idle());
|
|
4443
|
-
}
|
|
4444
|
-
onLoading() {
|
|
4445
|
-
this.onStatus(DataContextStatus.loading());
|
|
4446
|
-
}
|
|
4447
|
-
onStatus(status) {
|
|
4448
|
-
this._status.next(status);
|
|
4449
|
-
}
|
|
4450
|
-
/***************************************************************************
|
|
4451
|
-
* *
|
|
4452
|
-
* Private methods *
|
|
4453
|
-
* *
|
|
4454
|
-
**************************************************************************/
|
|
4455
|
-
clearIndex() {
|
|
4456
|
-
this._customIndex.clear();
|
|
4457
|
-
}
|
|
4458
|
-
}
|
|
4459
|
-
|
|
4460
|
-
class DataContextSimple extends DataContextBase {
|
|
4461
4282
|
/***************************************************************************
|
|
4462
4283
|
* *
|
|
4463
4284
|
* Constructor *
|
|
4464
4285
|
* *
|
|
4465
4286
|
**************************************************************************/
|
|
4466
|
-
constructor(
|
|
4467
|
-
super(
|
|
4287
|
+
constructor(localData, localSort, localFilter, idPropertyOrExtractor) {
|
|
4288
|
+
super(idPropertyOrExtractor);
|
|
4468
4289
|
/***************************************************************************
|
|
4469
4290
|
* *
|
|
4470
4291
|
* Fields *
|
|
4471
4292
|
* *
|
|
4472
4293
|
**************************************************************************/
|
|
4473
|
-
this.
|
|
4294
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
4295
|
+
this.data$ = new BehaviorSubject([]);
|
|
4296
|
+
if (!localData) {
|
|
4297
|
+
throw new Error('localData must not be null!');
|
|
4298
|
+
}
|
|
4299
|
+
this.localSort = localSort || SortUtil.sortData;
|
|
4300
|
+
this.localFilter = localFilter || FilterUtil.filterData;
|
|
4301
|
+
this.data = localData;
|
|
4474
4302
|
}
|
|
4475
4303
|
/***************************************************************************
|
|
4476
4304
|
* *
|
|
4477
4305
|
* Properties *
|
|
4478
4306
|
* *
|
|
4479
4307
|
**************************************************************************/
|
|
4480
|
-
get
|
|
4481
|
-
return
|
|
4308
|
+
get data() {
|
|
4309
|
+
return this.data$.getValue();
|
|
4310
|
+
}
|
|
4311
|
+
set data(data) {
|
|
4312
|
+
this.replaceAll(data);
|
|
4482
4313
|
}
|
|
4483
4314
|
/***************************************************************************
|
|
4484
4315
|
* *
|
|
4485
|
-
*
|
|
4316
|
+
* IDataSource API *
|
|
4486
4317
|
* *
|
|
4487
4318
|
**************************************************************************/
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
.subscribe((list) => {
|
|
4496
|
-
this.onIdle();
|
|
4497
|
-
this.setTotal(list.length);
|
|
4498
|
-
this.setData(list);
|
|
4499
|
-
this.log.debug(this.id + ': Got list data: ' + list.length);
|
|
4500
|
-
subject.next();
|
|
4501
|
-
}, (err) => {
|
|
4502
|
-
this.onError(err);
|
|
4503
|
-
this.clearAll();
|
|
4504
|
-
this.log.error(this.id + ': Failed to query data', err);
|
|
4505
|
-
subject.error(err);
|
|
4506
|
-
});
|
|
4319
|
+
findById(id) {
|
|
4320
|
+
if (id === undefined || id === null) {
|
|
4321
|
+
throw new Error('findById: id argument required!');
|
|
4322
|
+
}
|
|
4323
|
+
const found = this.data.find((d) => this.getId(d) === id);
|
|
4324
|
+
if (found) {
|
|
4325
|
+
return of(found);
|
|
4507
4326
|
}
|
|
4508
4327
|
else {
|
|
4509
|
-
|
|
4510
|
-
this.log.warn(errorMsg);
|
|
4511
|
-
subject.error(new Error(errorMsg));
|
|
4328
|
+
return throwError(() => new Error("Could not find local entity by id: '" + id + "'"));
|
|
4512
4329
|
}
|
|
4513
|
-
return subject.pipe(first());
|
|
4514
4330
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
* Constructors *
|
|
4525
|
-
* *
|
|
4526
|
-
**************************************************************************/
|
|
4527
|
-
constructor(dataSource, chunkSize, indexFn, localApply, localSort) {
|
|
4528
|
-
super(dataSource, indexFn, localApply, localSort);
|
|
4529
|
-
/***************************************************************************
|
|
4530
|
-
* *
|
|
4531
|
-
* Fields *
|
|
4532
|
-
* *
|
|
4533
|
-
**************************************************************************/
|
|
4534
|
-
this.cblogger = LoggerFactory.getLogger(this.constructor.name);
|
|
4535
|
-
this._chunkSize$ = new BehaviorSubject(chunkSize);
|
|
4331
|
+
findByIds(ids) {
|
|
4332
|
+
if (ids === undefined || ids === null) {
|
|
4333
|
+
throw new Error('findByIds: ids array argument required!');
|
|
4334
|
+
}
|
|
4335
|
+
const desiredIds = new Set(ids);
|
|
4336
|
+
return of(this.data.filter((d) => desiredIds.has(this.getId(d))));
|
|
4337
|
+
}
|
|
4338
|
+
findAllFiltered(filters, sorts) {
|
|
4339
|
+
return of(this.data).pipe(map((data) => this.localFilter(data, filters)), map((data) => this.localSort(data, sorts)));
|
|
4536
4340
|
}
|
|
4537
4341
|
/***************************************************************************
|
|
4538
4342
|
* *
|
|
4539
4343
|
* Public API *
|
|
4540
4344
|
* *
|
|
4541
4345
|
**************************************************************************/
|
|
4542
|
-
|
|
4543
|
-
this.
|
|
4544
|
-
|
|
4545
|
-
this.start(sorts, filters).subscribe({
|
|
4546
|
-
next: () => {
|
|
4547
|
-
this.cblogger.debug(this.id + ': First page has been loaded. Loading remaining data ...');
|
|
4548
|
-
// load rest in a recursive manner
|
|
4549
|
-
this.loadAllRec();
|
|
4550
|
-
},
|
|
4551
|
-
error: (err) => {
|
|
4552
|
-
this.onError(err);
|
|
4553
|
-
this.cblogger.error(this.id + ': Failed to load first page of load all procedure!', err);
|
|
4554
|
-
},
|
|
4555
|
-
});
|
|
4346
|
+
replaceAll(data) {
|
|
4347
|
+
this.silentReplaceData(data);
|
|
4348
|
+
this.publishChangeEvent(DataSourceChangeEvent.unknownChanges());
|
|
4556
4349
|
}
|
|
4557
|
-
|
|
4558
|
-
|
|
4350
|
+
delete(entity) {
|
|
4351
|
+
this.deleteAll([entity]);
|
|
4559
4352
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4353
|
+
deleteAll(toDelete) {
|
|
4354
|
+
if (toDelete?.length > 0) {
|
|
4355
|
+
return this.deleteAllById(toDelete.map((e) => this.getId(e)));
|
|
4356
|
+
}
|
|
4562
4357
|
}
|
|
4563
|
-
|
|
4564
|
-
|
|
4358
|
+
deleteAllById(idsToDelete) {
|
|
4359
|
+
if (idsToDelete?.length > 0) {
|
|
4360
|
+
const existing = this.data;
|
|
4361
|
+
const idsToDeleteSet = new Set(idsToDelete);
|
|
4362
|
+
this.silentReplaceData(existing.filter((e) => !idsToDeleteSet.has(this.getId(e))));
|
|
4363
|
+
this.publishChangeEvent(DataSourceChangeEvent.deleted(idsToDelete));
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
saveAll(toSave) {
|
|
4367
|
+
const idDataMap = this.buildIdDataMap(this.data);
|
|
4368
|
+
const createdEntities = [];
|
|
4369
|
+
const modifiedEntities = [];
|
|
4370
|
+
toSave.forEach((entity) => {
|
|
4371
|
+
const id = this.getId(entity);
|
|
4372
|
+
if (!idDataMap.has(id)) {
|
|
4373
|
+
createdEntities.push(entity);
|
|
4374
|
+
}
|
|
4375
|
+
else {
|
|
4376
|
+
modifiedEntities.push(entity);
|
|
4377
|
+
}
|
|
4378
|
+
idDataMap.set(id, entity);
|
|
4379
|
+
});
|
|
4380
|
+
this.silentReplaceData(Array.from(idDataMap.values()));
|
|
4381
|
+
this.publishChanges(createdEntities, modifiedEntities);
|
|
4382
|
+
}
|
|
4383
|
+
save(entity, index) {
|
|
4384
|
+
this.saveAtIndex(entity, index);
|
|
4385
|
+
}
|
|
4386
|
+
saveAtIndex(entity, index) {
|
|
4387
|
+
const id = this.getId(entity);
|
|
4388
|
+
const newData = [...this.data];
|
|
4389
|
+
const existingIndex = newData.findIndex((entity) => this.getId(entity) === id);
|
|
4390
|
+
let created = false;
|
|
4391
|
+
if (existingIndex === -1) {
|
|
4392
|
+
if (index !== null) {
|
|
4393
|
+
newData.splice(index, 0, entity);
|
|
4394
|
+
}
|
|
4395
|
+
else {
|
|
4396
|
+
newData.push(entity);
|
|
4397
|
+
}
|
|
4398
|
+
created = true;
|
|
4399
|
+
}
|
|
4400
|
+
else {
|
|
4401
|
+
newData[existingIndex] = entity;
|
|
4402
|
+
}
|
|
4403
|
+
this.silentReplaceData(newData);
|
|
4404
|
+
this.publishChangeEvent(created ? DataSourceChangeEvent.created([entity]) : DataSourceChangeEvent.modified([entity]));
|
|
4565
4405
|
}
|
|
4566
4406
|
/***************************************************************************
|
|
4567
4407
|
* *
|
|
4568
|
-
* Private
|
|
4408
|
+
* Private methods *
|
|
4569
4409
|
* *
|
|
4570
4410
|
**************************************************************************/
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4411
|
+
buildIdDataMap(data) {
|
|
4412
|
+
return MapUtils.toDistinctMap(data, (value) => this.getId(value));
|
|
4413
|
+
}
|
|
4414
|
+
silentReplaceData(newData) {
|
|
4415
|
+
this.data$.next(newData);
|
|
4416
|
+
}
|
|
4417
|
+
static guessIdProperty(localData) {
|
|
4418
|
+
const log = LoggerFactory.getLogger('LocalListDataSource');
|
|
4419
|
+
if (localData && localData.length > 0) {
|
|
4420
|
+
const sample = localData[0];
|
|
4421
|
+
if (typeof sample === 'object') {
|
|
4422
|
+
if (Object.prototype.hasOwnProperty.call(sample, 'id')) {
|
|
4423
|
+
log.warn('DataSource without defined id-property => autodetected property id-property as "id"');
|
|
4424
|
+
return 'id'; // Use id
|
|
4425
|
+
}
|
|
4426
|
+
else {
|
|
4427
|
+
log.warn('Local DataSource created without defined id-property and objects. Using object equality!');
|
|
4428
|
+
}
|
|
4576
4429
|
}
|
|
4577
4430
|
}
|
|
4431
|
+
return null; // Use value as id if scalar
|
|
4578
4432
|
}
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
this.onError(err);
|
|
4587
|
-
this.cblogger.error(this.id + ': Loading all failed!', err);
|
|
4588
|
-
},
|
|
4589
|
-
complete: () => {
|
|
4590
|
-
this.cblogger.info(this.id + ': All data loaded completely.');
|
|
4591
|
-
},
|
|
4592
|
-
});
|
|
4433
|
+
publishChanges(createdEntities, modifiedEntities) {
|
|
4434
|
+
if (createdEntities.length > 0) {
|
|
4435
|
+
this.publishChangeEvent(DataSourceChangeEvent.created(createdEntities));
|
|
4436
|
+
}
|
|
4437
|
+
if (modifiedEntities.length > 0) {
|
|
4438
|
+
this.publishChangeEvent(DataSourceChangeEvent.modified(modifiedEntities));
|
|
4439
|
+
}
|
|
4593
4440
|
}
|
|
4594
4441
|
}
|
|
4595
4442
|
|
|
4596
|
-
|
|
4597
|
-
* Extends a simple flat list data-context with infinite-scroll pagination support.
|
|
4598
|
-
*
|
|
4599
|
-
*/
|
|
4600
|
-
class DataContextContinuablePaged extends DataContextContinuableBase {
|
|
4601
|
-
/***************************************************************************
|
|
4602
|
-
* *
|
|
4603
|
-
* Constructors *
|
|
4604
|
-
* *
|
|
4605
|
-
**************************************************************************/
|
|
4606
|
-
constructor(dataSource, pageSize, indexFn, localApply, localSort) {
|
|
4607
|
-
super(dataSource, pageSize, indexFn, localApply, localSort);
|
|
4608
|
-
/***************************************************************************
|
|
4609
|
-
* *
|
|
4610
|
-
* Fields *
|
|
4611
|
-
* *
|
|
4612
|
-
**************************************************************************/
|
|
4613
|
-
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
4614
|
-
this._pageCache = new Map();
|
|
4615
|
-
this._latestPage = 0;
|
|
4616
|
-
this._hasMoreData = combineLatest([this.total, this.data]).pipe(map(([total, data]) => this.checkHasMoreData(total, data)), takeUntil(this.destroy$));
|
|
4617
|
-
}
|
|
4618
|
-
/***************************************************************************
|
|
4619
|
-
* *
|
|
4620
|
-
* Properties *
|
|
4621
|
-
* *
|
|
4622
|
-
**************************************************************************/
|
|
4623
|
-
get dataSource() {
|
|
4624
|
-
return super.dataSource;
|
|
4625
|
-
}
|
|
4443
|
+
class LocalPagedDataSource {
|
|
4626
4444
|
/***************************************************************************
|
|
4627
4445
|
* *
|
|
4628
|
-
*
|
|
4446
|
+
* Static Builder *
|
|
4629
4447
|
* *
|
|
4630
4448
|
**************************************************************************/
|
|
4631
4449
|
/**
|
|
4632
|
-
*
|
|
4633
|
-
*
|
|
4634
|
-
*
|
|
4450
|
+
* Creates an empty local list data-source.
|
|
4451
|
+
* You can set / modify data by using the data property.
|
|
4452
|
+
* @param idProperty
|
|
4453
|
+
* @param localSort
|
|
4454
|
+
* @param localFilter
|
|
4635
4455
|
*/
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
this.logger.info(this.id + ': Loading more...' + this._latestPage);
|
|
4639
|
-
if (this.loadingSnapshot) {
|
|
4640
|
-
return EMPTY;
|
|
4641
|
-
}
|
|
4642
|
-
const nextPage = this._latestPage + 1;
|
|
4643
|
-
return this.fetchPage(nextPage, this.chunkSize);
|
|
4644
|
-
}
|
|
4645
|
-
else {
|
|
4646
|
-
this.logger.debug(this.id + ': Cannot load more data, since no more data available.');
|
|
4647
|
-
return EMPTY;
|
|
4648
|
-
}
|
|
4649
|
-
}
|
|
4650
|
-
get hasMoreData() {
|
|
4651
|
-
return this._hasMoreData;
|
|
4456
|
+
static empty(idProperty, localSort, localFilter) {
|
|
4457
|
+
return LocalPagedDataSource.of(LocalListDataSource.empty(idProperty, localSort, localFilter));
|
|
4652
4458
|
}
|
|
4653
|
-
|
|
4654
|
-
return
|
|
4459
|
+
static of(listDataSource) {
|
|
4460
|
+
return new LocalPagedDataSource(listDataSource);
|
|
4655
4461
|
}
|
|
4656
4462
|
/***************************************************************************
|
|
4657
4463
|
* *
|
|
4658
|
-
*
|
|
4464
|
+
* Constructor *
|
|
4659
4465
|
* *
|
|
4660
4466
|
**************************************************************************/
|
|
4661
|
-
|
|
4662
|
-
if (
|
|
4663
|
-
|
|
4467
|
+
constructor(listDataSource) {
|
|
4468
|
+
if (!listDataSource) {
|
|
4469
|
+
throw new Error('listDataSource must not be null!');
|
|
4664
4470
|
}
|
|
4665
|
-
|
|
4471
|
+
this.localListFetcher = listDataSource;
|
|
4666
4472
|
}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4473
|
+
/***************************************************************************
|
|
4474
|
+
* *
|
|
4475
|
+
* Public API *
|
|
4476
|
+
* *
|
|
4477
|
+
**************************************************************************/
|
|
4478
|
+
get dataChanged() {
|
|
4479
|
+
return this.localListFetcher.dataChanged;
|
|
4672
4480
|
}
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
this._pageCache = new Map();
|
|
4676
|
-
this._latestPage = 0;
|
|
4481
|
+
findById(id) {
|
|
4482
|
+
return this.localListFetcher.findById(id);
|
|
4677
4483
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4484
|
+
findByIds(ids) {
|
|
4485
|
+
return this.localListFetcher.findByIds(ids);
|
|
4486
|
+
}
|
|
4487
|
+
findAllPaged(pageable, filters) {
|
|
4488
|
+
return this.localListFetcher
|
|
4489
|
+
.findAllFiltered(filters, pageable.sorts)
|
|
4490
|
+
.pipe(map((data) => this.pageSlice(pageable, data)));
|
|
4491
|
+
}
|
|
4492
|
+
getId(entity) {
|
|
4493
|
+
return this.localListFetcher.getId(entity);
|
|
4494
|
+
}
|
|
4495
|
+
/***************************************************************************
|
|
4496
|
+
* *
|
|
4497
|
+
* Private methods *
|
|
4498
|
+
* *
|
|
4499
|
+
**************************************************************************/
|
|
4500
|
+
pageSlice(pageable, data) {
|
|
4501
|
+
let page;
|
|
4502
|
+
if (data) {
|
|
4503
|
+
const start = pageable.page * pageable.size;
|
|
4504
|
+
const end = start + pageable.size;
|
|
4505
|
+
const slice = data.slice(start, end);
|
|
4506
|
+
page = Page.fromPage(slice, data.length, pageable);
|
|
4685
4507
|
}
|
|
4686
4508
|
else {
|
|
4687
|
-
|
|
4688
|
-
this.logger.debug(this.id + `: Loading page ${pageIndex} using pageable:`, pageRequest);
|
|
4689
|
-
const pageObs = this.dataSource.findAllPaged(pageRequest, this.filter.filtersSnapshot);
|
|
4690
|
-
this._pageCache.set(pageIndex, pageObs);
|
|
4691
|
-
pageObs.subscribe((page) => {
|
|
4692
|
-
this.logger.debug(this.id + ': Got page data:', page);
|
|
4693
|
-
this.populatePageData(page, clear);
|
|
4694
|
-
if (this._latestPage < page.number) {
|
|
4695
|
-
this._latestPage = page.number; // TODO This might cause that pages are skipped
|
|
4696
|
-
}
|
|
4697
|
-
subject.next();
|
|
4698
|
-
this.onIdle();
|
|
4699
|
-
}, (err) => {
|
|
4700
|
-
this.onError(err);
|
|
4701
|
-
this.clearData();
|
|
4702
|
-
this.setTotal(0);
|
|
4703
|
-
this.logger.error(this.id + ': Failed to query data', err);
|
|
4704
|
-
subject.error(err);
|
|
4705
|
-
});
|
|
4509
|
+
page = Page.empty();
|
|
4706
4510
|
}
|
|
4707
|
-
return
|
|
4511
|
+
return page;
|
|
4708
4512
|
}
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
else {
|
|
4720
|
-
this.insertData(page.content, start);
|
|
4721
|
-
}
|
|
4722
|
-
}
|
|
4723
|
-
catch (err) {
|
|
4724
|
-
this.onError(err);
|
|
4725
|
-
this.logger.error(this.id + ': Failed to populate data with page', page, err);
|
|
4726
|
-
}
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
/***************************************************************************
|
|
4516
|
+
* *
|
|
4517
|
+
* Type Predicates *
|
|
4518
|
+
* *
|
|
4519
|
+
**************************************************************************/
|
|
4520
|
+
function isDataSource(object) {
|
|
4521
|
+
if (!object) {
|
|
4522
|
+
return false;
|
|
4727
4523
|
}
|
|
4524
|
+
return (object.dataChanged !== undefined &&
|
|
4525
|
+
object.findById !== undefined &&
|
|
4526
|
+
object.getId !== undefined);
|
|
4527
|
+
}
|
|
4528
|
+
function isListDataSource(object) {
|
|
4529
|
+
return object.findAllFiltered !== undefined;
|
|
4530
|
+
}
|
|
4531
|
+
function isLocalListDataSource(object) {
|
|
4532
|
+
return object instanceof LocalListDataSource;
|
|
4533
|
+
}
|
|
4534
|
+
function isLocalPagedDataSource(object) {
|
|
4535
|
+
return object instanceof LocalPagedDataSource;
|
|
4536
|
+
}
|
|
4537
|
+
function isLocalDataSource(object) {
|
|
4538
|
+
return isLocalListDataSource(object) || isLocalPagedDataSource(object);
|
|
4539
|
+
}
|
|
4540
|
+
function isPagedDataSource(object) {
|
|
4541
|
+
return object.findAllPaged !== undefined;
|
|
4542
|
+
}
|
|
4543
|
+
function isContinuableDataSource(object) {
|
|
4544
|
+
return object.findAllContinuable !== undefined;
|
|
4728
4545
|
}
|
|
4729
4546
|
|
|
4730
|
-
class
|
|
4547
|
+
class ReloadRequest {
|
|
4548
|
+
constructor(number, reason) {
|
|
4549
|
+
this.number = number;
|
|
4550
|
+
this.reason = reason;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
class AfterReload {
|
|
4554
|
+
constructor(request, result) {
|
|
4555
|
+
this.request = request;
|
|
4556
|
+
this.result = result;
|
|
4557
|
+
}
|
|
4558
|
+
fulfills(minRequest) {
|
|
4559
|
+
return this.request.number >= minRequest.number;
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
const DEFAULT_DEBOUNCE_TIME$1 = 50;
|
|
4563
|
+
class DataContextBase extends DataSource {
|
|
4564
|
+
/***************************************************************************
|
|
4565
|
+
* *
|
|
4566
|
+
* Fields *
|
|
4567
|
+
* *
|
|
4568
|
+
**************************************************************************/
|
|
4569
|
+
static { this.DC_ID_COUNTER = 0; }
|
|
4731
4570
|
/***************************************************************************
|
|
4732
4571
|
* *
|
|
4733
4572
|
* Constructor *
|
|
4734
4573
|
* *
|
|
4735
4574
|
**************************************************************************/
|
|
4736
|
-
constructor(dataSource,
|
|
4737
|
-
super(
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
this.
|
|
4744
|
-
this.
|
|
4745
|
-
this.
|
|
4575
|
+
constructor(dataSource, _indexFn, _localApply, _localSort) {
|
|
4576
|
+
super();
|
|
4577
|
+
this._indexFn = _indexFn;
|
|
4578
|
+
this._localApply = _localApply;
|
|
4579
|
+
this._localSort = _localSort;
|
|
4580
|
+
this.baselog = LoggerFactory.getLogger('DataContextBase');
|
|
4581
|
+
this._filter = new FilterContext();
|
|
4582
|
+
this._sort = new SortContext();
|
|
4583
|
+
this._total = new BehaviorSubject(undefined);
|
|
4584
|
+
this._status = new BehaviorSubject(DataContextStatus.idle());
|
|
4585
|
+
this._started = new BehaviorSubject(false);
|
|
4586
|
+
this._closed = new BehaviorSubject(false);
|
|
4587
|
+
this._customIndex = new Map();
|
|
4588
|
+
this._reloadCounter = 0;
|
|
4589
|
+
this._reloadQueue = new Subject();
|
|
4590
|
+
this._reloaded = new Subject();
|
|
4591
|
+
this.destroy$ = new Subject();
|
|
4592
|
+
this.id = 'DataContext#' + ++DataContextBase.DC_ID_COUNTER;
|
|
4593
|
+
this._dataSource = dataSource;
|
|
4594
|
+
this._data = new IndexedEntities((e) => dataSource.getId(e), _localSort, this._sort);
|
|
4595
|
+
this._loading = this._status.pipe(map((status) => status.loading));
|
|
4596
|
+
this._filter.filters
|
|
4597
|
+
.pipe(filter(() => this.started), takeUntil(this.destroy$))
|
|
4598
|
+
.subscribe((filters) => this.onFiltersChanged(filters));
|
|
4599
|
+
this._sort.sorts
|
|
4600
|
+
.pipe(filter(() => this.started), takeUntil(this.destroy$))
|
|
4601
|
+
.subscribe((sorts) => this.onSortsChanged(sorts));
|
|
4602
|
+
this._reloadQueue
|
|
4603
|
+
.pipe(takeUntil(this.destroy$), filter((request) => this.started), debounceTime(this.getDebounceTime()), switchMap((request) => this.reloadNow(request).pipe(map((result) => new AfterReload(request, result)), catchError((err) => {
|
|
4604
|
+
// Dont die on errors
|
|
4605
|
+
this.baselog.error(this.id + ': Reload queue detected error, bad!', err);
|
|
4606
|
+
return of(new AfterReload(request, err));
|
|
4607
|
+
}))))
|
|
4608
|
+
.subscribe((afterReload) => this._reloaded.next(afterReload));
|
|
4746
4609
|
}
|
|
4747
4610
|
/***************************************************************************
|
|
4748
4611
|
* *
|
|
4749
|
-
*
|
|
4612
|
+
* Public API Material DC *
|
|
4750
4613
|
* *
|
|
4751
4614
|
**************************************************************************/
|
|
4752
|
-
|
|
4753
|
-
return
|
|
4615
|
+
connect(collectionViewer) {
|
|
4616
|
+
return this.data;
|
|
4754
4617
|
}
|
|
4618
|
+
disconnect(collectionViewer) { }
|
|
4755
4619
|
/***************************************************************************
|
|
4756
4620
|
* *
|
|
4757
|
-
*
|
|
4621
|
+
* Properties *
|
|
4758
4622
|
* *
|
|
4759
4623
|
**************************************************************************/
|
|
4760
|
-
get
|
|
4761
|
-
return this.
|
|
4624
|
+
get dataSource() {
|
|
4625
|
+
return this._dataSource;
|
|
4626
|
+
}
|
|
4627
|
+
get snapshot() {
|
|
4628
|
+
return new DataContextSnapshot(this.dataSnapshot, this.isEmpty, this.loadingSnapshot, this.totalSnapshot, this.statusSnapshot);
|
|
4629
|
+
}
|
|
4630
|
+
get total() {
|
|
4631
|
+
return this._total.asObservable();
|
|
4632
|
+
}
|
|
4633
|
+
get totalSnapshot() {
|
|
4634
|
+
return this._total.getValue();
|
|
4635
|
+
}
|
|
4636
|
+
get sort() {
|
|
4637
|
+
return this._sort;
|
|
4638
|
+
}
|
|
4639
|
+
get filter() {
|
|
4640
|
+
return this._filter;
|
|
4641
|
+
}
|
|
4642
|
+
get loading() {
|
|
4643
|
+
return this._loading;
|
|
4644
|
+
}
|
|
4645
|
+
get loadingSnapshot() {
|
|
4646
|
+
return this.statusSnapshot?.loading;
|
|
4647
|
+
}
|
|
4648
|
+
get data() {
|
|
4649
|
+
return this._data.entities$;
|
|
4650
|
+
}
|
|
4651
|
+
get dataSnapshot() {
|
|
4652
|
+
return this._data.entitiesSnapshot;
|
|
4653
|
+
}
|
|
4654
|
+
get status() {
|
|
4655
|
+
return this._status;
|
|
4656
|
+
}
|
|
4657
|
+
get statusSnapshot() {
|
|
4658
|
+
return this._status.getValue();
|
|
4659
|
+
}
|
|
4660
|
+
get isEmpty() {
|
|
4661
|
+
return this.dataSnapshot.length === 0;
|
|
4662
|
+
}
|
|
4663
|
+
get isStarted() {
|
|
4664
|
+
return this.started;
|
|
4665
|
+
}
|
|
4666
|
+
get isStarted$() {
|
|
4667
|
+
return this._started.asObservable();
|
|
4668
|
+
}
|
|
4669
|
+
get isClosed() {
|
|
4670
|
+
return this._closed.getValue();
|
|
4762
4671
|
}
|
|
4763
|
-
get
|
|
4764
|
-
return this.
|
|
4672
|
+
get started() {
|
|
4673
|
+
return this._started.getValue();
|
|
4765
4674
|
}
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
this.logger.debug(this.id + ': Skipping load-more since already loading a chunk!');
|
|
4769
|
-
return EMPTY;
|
|
4770
|
-
}
|
|
4771
|
-
const token = this._expectedChunkToken;
|
|
4772
|
-
if (token && token.length > 0) {
|
|
4773
|
-
return this.fetchNextChunk(token);
|
|
4774
|
-
}
|
|
4775
|
-
else {
|
|
4776
|
-
this.logger.debug(this.id + ': Cannot load more data, since no more data available.');
|
|
4777
|
-
return EMPTY;
|
|
4778
|
-
}
|
|
4675
|
+
set started(started) {
|
|
4676
|
+
this._started.next(started);
|
|
4779
4677
|
}
|
|
4780
4678
|
/***************************************************************************
|
|
4781
4679
|
* *
|
|
4782
|
-
*
|
|
4680
|
+
* Public API *
|
|
4783
4681
|
* *
|
|
4784
4682
|
**************************************************************************/
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
this.
|
|
4683
|
+
start(sorts, filters) {
|
|
4684
|
+
if (this.isClosed) {
|
|
4685
|
+
throw new Error(this.id + ': Restarting a closed DataContext is not possible!'); // See unsubscribe$
|
|
4686
|
+
}
|
|
4687
|
+
this.started = false;
|
|
4688
|
+
this._sort.replaceSorts(sorts || this.sort.sortsSnapshot);
|
|
4689
|
+
this._filter.replaceFilters(filters || this.filter.filtersSnapshot);
|
|
4690
|
+
this.baselog.debug(this.id + ': Start ...', {
|
|
4691
|
+
dataSource: this.dataSource,
|
|
4692
|
+
sort: this._sort.sortsSnapshot,
|
|
4693
|
+
filter: this._filter.filtersSnapshot,
|
|
4694
|
+
});
|
|
4695
|
+
this.started = true;
|
|
4696
|
+
return this.reload('START');
|
|
4790
4697
|
}
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
this.
|
|
4795
|
-
return this.
|
|
4698
|
+
reload(reason) {
|
|
4699
|
+
const request = new ReloadRequest(++this._reloadCounter, reason);
|
|
4700
|
+
this.baselog.debug(this.id + ': Enqueuing reload request #' + request.number + ' [' + reason + ']');
|
|
4701
|
+
this._reloadQueue.next(request);
|
|
4702
|
+
return this._reloaded.pipe(filter((reload) => reload.fulfills(request)), map((reload) => reload.result));
|
|
4796
4703
|
}
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
if (this._chunkCache.has(nextToken)) {
|
|
4801
|
-
this.logger.debug(this.id +
|
|
4802
|
-
': Skipping fetching chunk for token "' +
|
|
4803
|
-
nextToken +
|
|
4804
|
-
'" since its already in observable cache.');
|
|
4805
|
-
subject.complete();
|
|
4806
|
-
}
|
|
4807
|
-
else {
|
|
4808
|
-
this.onLoading();
|
|
4809
|
-
this._chunkCache.add(nextToken);
|
|
4810
|
-
this.dataSource
|
|
4811
|
-
.findAllContinuable(new TokenChunkRequest(nextToken, this.filter.filtersSnapshot, this.sort.sortsSnapshot, this.chunkSize))
|
|
4812
|
-
.pipe(first())
|
|
4813
|
-
.subscribe({
|
|
4814
|
-
next: (chunk) => {
|
|
4815
|
-
this.onChunkFetched(chunk);
|
|
4816
|
-
subject.next(chunk);
|
|
4817
|
-
},
|
|
4818
|
-
error: (err) => {
|
|
4819
|
-
this.onChunkFetchError(err);
|
|
4820
|
-
subject.error(err);
|
|
4821
|
-
},
|
|
4822
|
-
});
|
|
4704
|
+
findByIndex(key) {
|
|
4705
|
+
if (!this._indexFn) {
|
|
4706
|
+
throw new Error(this.id + ': findByIndex requires you to pass a index function!');
|
|
4823
4707
|
}
|
|
4824
|
-
return
|
|
4825
|
-
}
|
|
4826
|
-
onChunkFetched(chunk) {
|
|
4827
|
-
this.logger.debug(this.id + ': Got next chunk data:', chunk);
|
|
4828
|
-
this._hasMoreData.next(chunk.hasMore);
|
|
4829
|
-
this.updateChunkSize(chunk.chunkSize ?? chunk.maxChunkSize, false);
|
|
4830
|
-
this.populateChunkData(chunk);
|
|
4831
|
-
this.onIdle();
|
|
4708
|
+
return this._customIndex.get(key);
|
|
4832
4709
|
}
|
|
4833
|
-
|
|
4834
|
-
this.
|
|
4835
|
-
this.logger.error(this.id + ': Failed to query data', err);
|
|
4836
|
-
this.clearData();
|
|
4837
|
-
this.setTotal(0);
|
|
4710
|
+
findById(id) {
|
|
4711
|
+
return this._data.getById(id);
|
|
4838
4712
|
}
|
|
4839
4713
|
/**
|
|
4840
|
-
*
|
|
4714
|
+
* Closes this DataContext and cleans up all used resources.
|
|
4841
4715
|
*/
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
catch (err) {
|
|
4855
|
-
this.onError(err);
|
|
4856
|
-
this.logger.error(this.id + ': Failed to populate data with chunk', chunk, err);
|
|
4857
|
-
}
|
|
4858
|
-
this._expectedChunkToken = chunk.nextContinuationToken;
|
|
4859
|
-
}
|
|
4860
|
-
else {
|
|
4861
|
-
this.logger.warn(this.id +
|
|
4862
|
-
': Discarding continuable chunk (items: ' +
|
|
4863
|
-
chunk.content.length +
|
|
4864
|
-
', token: ' +
|
|
4865
|
-
chunk.continuationToken +
|
|
4866
|
-
' )' +
|
|
4867
|
-
' as it does not match the expected contiunation-token: ' +
|
|
4868
|
-
this._expectedChunkToken);
|
|
4869
|
-
}
|
|
4716
|
+
close() {
|
|
4717
|
+
this.started = false;
|
|
4718
|
+
this.destroy$.next();
|
|
4719
|
+
this.destroy$.complete();
|
|
4720
|
+
this._closed.next(true);
|
|
4721
|
+
this._data.destroy();
|
|
4722
|
+
this._total.complete();
|
|
4723
|
+
this._status.complete();
|
|
4724
|
+
this.clearAll();
|
|
4725
|
+
this.baselog.debug(this.id + ': Has been closed and resources cleaned up!');
|
|
4870
4726
|
}
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
return true;
|
|
4874
|
-
}
|
|
4875
|
-
return token1 === token2;
|
|
4727
|
+
refresh() {
|
|
4728
|
+
this._data.notify();
|
|
4876
4729
|
}
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
class DataContextActivePage extends DataContextBase {
|
|
4880
|
-
/***************************************************************************
|
|
4881
|
-
* *
|
|
4882
|
-
* Constructor *
|
|
4883
|
-
* *
|
|
4884
|
-
**************************************************************************/
|
|
4885
|
-
constructor(dataSource, pageSize, indexFn, localApply, localSort) {
|
|
4886
|
-
super(dataSource, indexFn, localApply, localSort);
|
|
4887
|
-
/***************************************************************************
|
|
4888
|
-
* *
|
|
4889
|
-
* Fields *
|
|
4890
|
-
* *
|
|
4891
|
-
**************************************************************************/
|
|
4892
|
-
this.actlogger = LoggerFactory.getLogger(this.constructor.name);
|
|
4893
|
-
this._page = new BehaviorSubject(new PageRequest(0, pageSize));
|
|
4730
|
+
update(entities) {
|
|
4731
|
+
this._data.updateSome(entities);
|
|
4894
4732
|
}
|
|
4895
4733
|
/***************************************************************************
|
|
4896
4734
|
* *
|
|
4897
|
-
*
|
|
4735
|
+
* Protected methods *
|
|
4898
4736
|
* *
|
|
4899
4737
|
**************************************************************************/
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4738
|
+
reloadNow(request) {
|
|
4739
|
+
this.baselog.debug(this.id + ': reloadNow triggered. Request #' + request.number + ' ' + request.reason, {
|
|
4740
|
+
filter: this._filter.filtersSnapshot,
|
|
4741
|
+
sort: this._sort.sortsSnapshot,
|
|
4742
|
+
dataSource: this.dataSource,
|
|
4743
|
+
request: request,
|
|
4744
|
+
});
|
|
4745
|
+
return this.reloadInternal(); // TODO This should actually return the real (http) request
|
|
4905
4746
|
}
|
|
4906
|
-
|
|
4907
|
-
|
|
4747
|
+
/**
|
|
4748
|
+
* Append the given rows to the existing ones.
|
|
4749
|
+
*/
|
|
4750
|
+
appendData(additionalData) {
|
|
4751
|
+
additionalData = this.localApply(additionalData);
|
|
4752
|
+
this.indexAll(additionalData);
|
|
4753
|
+
this._data.append(additionalData);
|
|
4908
4754
|
}
|
|
4909
|
-
/***************************************************************************
|
|
4910
|
-
* *
|
|
4911
|
-
* Public API *
|
|
4912
|
-
* *
|
|
4913
|
-
**************************************************************************/
|
|
4914
4755
|
/**
|
|
4915
|
-
*
|
|
4916
|
-
* @param pageIndex
|
|
4756
|
+
* Insert the given rows at the given location.
|
|
4917
4757
|
*/
|
|
4918
|
-
|
|
4919
|
-
|
|
4758
|
+
insertData(additionalData, offset) {
|
|
4759
|
+
additionalData = this.localApply(additionalData);
|
|
4760
|
+
this.indexAll(additionalData);
|
|
4761
|
+
this._data.insert(additionalData, offset);
|
|
4920
4762
|
}
|
|
4921
4763
|
/**
|
|
4922
|
-
*
|
|
4764
|
+
* Replaces the existing data with the new set.
|
|
4765
|
+
* @param newData The new data set.
|
|
4766
|
+
* @param alreadyIndexed The rows will be indexed unless they already have been.
|
|
4923
4767
|
*/
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4768
|
+
setData(newData, alreadyIndexed = false) {
|
|
4769
|
+
newData = this.localApply(newData);
|
|
4770
|
+
if (!alreadyIndexed) {
|
|
4771
|
+
this.clearIndex();
|
|
4772
|
+
this.indexAll(newData);
|
|
4927
4773
|
}
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4774
|
+
this._data.replaceAll(newData);
|
|
4775
|
+
}
|
|
4776
|
+
clearData() {
|
|
4777
|
+
this.setData([]);
|
|
4778
|
+
}
|
|
4779
|
+
localApply(data) {
|
|
4780
|
+
if (this._localApply) {
|
|
4781
|
+
return this._localApply(data);
|
|
4932
4782
|
}
|
|
4933
|
-
else
|
|
4934
|
-
|
|
4783
|
+
else {
|
|
4784
|
+
return data;
|
|
4935
4785
|
}
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4786
|
+
}
|
|
4787
|
+
setTotal(total) {
|
|
4788
|
+
this._total.next(total);
|
|
4789
|
+
}
|
|
4790
|
+
/**
|
|
4791
|
+
* Clears the current data-context cached data.
|
|
4792
|
+
* State such as current sorting and filtersSnapshot are kept.
|
|
4793
|
+
*/
|
|
4794
|
+
clearAll(silent = false) {
|
|
4795
|
+
this.clearIndex();
|
|
4796
|
+
if (!silent) {
|
|
4797
|
+
this.setTotal(0);
|
|
4798
|
+
this.clearData();
|
|
4799
|
+
}
|
|
4800
|
+
this.onIdle();
|
|
4801
|
+
}
|
|
4802
|
+
updateIndex() {
|
|
4803
|
+
this.clearIndex();
|
|
4804
|
+
this.indexAll(this.dataSnapshot);
|
|
4805
|
+
}
|
|
4806
|
+
/**
|
|
4807
|
+
* Indexes the given item by key if there is an index function defined.
|
|
4808
|
+
*/
|
|
4809
|
+
indexItem(item) {
|
|
4810
|
+
const key = this.getItemKey(item);
|
|
4811
|
+
if (key) {
|
|
4812
|
+
this._customIndex.set(key, item);
|
|
4939
4813
|
}
|
|
4940
4814
|
}
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4815
|
+
/**
|
|
4816
|
+
* Indexes all the given items by key if there is an index function defined.
|
|
4817
|
+
*/
|
|
4818
|
+
indexAll(items) {
|
|
4819
|
+
if (this._indexFn) {
|
|
4820
|
+
items.forEach((item) => this.indexItem(item));
|
|
4945
4821
|
}
|
|
4946
4822
|
}
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
clearAll() {
|
|
4953
|
-
super.clearAll();
|
|
4954
|
-
this.setActiveIndex(0);
|
|
4823
|
+
getItemKey(item) {
|
|
4824
|
+
if (this._indexFn) {
|
|
4825
|
+
return this._indexFn(item);
|
|
4826
|
+
}
|
|
4827
|
+
return null;
|
|
4955
4828
|
}
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4829
|
+
getItemId(item) {
|
|
4830
|
+
return this.dataSource.getId(item);
|
|
4831
|
+
}
|
|
4832
|
+
getDebounceTime() {
|
|
4833
|
+
if (isLocalDataSource(this.dataSource)) {
|
|
4834
|
+
return 0;
|
|
4960
4835
|
}
|
|
4961
|
-
|
|
4962
|
-
this.onLoading();
|
|
4963
|
-
const page = this.pageSnapshot;
|
|
4964
|
-
const pageRequest = new Pageable(page.index, page.size, this.sort.sortsSnapshot);
|
|
4965
|
-
this._activePageLoad = this.dataSource
|
|
4966
|
-
.findAllPaged(pageRequest, this.filter.filtersSnapshot)
|
|
4967
|
-
.pipe(first())
|
|
4968
|
-
.subscribe((success) => {
|
|
4969
|
-
this.setTotal(success.totalElements);
|
|
4970
|
-
this.setData(success.content);
|
|
4971
|
-
subject.next(success);
|
|
4972
|
-
this.onIdle();
|
|
4973
|
-
}, (err) => {
|
|
4974
|
-
this.clearData();
|
|
4975
|
-
this.actlogger.error(this.id + ': Failed to query data', err);
|
|
4976
|
-
subject.error(err);
|
|
4977
|
-
this.onError(err);
|
|
4978
|
-
}, () => {
|
|
4979
|
-
this.onIdle();
|
|
4980
|
-
});
|
|
4981
|
-
return subject.pipe(first());
|
|
4836
|
+
return DEFAULT_DEBOUNCE_TIME$1;
|
|
4982
4837
|
}
|
|
4983
4838
|
/***************************************************************************
|
|
4984
4839
|
* *
|
|
4985
|
-
* Event
|
|
4840
|
+
* Event handler *
|
|
4986
4841
|
* *
|
|
4987
4842
|
**************************************************************************/
|
|
4988
4843
|
/**
|
|
4989
|
-
* Occurs when the
|
|
4844
|
+
* Occurs when the sort has changed.
|
|
4990
4845
|
*/
|
|
4991
4846
|
onSortsChanged(sorts) {
|
|
4992
|
-
this.
|
|
4993
|
-
|
|
4847
|
+
if (!this._localSort) {
|
|
4848
|
+
this.reload('Sort Changed');
|
|
4849
|
+
}
|
|
4850
|
+
else {
|
|
4851
|
+
this.setData(this.dataSnapshot, true);
|
|
4852
|
+
}
|
|
4994
4853
|
}
|
|
4995
4854
|
/**
|
|
4996
|
-
* Occurs when the
|
|
4855
|
+
* Occurs when the filter has changed.
|
|
4997
4856
|
*/
|
|
4998
4857
|
onFiltersChanged(filters) {
|
|
4999
|
-
this.
|
|
5000
|
-
|
|
4858
|
+
this.reload('Filters Changed');
|
|
4859
|
+
}
|
|
4860
|
+
onError(err) {
|
|
4861
|
+
this.onStatus(DataContextStatus.error(err));
|
|
4862
|
+
}
|
|
4863
|
+
onIdle() {
|
|
4864
|
+
this.onStatus(DataContextStatus.idle());
|
|
4865
|
+
}
|
|
4866
|
+
onLoading() {
|
|
4867
|
+
this.onStatus(DataContextStatus.loading());
|
|
4868
|
+
}
|
|
4869
|
+
onStatus(status) {
|
|
4870
|
+
this._status.next(status);
|
|
4871
|
+
}
|
|
4872
|
+
/***************************************************************************
|
|
4873
|
+
* *
|
|
4874
|
+
* Private methods *
|
|
4875
|
+
* *
|
|
4876
|
+
**************************************************************************/
|
|
4877
|
+
clearIndex() {
|
|
4878
|
+
this._customIndex.clear();
|
|
5001
4879
|
}
|
|
5002
4880
|
}
|
|
5003
4881
|
|
|
5004
|
-
class
|
|
4882
|
+
class DataContextSimple extends DataContextBase {
|
|
5005
4883
|
/***************************************************************************
|
|
5006
4884
|
* *
|
|
5007
|
-
*
|
|
4885
|
+
* Constructor *
|
|
5008
4886
|
* *
|
|
5009
4887
|
**************************************************************************/
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
}
|
|
5013
|
-
constructor(dataContext$) {
|
|
4888
|
+
constructor(dataSource, indexFn, localApply, localSort) {
|
|
4889
|
+
super(dataSource, indexFn, localApply, localSort);
|
|
5014
4890
|
/***************************************************************************
|
|
5015
4891
|
* *
|
|
5016
4892
|
* Fields *
|
|
5017
4893
|
* *
|
|
5018
4894
|
**************************************************************************/
|
|
5019
|
-
this.
|
|
5020
|
-
this._dataContext$ = dataContext$;
|
|
4895
|
+
this.log = LoggerFactory.getLogger(this.constructor.name);
|
|
5021
4896
|
}
|
|
5022
4897
|
/***************************************************************************
|
|
5023
4898
|
* *
|
|
5024
|
-
*
|
|
4899
|
+
* Properties *
|
|
5025
4900
|
* *
|
|
5026
4901
|
**************************************************************************/
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
return this;
|
|
5030
|
-
}
|
|
5031
|
-
withPaginator(paginator$) {
|
|
5032
|
-
this._matPaginator$ = paginator$;
|
|
5033
|
-
return this;
|
|
5034
|
-
}
|
|
5035
|
-
withContinuator(continuator$) {
|
|
5036
|
-
this._continuator$ = continuator$;
|
|
5037
|
-
return this;
|
|
4902
|
+
get dataSource() {
|
|
4903
|
+
return super.dataSource;
|
|
5038
4904
|
}
|
|
5039
|
-
|
|
5040
|
-
|
|
4905
|
+
/***************************************************************************
|
|
4906
|
+
* *
|
|
4907
|
+
* Private methods *
|
|
4908
|
+
* *
|
|
4909
|
+
**************************************************************************/
|
|
4910
|
+
reloadInternal() {
|
|
4911
|
+
const subject = new Subject();
|
|
4912
|
+
if (this.dataSource) {
|
|
4913
|
+
this.onLoading();
|
|
4914
|
+
this.dataSource
|
|
4915
|
+
.findAllFiltered(this.filter.filtersSnapshot, this.sort.sortsSnapshot)
|
|
4916
|
+
.pipe(first())
|
|
4917
|
+
.subscribe((list) => {
|
|
4918
|
+
this.onIdle();
|
|
4919
|
+
this.setTotal(list.length);
|
|
4920
|
+
this.setData(list);
|
|
4921
|
+
this.log.debug(this.id + ': Got list data: ' + list.length);
|
|
4922
|
+
subject.next();
|
|
4923
|
+
}, (err) => {
|
|
4924
|
+
this.onError(err);
|
|
4925
|
+
this.clearAll();
|
|
4926
|
+
this.log.error(this.id + ': Failed to query data', err);
|
|
4927
|
+
subject.error(err);
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4930
|
+
else {
|
|
4931
|
+
const errorMsg = this.id + ': Skipping data context load - no list fetcher present!';
|
|
4932
|
+
this.log.warn(errorMsg);
|
|
4933
|
+
subject.error(new Error(errorMsg));
|
|
4934
|
+
}
|
|
4935
|
+
return subject.pipe(first());
|
|
5041
4936
|
}
|
|
5042
4937
|
}
|
|
5043
|
-
|
|
4938
|
+
|
|
4939
|
+
/**
|
|
4940
|
+
* Extends a simple flat list data-context with infinite-scroll pagination support.
|
|
4941
|
+
*
|
|
4942
|
+
*/
|
|
4943
|
+
class DataContextContinuableBase extends DataContextBase {
|
|
5044
4944
|
/***************************************************************************
|
|
5045
4945
|
* *
|
|
5046
|
-
*
|
|
4946
|
+
* Constructors *
|
|
5047
4947
|
* *
|
|
5048
4948
|
**************************************************************************/
|
|
5049
|
-
constructor(
|
|
5050
|
-
|
|
5051
|
-
this._matSorts$ = _matSorts$;
|
|
5052
|
-
this._matPaginator$ = _matPaginator$;
|
|
5053
|
-
this._continuator$ = _continuator$;
|
|
4949
|
+
constructor(dataSource, chunkSize, indexFn, localApply, localSort) {
|
|
4950
|
+
super(dataSource, indexFn, localApply, localSort);
|
|
5054
4951
|
/***************************************************************************
|
|
5055
4952
|
* *
|
|
5056
4953
|
* Fields *
|
|
5057
4954
|
* *
|
|
5058
4955
|
**************************************************************************/
|
|
5059
|
-
this.
|
|
5060
|
-
this.
|
|
4956
|
+
this.cblogger = LoggerFactory.getLogger(this.constructor.name);
|
|
4957
|
+
this._chunkSize$ = new BehaviorSubject(chunkSize);
|
|
5061
4958
|
}
|
|
5062
4959
|
/***************************************************************************
|
|
5063
4960
|
* *
|
|
5064
|
-
*
|
|
4961
|
+
* Public API *
|
|
5065
4962
|
* *
|
|
5066
4963
|
**************************************************************************/
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
const dcSorts$ = this._dataContext$.pipe(filter((dc) => !!dc), switchMap$1((dc) => dc.sort.sorts));
|
|
5081
|
-
combineLatest([dcSorts$, matSorts$])
|
|
5082
|
-
.pipe(takeUntil(destroy$))
|
|
5083
|
-
.subscribe(([dcSorts, matSorts]) => {
|
|
5084
|
-
if (dcSorts.length >= 1) {
|
|
5085
|
-
// At least one sort active
|
|
5086
|
-
const sort = dcSorts[0];
|
|
5087
|
-
this.updateMatSorts(sort, matSorts);
|
|
5088
|
-
}
|
|
5089
|
-
else {
|
|
5090
|
-
// No sort active
|
|
5091
|
-
this.updateMatSorts(Sort.NONE, matSorts);
|
|
5092
|
-
}
|
|
5093
|
-
});
|
|
5094
|
-
}
|
|
5095
|
-
updateMatSorts(dcSort, matSorts) {
|
|
5096
|
-
const active = dcSort.prop;
|
|
5097
|
-
const direction = this.toMatDirection(dcSort.dir);
|
|
5098
|
-
matSorts.forEach((matSort) => this.updateMatSort(active, direction, matSort));
|
|
5099
|
-
}
|
|
5100
|
-
updateMatSort(activeProp, direction, matSort) {
|
|
5101
|
-
if (activeProp) {
|
|
5102
|
-
if (!matSort.sortables.has(activeProp)) {
|
|
5103
|
-
// The current sort property is not part of this MatSort.
|
|
5104
|
-
activeProp = Sort.NONE.prop; // Force no sort in this mat context
|
|
5105
|
-
}
|
|
5106
|
-
}
|
|
5107
|
-
if (matSort.active !== activeProp || matSort.direction !== direction) {
|
|
5108
|
-
// We do only update matSort when there was a real change
|
|
5109
|
-
matSort.active = activeProp;
|
|
5110
|
-
matSort.direction = direction;
|
|
5111
|
-
matSort._stateChanges.next();
|
|
5112
|
-
}
|
|
5113
|
-
}
|
|
5114
|
-
bindMatSortsToDataContextUntil(sorts$, destroy$) {
|
|
5115
|
-
const sortChanges$ = sorts$.pipe(mergeMap((sorts) => merge(...sorts.map((s) => s.sortChange))), map((matSort) => {
|
|
5116
|
-
return new Sort(matSort.active, this.fromMatDirection(matSort.direction));
|
|
5117
|
-
}));
|
|
5118
|
-
combineLatest([this._dataContext$, sortChanges$])
|
|
5119
|
-
.pipe(takeUntil(destroy$))
|
|
5120
|
-
.subscribe(([dc, sortRequest]) => {
|
|
5121
|
-
if (dc) {
|
|
5122
|
-
dc.sort.updateSort(sortRequest);
|
|
5123
|
-
}
|
|
5124
|
-
});
|
|
5125
|
-
}
|
|
5126
|
-
bindPaginatorUntil(paginator$, destroy$) {
|
|
5127
|
-
const pageRequest$ = paginator$.pipe(filter((paginator) => !!paginator), switchMap$1((paginator) => paginator.page), map((pageEvent) => new PageRequest(pageEvent.pageIndex, pageEvent.pageSize)));
|
|
5128
|
-
combineLatest([this._dataContext$, pageRequest$])
|
|
5129
|
-
.pipe(takeUntil(destroy$))
|
|
5130
|
-
.subscribe(([dc, pageRequest]) => {
|
|
5131
|
-
if (dc) {
|
|
5132
|
-
if (isActivePagedDataContext(dc)) {
|
|
5133
|
-
const pagedDc = dc;
|
|
5134
|
-
pagedDc.setActivePage(pageRequest);
|
|
5135
|
-
}
|
|
5136
|
-
else {
|
|
5137
|
-
this.logger.warn('Can not bind the given paginator to the given data-context,' +
|
|
5138
|
-
' as the datacontext does not support pagination!', dc);
|
|
5139
|
-
}
|
|
5140
|
-
}
|
|
4964
|
+
loadAll(sorts, filters) {
|
|
4965
|
+
this.cblogger.debug(this.id + ': Starting to load all data ...');
|
|
4966
|
+
// load first page
|
|
4967
|
+
this.start(sorts, filters).subscribe({
|
|
4968
|
+
next: () => {
|
|
4969
|
+
this.cblogger.debug(this.id + ': First page has been loaded. Loading remaining data ...');
|
|
4970
|
+
// load rest in a recursive manner
|
|
4971
|
+
this.loadAllRec();
|
|
4972
|
+
},
|
|
4973
|
+
error: (err) => {
|
|
4974
|
+
this.onError(err);
|
|
4975
|
+
this.cblogger.error(this.id + ': Failed to load first page of load all procedure!', err);
|
|
4976
|
+
},
|
|
5141
4977
|
});
|
|
5142
4978
|
}
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
combineLatest([this._dataContext$, chunkSizeChange$])
|
|
5146
|
-
.pipe(takeUntil(destroy$))
|
|
5147
|
-
.subscribe(([dc, newChunkSize]) => {
|
|
5148
|
-
if (dc) {
|
|
5149
|
-
if (isContinuableDataContext(dc)) {
|
|
5150
|
-
const continuableDc = dc;
|
|
5151
|
-
continuableDc.chunkSize = newChunkSize;
|
|
5152
|
-
}
|
|
5153
|
-
else {
|
|
5154
|
-
this.logger.warn('Can not bind the given Continuator to the given data-context,' +
|
|
5155
|
-
' as the DataContext does not support continuation!', dc);
|
|
5156
|
-
}
|
|
5157
|
-
}
|
|
5158
|
-
});
|
|
4979
|
+
get chunkSize$() {
|
|
4980
|
+
return this._chunkSize$.asObservable();
|
|
5159
4981
|
}
|
|
5160
|
-
|
|
5161
|
-
return
|
|
4982
|
+
get chunkSize() {
|
|
4983
|
+
return this._chunkSize$.value;
|
|
5162
4984
|
}
|
|
5163
|
-
|
|
5164
|
-
|
|
4985
|
+
set chunkSize(size) {
|
|
4986
|
+
this.updateChunkSize(size, true);
|
|
4987
|
+
}
|
|
4988
|
+
/***************************************************************************
|
|
4989
|
+
* *
|
|
4990
|
+
* Private Methods *
|
|
4991
|
+
* *
|
|
4992
|
+
**************************************************************************/
|
|
4993
|
+
updateChunkSize(newSize, reloadOnChange) {
|
|
4994
|
+
if (this._chunkSize$.value !== newSize) {
|
|
4995
|
+
this._chunkSize$.next(newSize);
|
|
4996
|
+
if (reloadOnChange) {
|
|
4997
|
+
this.reload('ChunkSizeChanged:' + newSize);
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
loadAllRec() {
|
|
5002
|
+
this.loadMore().subscribe({
|
|
5003
|
+
next: () => {
|
|
5004
|
+
this.cblogger.debug(this.id + ': Loading data chunk finished, loading next...');
|
|
5005
|
+
this.loadAllRec();
|
|
5006
|
+
},
|
|
5007
|
+
error: (err) => {
|
|
5008
|
+
this.onError(err);
|
|
5009
|
+
this.cblogger.error(this.id + ': Loading all failed!', err);
|
|
5010
|
+
},
|
|
5011
|
+
complete: () => {
|
|
5012
|
+
this.cblogger.info(this.id + ': All data loaded completely.');
|
|
5013
|
+
},
|
|
5014
|
+
});
|
|
5165
5015
|
}
|
|
5166
5016
|
}
|
|
5167
5017
|
|
|
5168
5018
|
/**
|
|
5169
|
-
*
|
|
5170
|
-
*
|
|
5019
|
+
* Extends a simple flat list data-context with infinite-scroll pagination support.
|
|
5020
|
+
*
|
|
5171
5021
|
*/
|
|
5172
|
-
class
|
|
5022
|
+
class DataContextContinuablePaged extends DataContextContinuableBase {
|
|
5173
5023
|
/***************************************************************************
|
|
5174
5024
|
* *
|
|
5175
|
-
*
|
|
5025
|
+
* Constructors *
|
|
5176
5026
|
* *
|
|
5177
5027
|
**************************************************************************/
|
|
5178
|
-
constructor(
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5028
|
+
constructor(dataSource, pageSize, indexFn, localApply, localSort) {
|
|
5029
|
+
super(dataSource, pageSize, indexFn, localApply, localSort);
|
|
5030
|
+
/***************************************************************************
|
|
5031
|
+
* *
|
|
5032
|
+
* Fields *
|
|
5033
|
+
* *
|
|
5034
|
+
**************************************************************************/
|
|
5035
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
5036
|
+
this._pageCache = new Map();
|
|
5037
|
+
this._latestPage = 0;
|
|
5038
|
+
this._hasMoreData = combineLatest([this.total, this.data]).pipe(map(([total, data]) => this.checkHasMoreData(total, data)), takeUntil(this.destroy$));
|
|
5039
|
+
}
|
|
5040
|
+
/***************************************************************************
|
|
5041
|
+
* *
|
|
5042
|
+
* Properties *
|
|
5043
|
+
* *
|
|
5044
|
+
**************************************************************************/
|
|
5045
|
+
get dataSource() {
|
|
5046
|
+
return super.dataSource;
|
|
5187
5047
|
}
|
|
5188
5048
|
/***************************************************************************
|
|
5189
5049
|
* *
|
|
5190
5050
|
* Public API *
|
|
5191
5051
|
* *
|
|
5192
5052
|
**************************************************************************/
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5053
|
+
/**
|
|
5054
|
+
* Load the next chunk of data.
|
|
5055
|
+
* Useful for infinite scroll like data flows.
|
|
5056
|
+
*
|
|
5057
|
+
*/
|
|
5058
|
+
loadMore() {
|
|
5059
|
+
if (this.hasMoreDataSnapshot) {
|
|
5060
|
+
this.logger.info(this.id + ': Loading more...' + this._latestPage);
|
|
5061
|
+
if (this.loadingSnapshot) {
|
|
5062
|
+
return EMPTY;
|
|
5063
|
+
}
|
|
5064
|
+
const nextPage = this._latestPage + 1;
|
|
5065
|
+
return this.fetchPage(nextPage, this.chunkSize);
|
|
5066
|
+
}
|
|
5067
|
+
else {
|
|
5068
|
+
this.logger.debug(this.id + ': Cannot load more data, since no more data available.');
|
|
5069
|
+
return EMPTY;
|
|
5197
5070
|
}
|
|
5198
5071
|
}
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
this.
|
|
5204
|
-
this.currentFilters = currentFilters;
|
|
5205
|
-
this.isValid = isValid;
|
|
5072
|
+
get hasMoreData() {
|
|
5073
|
+
return this._hasMoreData;
|
|
5074
|
+
}
|
|
5075
|
+
get hasMoreDataSnapshot() {
|
|
5076
|
+
return this.checkHasMoreData(this.totalSnapshot, this.dataSnapshot);
|
|
5206
5077
|
}
|
|
5207
|
-
}
|
|
5208
|
-
class RequiredFilterEvaluator {
|
|
5209
5078
|
/***************************************************************************
|
|
5210
5079
|
* *
|
|
5211
|
-
*
|
|
5080
|
+
* Private Methods *
|
|
5212
5081
|
* *
|
|
5213
5082
|
**************************************************************************/
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
if (filterContext == null) {
|
|
5218
|
-
throw new Error('filterContext must not be null!');
|
|
5083
|
+
checkHasMoreData(total, data) {
|
|
5084
|
+
if (total) {
|
|
5085
|
+
return total > data.length;
|
|
5219
5086
|
}
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5087
|
+
return false;
|
|
5088
|
+
}
|
|
5089
|
+
reloadInternal() {
|
|
5090
|
+
// Since continuable data-contexts are appending data,
|
|
5091
|
+
// we need to clear it for a reload.
|
|
5092
|
+
this.clearAll(true);
|
|
5093
|
+
return this.fetchPage(0, this.chunkSize, true);
|
|
5094
|
+
}
|
|
5095
|
+
clearAll(silent = false) {
|
|
5096
|
+
super.clearAll(silent);
|
|
5097
|
+
this._pageCache = new Map();
|
|
5098
|
+
this._latestPage = 0;
|
|
5099
|
+
}
|
|
5100
|
+
fetchPage(pageIndex, pageSize, clear = false) {
|
|
5101
|
+
const subject = new Subject();
|
|
5102
|
+
const pageRequest = new Pageable(pageIndex, pageSize, this.sort.sortsSnapshot);
|
|
5103
|
+
if (this._pageCache.has(pageIndex)) {
|
|
5104
|
+
// Page already loaded - skipping request!
|
|
5105
|
+
this.logger.debug(this.id + ': Skipping fetching page since its already in page observable cache.');
|
|
5106
|
+
subject.next();
|
|
5223
5107
|
}
|
|
5224
|
-
|
|
5108
|
+
else {
|
|
5109
|
+
this.onLoading();
|
|
5110
|
+
this.logger.debug(this.id + `: Loading page ${pageIndex} using pageable:`, pageRequest);
|
|
5111
|
+
const pageObs = this.dataSource.findAllPaged(pageRequest, this.filter.filtersSnapshot);
|
|
5112
|
+
this._pageCache.set(pageIndex, pageObs);
|
|
5113
|
+
pageObs.subscribe((page) => {
|
|
5114
|
+
this.logger.debug(this.id + ': Got page data:', page);
|
|
5115
|
+
this.populatePageData(page, clear);
|
|
5116
|
+
if (this._latestPage < page.number) {
|
|
5117
|
+
this._latestPage = page.number; // TODO This might cause that pages are skipped
|
|
5118
|
+
}
|
|
5119
|
+
subject.next();
|
|
5120
|
+
this.onIdle();
|
|
5121
|
+
}, (err) => {
|
|
5122
|
+
this.onError(err);
|
|
5123
|
+
this.clearData();
|
|
5124
|
+
this.setTotal(0);
|
|
5125
|
+
this.logger.error(this.id + ': Failed to query data', err);
|
|
5126
|
+
subject.error(err);
|
|
5127
|
+
});
|
|
5128
|
+
}
|
|
5129
|
+
return subject.pipe(first());
|
|
5130
|
+
}
|
|
5131
|
+
/**
|
|
5132
|
+
* Load the data from the given page into the current data context
|
|
5133
|
+
*/
|
|
5134
|
+
populatePageData(page, clear) {
|
|
5135
|
+
try {
|
|
5136
|
+
this.setTotal(page.totalElements);
|
|
5137
|
+
const start = page.number * page.size;
|
|
5138
|
+
if (clear) {
|
|
5139
|
+
this.setData(page.content);
|
|
5140
|
+
}
|
|
5141
|
+
else {
|
|
5142
|
+
this.insertData(page.content, start);
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
catch (err) {
|
|
5146
|
+
this.onError(err);
|
|
5147
|
+
this.logger.error(this.id + ': Failed to populate data with page', page, err);
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
class DataContextContinuableToken extends DataContextContinuableBase {
|
|
5153
|
+
/***************************************************************************
|
|
5154
|
+
* *
|
|
5155
|
+
* Constructor *
|
|
5156
|
+
* *
|
|
5157
|
+
**************************************************************************/
|
|
5158
|
+
constructor(dataSource, chunkSize, indexFn, localApply, localSort) {
|
|
5159
|
+
super(dataSource, chunkSize, indexFn, localApply, localSort);
|
|
5160
|
+
/***************************************************************************
|
|
5161
|
+
* *
|
|
5162
|
+
* Fields *
|
|
5163
|
+
* *
|
|
5164
|
+
**************************************************************************/
|
|
5165
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
5166
|
+
this._hasMoreData = new BehaviorSubject(false);
|
|
5167
|
+
this._chunkCache = new Set();
|
|
5225
5168
|
}
|
|
5226
5169
|
/***************************************************************************
|
|
5227
5170
|
* *
|
|
5228
5171
|
* Properties *
|
|
5229
5172
|
* *
|
|
5230
5173
|
**************************************************************************/
|
|
5231
|
-
get
|
|
5232
|
-
return
|
|
5174
|
+
get dataSource() {
|
|
5175
|
+
return super.dataSource;
|
|
5233
5176
|
}
|
|
5234
|
-
|
|
5235
|
-
|
|
5177
|
+
/***************************************************************************
|
|
5178
|
+
* *
|
|
5179
|
+
* Public API *
|
|
5180
|
+
* *
|
|
5181
|
+
**************************************************************************/
|
|
5182
|
+
get hasMoreDataSnapshot() {
|
|
5183
|
+
return this._hasMoreData.getValue();
|
|
5236
5184
|
}
|
|
5237
|
-
get
|
|
5238
|
-
return this.
|
|
5185
|
+
get hasMoreData() {
|
|
5186
|
+
return this._hasMoreData.asObservable();
|
|
5187
|
+
}
|
|
5188
|
+
loadMore() {
|
|
5189
|
+
if (this.loadingSnapshot) {
|
|
5190
|
+
this.logger.debug(this.id + ': Skipping load-more since already loading a chunk!');
|
|
5191
|
+
return EMPTY;
|
|
5192
|
+
}
|
|
5193
|
+
const token = this._expectedChunkToken;
|
|
5194
|
+
if (token && token.length > 0) {
|
|
5195
|
+
return this.fetchNextChunk(token);
|
|
5196
|
+
}
|
|
5197
|
+
else {
|
|
5198
|
+
this.logger.debug(this.id + ': Cannot load more data, since no more data available.');
|
|
5199
|
+
return EMPTY;
|
|
5200
|
+
}
|
|
5239
5201
|
}
|
|
5240
5202
|
/***************************************************************************
|
|
5241
5203
|
* *
|
|
5242
|
-
*
|
|
5243
|
-
* *
|
|
5244
|
-
**************************************************************************/
|
|
5245
|
-
/***************************************************************************
|
|
5246
|
-
* *
|
|
5247
|
-
* Private methods *
|
|
5204
|
+
* Protect API *
|
|
5248
5205
|
* *
|
|
5249
5206
|
**************************************************************************/
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
return requiredFilters.some((filterGroup) => this.hasAllFilters(filterGroup, currentFilters));
|
|
5256
|
-
}
|
|
5257
|
-
return true;
|
|
5207
|
+
clearAll(silent = false) {
|
|
5208
|
+
super.clearAll(silent);
|
|
5209
|
+
this._chunkCache.clear();
|
|
5210
|
+
this._hasMoreData.next(true);
|
|
5211
|
+
this._expectedChunkToken = undefined;
|
|
5258
5212
|
}
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
.filter((f) => requiredFilterSet.has(f.key))
|
|
5265
|
-
.every((currentFilter) => Filter.hasValue(currentFilter));
|
|
5266
|
-
return areAllRequiredFiltersPresent && areAllRequiredFiltersHaveValue;
|
|
5213
|
+
reloadInternal() {
|
|
5214
|
+
// Since continuable data-contexts are appending data,
|
|
5215
|
+
// we need to clear it for a reload.
|
|
5216
|
+
this.clearAll(true);
|
|
5217
|
+
return this.fetchNextChunk(undefined);
|
|
5267
5218
|
}
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5219
|
+
fetchNextChunk(nextToken) {
|
|
5220
|
+
const subject = new Subject();
|
|
5221
|
+
nextToken = nextToken ? nextToken : undefined;
|
|
5222
|
+
if (this._chunkCache.has(nextToken)) {
|
|
5223
|
+
this.logger.debug(this.id +
|
|
5224
|
+
': Skipping fetching chunk for token "' +
|
|
5225
|
+
nextToken +
|
|
5226
|
+
'" since its already in observable cache.');
|
|
5227
|
+
subject.complete();
|
|
5228
|
+
}
|
|
5229
|
+
else {
|
|
5230
|
+
this.onLoading();
|
|
5231
|
+
this._chunkCache.add(nextToken);
|
|
5232
|
+
this.dataSource
|
|
5233
|
+
.findAllContinuable(new TokenChunkRequest(nextToken, this.filter.filtersSnapshot, this.sort.sortsSnapshot, this.chunkSize))
|
|
5234
|
+
.pipe(first())
|
|
5235
|
+
.subscribe({
|
|
5236
|
+
next: (chunk) => {
|
|
5237
|
+
this.onChunkFetched(chunk);
|
|
5238
|
+
subject.next(chunk);
|
|
5239
|
+
},
|
|
5240
|
+
error: (err) => {
|
|
5241
|
+
this.onChunkFetchError(err);
|
|
5242
|
+
subject.error(err);
|
|
5243
|
+
},
|
|
5244
|
+
});
|
|
5245
|
+
}
|
|
5246
|
+
return subject.pipe(take(1));
|
|
5273
5247
|
}
|
|
5274
|
-
|
|
5275
|
-
|
|
5248
|
+
onChunkFetched(chunk) {
|
|
5249
|
+
this.logger.debug(this.id + ': Got next chunk data:', chunk);
|
|
5250
|
+
this._hasMoreData.next(chunk.hasMore);
|
|
5251
|
+
this.updateChunkSize(chunk.chunkSize ?? chunk.maxChunkSize, false);
|
|
5252
|
+
this.populateChunkData(chunk);
|
|
5253
|
+
this.onIdle();
|
|
5276
5254
|
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5255
|
+
onChunkFetchError(err) {
|
|
5256
|
+
this.onError(err);
|
|
5257
|
+
this.logger.error(this.id + ': Failed to query data', err);
|
|
5258
|
+
this.clearData();
|
|
5259
|
+
this.setTotal(0);
|
|
5280
5260
|
}
|
|
5281
|
-
|
|
5282
|
-
|
|
5261
|
+
/**
|
|
5262
|
+
* Load the data from the given page into the current data context
|
|
5263
|
+
*/
|
|
5264
|
+
populateChunkData(chunk) {
|
|
5265
|
+
if (this.areTokenEqual(chunk.continuationToken, this._expectedChunkToken)) {
|
|
5266
|
+
try {
|
|
5267
|
+
this.setTotal(chunk.total);
|
|
5268
|
+
if (chunk.continuationToken) {
|
|
5269
|
+
// We had previous chunks so append to current data.
|
|
5270
|
+
this.appendData(chunk.content);
|
|
5271
|
+
}
|
|
5272
|
+
else {
|
|
5273
|
+
this.setData(chunk.content);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
catch (err) {
|
|
5277
|
+
this.onError(err);
|
|
5278
|
+
this.logger.error(this.id + ': Failed to populate data with chunk', chunk, err);
|
|
5279
|
+
}
|
|
5280
|
+
this._expectedChunkToken = chunk.nextContinuationToken;
|
|
5281
|
+
}
|
|
5282
|
+
else {
|
|
5283
|
+
this.logger.warn(this.id +
|
|
5284
|
+
': Discarding continuable chunk (items: ' +
|
|
5285
|
+
chunk.content.length +
|
|
5286
|
+
', token: ' +
|
|
5287
|
+
chunk.continuationToken +
|
|
5288
|
+
' )' +
|
|
5289
|
+
' as it does not match the expected contiunation-token: ' +
|
|
5290
|
+
this._expectedChunkToken);
|
|
5291
|
+
}
|
|
5283
5292
|
}
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5293
|
+
areTokenEqual(token1, token2) {
|
|
5294
|
+
if (!token1 && !token2) {
|
|
5295
|
+
return true;
|
|
5296
|
+
}
|
|
5297
|
+
return token1 === token2;
|
|
5287
5298
|
}
|
|
5288
5299
|
}
|
|
5289
|
-
|
|
5300
|
+
|
|
5301
|
+
class DataContextActivePage extends DataContextBase {
|
|
5290
5302
|
/***************************************************************************
|
|
5291
5303
|
* *
|
|
5292
5304
|
* Constructor *
|
|
5293
5305
|
* *
|
|
5294
5306
|
**************************************************************************/
|
|
5295
|
-
constructor(
|
|
5296
|
-
super(
|
|
5297
|
-
this._autoStartSpec = _autoStartSpec;
|
|
5307
|
+
constructor(dataSource, pageSize, indexFn, localApply, localSort) {
|
|
5308
|
+
super(dataSource, indexFn, localApply, localSort);
|
|
5298
5309
|
/***************************************************************************
|
|
5299
5310
|
* *
|
|
5300
5311
|
* Fields *
|
|
5301
5312
|
* *
|
|
5302
5313
|
**************************************************************************/
|
|
5303
|
-
this.
|
|
5304
|
-
|
|
5305
|
-
if (_autoStartSpec == null) {
|
|
5306
|
-
throw new Error('autoStartSpec must not be null!');
|
|
5307
|
-
}
|
|
5308
|
-
if (_autoStartSpec.requiredFilters) {
|
|
5309
|
-
this._autoStartConditionFulfilled$ = this.buildRequiredFilterConditionObservable();
|
|
5310
|
-
}
|
|
5311
|
-
else {
|
|
5312
|
-
// no condition defined, try to start immediately
|
|
5313
|
-
this.startDataContext();
|
|
5314
|
-
}
|
|
5315
|
-
this.subscribe();
|
|
5314
|
+
this.actlogger = LoggerFactory.getLogger(this.constructor.name);
|
|
5315
|
+
this._page = new BehaviorSubject(new PageRequest(0, pageSize));
|
|
5316
5316
|
}
|
|
5317
5317
|
/***************************************************************************
|
|
5318
5318
|
* *
|
|
5319
|
-
*
|
|
5319
|
+
* Properties *
|
|
5320
5320
|
* *
|
|
5321
5321
|
**************************************************************************/
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
if (hasRequiredFilters) {
|
|
5325
|
-
const startedTrue$ = this._dataContext.isStarted$.pipe(filter((t) => t));
|
|
5326
|
-
this._subscription = this._autoStartConditionFulfilled$
|
|
5327
|
-
.pipe(takeUntil(startedTrue$), tap((fulfilled) => this.logger.debug(`Got fulfilled event: ${fulfilled}`)), filter((fulfilled) => !!fulfilled))
|
|
5328
|
-
.subscribe(() => this.startDataContext());
|
|
5329
|
-
}
|
|
5322
|
+
get dataSource() {
|
|
5323
|
+
return super.dataSource;
|
|
5330
5324
|
}
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
* Private methods *
|
|
5334
|
-
* *
|
|
5335
|
-
**************************************************************************/
|
|
5336
|
-
buildRequiredFilterConditionObservable() {
|
|
5337
|
-
return new RequiredFilterEvaluator(this._dataContext.filter, this._autoStartSpec.requiredFilters).context$.pipe(map((event) => event.isValid));
|
|
5325
|
+
get page() {
|
|
5326
|
+
return this._page.asObservable();
|
|
5338
5327
|
}
|
|
5339
|
-
|
|
5340
|
-
this.
|
|
5341
|
-
this._dataContext.start(this._autoStartSpec.initialSort);
|
|
5328
|
+
get pageSnapshot() {
|
|
5329
|
+
return this._page.getValue();
|
|
5342
5330
|
}
|
|
5343
|
-
}
|
|
5344
|
-
|
|
5345
|
-
class DeepPartialPatcher {
|
|
5346
5331
|
/***************************************************************************
|
|
5347
5332
|
* *
|
|
5348
5333
|
* Public API *
|
|
5349
5334
|
* *
|
|
5350
5335
|
**************************************************************************/
|
|
5351
5336
|
/**
|
|
5352
|
-
*
|
|
5337
|
+
* Set the current page index and reload.
|
|
5338
|
+
* @param pageIndex
|
|
5353
5339
|
*/
|
|
5354
|
-
|
|
5355
|
-
|
|
5340
|
+
setActiveIndex(pageIndex) {
|
|
5341
|
+
this.setActivePage(new PageRequest(pageIndex, this.pageSnapshot.size));
|
|
5356
5342
|
}
|
|
5357
5343
|
/**
|
|
5358
|
-
*
|
|
5344
|
+
* Sets the current page index / size and reload.
|
|
5359
5345
|
*/
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5346
|
+
setActivePage(request) {
|
|
5347
|
+
if (!request) {
|
|
5348
|
+
throw new Error(this.id + ': Setting page PageRequest must not be null!');
|
|
5349
|
+
}
|
|
5350
|
+
let hasChange = false;
|
|
5351
|
+
const page = this.pageSnapshot;
|
|
5352
|
+
if (page.index !== request.index) {
|
|
5353
|
+
hasChange = true;
|
|
5354
|
+
}
|
|
5355
|
+
else if (page.size !== request.size) {
|
|
5356
|
+
hasChange = true;
|
|
5357
|
+
}
|
|
5358
|
+
if (hasChange) {
|
|
5359
|
+
this._page.next(request);
|
|
5360
|
+
this.reload('setActivePage');
|
|
5361
|
+
}
|
|
5363
5362
|
}
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
const result = original;
|
|
5369
|
-
for (const key in patch) {
|
|
5370
|
-
if (patch[key] && typeof patch[key] === 'object' && !Array.isArray(patch[key])) {
|
|
5371
|
-
// Recursively apply patch for nested objects
|
|
5372
|
-
result[key] = DeepPartialPatcher.patchOriginal(result[key] ?? new Object(patch[key]), patch[key]);
|
|
5373
|
-
}
|
|
5374
|
-
else {
|
|
5375
|
-
// Apply patch for non-object fields
|
|
5376
|
-
result[key] = patch[key];
|
|
5377
|
-
}
|
|
5363
|
+
close() {
|
|
5364
|
+
super.close();
|
|
5365
|
+
if (this._activePageChangedSub) {
|
|
5366
|
+
this._activePageChangedSub.unsubscribe();
|
|
5378
5367
|
}
|
|
5379
|
-
return result;
|
|
5380
5368
|
}
|
|
5381
5369
|
/***************************************************************************
|
|
5382
5370
|
* *
|
|
5383
|
-
* Private methods *
|
|
5371
|
+
* Private methods *
|
|
5372
|
+
* *
|
|
5373
|
+
**************************************************************************/
|
|
5374
|
+
clearAll() {
|
|
5375
|
+
super.clearAll();
|
|
5376
|
+
this.setActiveIndex(0);
|
|
5377
|
+
}
|
|
5378
|
+
reloadInternal() {
|
|
5379
|
+
if (this._activePageLoad) {
|
|
5380
|
+
// Cancel previous pending request
|
|
5381
|
+
this._activePageLoad.unsubscribe();
|
|
5382
|
+
}
|
|
5383
|
+
const subject = new Subject();
|
|
5384
|
+
this.onLoading();
|
|
5385
|
+
const page = this.pageSnapshot;
|
|
5386
|
+
const pageRequest = new Pageable(page.index, page.size, this.sort.sortsSnapshot);
|
|
5387
|
+
this._activePageLoad = this.dataSource
|
|
5388
|
+
.findAllPaged(pageRequest, this.filter.filtersSnapshot)
|
|
5389
|
+
.pipe(first())
|
|
5390
|
+
.subscribe((success) => {
|
|
5391
|
+
this.setTotal(success.totalElements);
|
|
5392
|
+
this.setData(success.content);
|
|
5393
|
+
subject.next(success);
|
|
5394
|
+
this.onIdle();
|
|
5395
|
+
}, (err) => {
|
|
5396
|
+
this.clearData();
|
|
5397
|
+
this.actlogger.error(this.id + ': Failed to query data', err);
|
|
5398
|
+
subject.error(err);
|
|
5399
|
+
this.onError(err);
|
|
5400
|
+
}, () => {
|
|
5401
|
+
this.onIdle();
|
|
5402
|
+
});
|
|
5403
|
+
return subject.pipe(first());
|
|
5404
|
+
}
|
|
5405
|
+
/***************************************************************************
|
|
5406
|
+
* *
|
|
5407
|
+
* Event handlers *
|
|
5384
5408
|
* *
|
|
5385
5409
|
**************************************************************************/
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
}, {});
|
|
5410
|
+
/**
|
|
5411
|
+
* Occurs when the sorts property has changed.
|
|
5412
|
+
*/
|
|
5413
|
+
onSortsChanged(sorts) {
|
|
5414
|
+
this.setActiveIndex(0);
|
|
5415
|
+
super.onSortsChanged(sorts);
|
|
5416
|
+
}
|
|
5417
|
+
/**
|
|
5418
|
+
* Occurs when the filtersSnapshot property has changed.
|
|
5419
|
+
*/
|
|
5420
|
+
onFiltersChanged(filters) {
|
|
5421
|
+
this.setActiveIndex(0);
|
|
5422
|
+
super.onFiltersChanged(filters);
|
|
5400
5423
|
}
|
|
5401
5424
|
}
|
|
5402
5425
|
|
|
5403
|
-
class
|
|
5426
|
+
class MatTableDataContextBindingBuilder {
|
|
5404
5427
|
/***************************************************************************
|
|
5405
5428
|
* *
|
|
5406
|
-
*
|
|
5429
|
+
* Static Builder *
|
|
5407
5430
|
* *
|
|
5408
5431
|
**************************************************************************/
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5432
|
+
static start(dataContext$) {
|
|
5433
|
+
return new MatTableDataContextBindingBuilder(dataContext$);
|
|
5434
|
+
}
|
|
5435
|
+
constructor(dataContext$) {
|
|
5436
|
+
/***************************************************************************
|
|
5437
|
+
* *
|
|
5438
|
+
* Fields *
|
|
5439
|
+
* *
|
|
5440
|
+
**************************************************************************/
|
|
5441
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
5442
|
+
this._dataContext$ = dataContext$;
|
|
5414
5443
|
}
|
|
5415
5444
|
/***************************************************************************
|
|
5416
5445
|
* *
|
|
5417
|
-
*
|
|
5446
|
+
* Public API *
|
|
5418
5447
|
* *
|
|
5419
5448
|
**************************************************************************/
|
|
5420
|
-
|
|
5421
|
-
this.
|
|
5449
|
+
withSorts(sorts$) {
|
|
5450
|
+
this._sorts$ = sorts$;
|
|
5451
|
+
return this;
|
|
5452
|
+
}
|
|
5453
|
+
withPaginator(paginator$) {
|
|
5454
|
+
this._matPaginator$ = paginator$;
|
|
5455
|
+
return this;
|
|
5456
|
+
}
|
|
5457
|
+
withContinuator(continuator$) {
|
|
5458
|
+
this._continuator$ = continuator$;
|
|
5459
|
+
return this;
|
|
5460
|
+
}
|
|
5461
|
+
bindUntil(destroy$) {
|
|
5462
|
+
return new MatTableDataContextBinding(this._dataContext$, this._sorts$, this._matPaginator$, this._continuator$, destroy$);
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
class MatTableDataContextBinding {
|
|
5466
|
+
/***************************************************************************
|
|
5467
|
+
* *
|
|
5468
|
+
* Constructor *
|
|
5469
|
+
* *
|
|
5470
|
+
**************************************************************************/
|
|
5471
|
+
constructor(_dataContext$, _matSorts$, _matPaginator$, _continuator$, destroy$) {
|
|
5472
|
+
this._dataContext$ = _dataContext$;
|
|
5473
|
+
this._matSorts$ = _matSorts$;
|
|
5474
|
+
this._matPaginator$ = _matPaginator$;
|
|
5475
|
+
this._continuator$ = _continuator$;
|
|
5476
|
+
/***************************************************************************
|
|
5477
|
+
* *
|
|
5478
|
+
* Fields *
|
|
5479
|
+
* *
|
|
5480
|
+
**************************************************************************/
|
|
5481
|
+
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
5482
|
+
this.subscribeUntil(destroy$);
|
|
5422
5483
|
}
|
|
5423
5484
|
/***************************************************************************
|
|
5424
5485
|
* *
|
|
5425
5486
|
* Private methods *
|
|
5426
5487
|
* *
|
|
5427
5488
|
**************************************************************************/
|
|
5428
|
-
|
|
5429
|
-
if (this.
|
|
5430
|
-
this.
|
|
5431
|
-
|
|
5432
|
-
}
|
|
5433
|
-
// We might also be able to perform deletions locally, and avoid reload data
|
|
5434
|
-
if (event.modified) {
|
|
5435
|
-
this.updateExisting(event.modified);
|
|
5489
|
+
subscribeUntil(destroy$) {
|
|
5490
|
+
if (this._matSorts$) {
|
|
5491
|
+
this.bindMatSortsToDataContextUntil(this._matSorts$, destroy$);
|
|
5492
|
+
this.bindDataContextToMatSortsUntil(this._matSorts$, destroy$);
|
|
5436
5493
|
}
|
|
5437
|
-
if (
|
|
5438
|
-
this.
|
|
5494
|
+
if (this._matPaginator$) {
|
|
5495
|
+
this.bindPaginatorUntil(this._matPaginator$, destroy$);
|
|
5439
5496
|
}
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
if (entities.length > 0) {
|
|
5443
|
-
this._dataContext.update(entities);
|
|
5497
|
+
if (this._continuator$) {
|
|
5498
|
+
this.bindContinuatorUntil(this._continuator$, destroy$);
|
|
5444
5499
|
}
|
|
5445
5500
|
}
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
if (existing) {
|
|
5456
|
-
return DeepPartialPatcher.patchShallowCopy(existing, patch.patch);
|
|
5501
|
+
bindDataContextToMatSortsUntil(matSorts$, destroy$) {
|
|
5502
|
+
const dcSorts$ = this._dataContext$.pipe(filter((dc) => !!dc), switchMap$1((dc) => dc.sort.sorts));
|
|
5503
|
+
combineLatest([dcSorts$, matSorts$])
|
|
5504
|
+
.pipe(takeUntil(destroy$))
|
|
5505
|
+
.subscribe(([dcSorts, matSorts]) => {
|
|
5506
|
+
if (dcSorts.length >= 1) {
|
|
5507
|
+
// At least one sort active
|
|
5508
|
+
const sort = dcSorts[0];
|
|
5509
|
+
this.updateMatSorts(sort, matSorts);
|
|
5457
5510
|
}
|
|
5458
5511
|
else {
|
|
5459
|
-
|
|
5512
|
+
// No sort active
|
|
5513
|
+
this.updateMatSorts(Sort.NONE, matSorts);
|
|
5460
5514
|
}
|
|
5461
|
-
})
|
|
5462
|
-
.filter((update) => !!update);
|
|
5515
|
+
});
|
|
5463
5516
|
}
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
this.entityId = entityId;
|
|
5469
|
-
this.patch = patch;
|
|
5517
|
+
updateMatSorts(dcSort, matSorts) {
|
|
5518
|
+
const active = dcSort.prop;
|
|
5519
|
+
const direction = this.toMatDirection(dcSort.dir);
|
|
5520
|
+
matSorts.forEach((matSort) => this.updateMatSort(active, direction, matSort));
|
|
5470
5521
|
}
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5522
|
+
updateMatSort(activeProp, direction, matSort) {
|
|
5523
|
+
if (activeProp) {
|
|
5524
|
+
if (!matSort.sortables.has(activeProp)) {
|
|
5525
|
+
// The current sort property is not part of this MatSort.
|
|
5526
|
+
activeProp = Sort.NONE.prop; // Force no sort in this mat context
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
if (matSort.active !== activeProp || matSort.direction !== direction) {
|
|
5530
|
+
// We do only update matSort when there was a real change
|
|
5531
|
+
matSort.active = activeProp;
|
|
5532
|
+
matSort.direction = direction;
|
|
5533
|
+
matSort._stateChanges.next();
|
|
5534
|
+
}
|
|
5478
5535
|
}
|
|
5479
|
-
|
|
5480
|
-
|
|
5536
|
+
bindMatSortsToDataContextUntil(sorts$, destroy$) {
|
|
5537
|
+
const sortChanges$ = sorts$.pipe(mergeMap((sorts) => merge(...sorts.map((s) => s.sortChange))), map((matSort) => {
|
|
5538
|
+
return new Sort(matSort.active, this.fromMatDirection(matSort.direction));
|
|
5539
|
+
}));
|
|
5540
|
+
combineLatest([this._dataContext$, sortChanges$])
|
|
5541
|
+
.pipe(takeUntil(destroy$))
|
|
5542
|
+
.subscribe(([dc, sortRequest]) => {
|
|
5543
|
+
if (dc) {
|
|
5544
|
+
dc.sort.updateSort(sortRequest);
|
|
5545
|
+
}
|
|
5546
|
+
});
|
|
5481
5547
|
}
|
|
5482
|
-
|
|
5483
|
-
|
|
5548
|
+
bindPaginatorUntil(paginator$, destroy$) {
|
|
5549
|
+
const pageRequest$ = paginator$.pipe(filter((paginator) => !!paginator), switchMap$1((paginator) => paginator.page), map((pageEvent) => new PageRequest(pageEvent.pageIndex, pageEvent.pageSize)));
|
|
5550
|
+
combineLatest([this._dataContext$, pageRequest$])
|
|
5551
|
+
.pipe(takeUntil(destroy$))
|
|
5552
|
+
.subscribe(([dc, pageRequest]) => {
|
|
5553
|
+
if (dc) {
|
|
5554
|
+
if (isActivePagedDataContext(dc)) {
|
|
5555
|
+
const pagedDc = dc;
|
|
5556
|
+
pagedDc.setActivePage(pageRequest);
|
|
5557
|
+
}
|
|
5558
|
+
else {
|
|
5559
|
+
this.logger.warn('Can not bind the given paginator to the given data-context,' +
|
|
5560
|
+
' as the datacontext does not support pagination!', dc);
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
});
|
|
5484
5564
|
}
|
|
5485
|
-
|
|
5486
|
-
|
|
5565
|
+
bindContinuatorUntil(continuator$, destroy$) {
|
|
5566
|
+
const chunkSizeChange$ = continuator$.pipe(filter((continuator) => !!continuator), switchMap$1((continuator) => outputToObservable(continuator.chunkSizeChange)));
|
|
5567
|
+
combineLatest([this._dataContext$, chunkSizeChange$])
|
|
5568
|
+
.pipe(takeUntil(destroy$))
|
|
5569
|
+
.subscribe(([dc, newChunkSize]) => {
|
|
5570
|
+
if (dc) {
|
|
5571
|
+
if (isContinuableDataContext(dc)) {
|
|
5572
|
+
const continuableDc = dc;
|
|
5573
|
+
continuableDc.chunkSize = newChunkSize;
|
|
5574
|
+
}
|
|
5575
|
+
else {
|
|
5576
|
+
this.logger.warn('Can not bind the given Continuator to the given data-context,' +
|
|
5577
|
+
' as the DataContext does not support continuation!', dc);
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
});
|
|
5487
5581
|
}
|
|
5488
|
-
|
|
5489
|
-
return
|
|
5582
|
+
toMatDirection(direction) {
|
|
5583
|
+
return direction;
|
|
5490
5584
|
}
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
this.modified = modified;
|
|
5494
|
-
this.created = created;
|
|
5495
|
-
this.patches = patches;
|
|
5496
|
-
this.unknownChanges = !deletedIds && !modified && !created && !patches;
|
|
5585
|
+
fromMatDirection(matSortDirection) {
|
|
5586
|
+
return matSortDirection;
|
|
5497
5587
|
}
|
|
5498
5588
|
}
|
|
5499
5589
|
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
}
|
|
5516
|
-
else if (idPropertyOrExtractFn === null || idPropertyOrExtractFn === undefined) {
|
|
5517
|
-
return EntityIdUtil.extractIdFn(null);
|
|
5518
|
-
}
|
|
5519
|
-
else {
|
|
5520
|
-
throw new Error('Invalid idPropertyOrExtractFn');
|
|
5590
|
+
/**
|
|
5591
|
+
* Allows making any kind of subscription which will be automatically unsubscribed
|
|
5592
|
+
* upon data context closing/cleanup.
|
|
5593
|
+
*/
|
|
5594
|
+
class DataContextLifeCycleBinding {
|
|
5595
|
+
/***************************************************************************
|
|
5596
|
+
* *
|
|
5597
|
+
* Constructor *
|
|
5598
|
+
* *
|
|
5599
|
+
**************************************************************************/
|
|
5600
|
+
constructor(_dataContext) {
|
|
5601
|
+
this._dataContext = _dataContext;
|
|
5602
|
+
// eslint-disable-next-line eqeqeq
|
|
5603
|
+
if (_dataContext == null) {
|
|
5604
|
+
throw new Error('dataContext must not be null!');
|
|
5521
5605
|
}
|
|
5606
|
+
this._dataContext.data.subscribe({
|
|
5607
|
+
complete: () => this.unsubscribe(),
|
|
5608
|
+
});
|
|
5522
5609
|
}
|
|
5523
|
-
|
|
5524
|
-
|
|
5610
|
+
/***************************************************************************
|
|
5611
|
+
* *
|
|
5612
|
+
* Public API *
|
|
5613
|
+
* *
|
|
5614
|
+
**************************************************************************/
|
|
5615
|
+
unsubscribe() {
|
|
5616
|
+
if (this._subscription) {
|
|
5617
|
+
this._subscription.unsubscribe();
|
|
5618
|
+
this._subscription = null;
|
|
5619
|
+
}
|
|
5525
5620
|
}
|
|
5526
5621
|
}
|
|
5527
5622
|
|
|
5528
|
-
class
|
|
5623
|
+
class RequiredFilterContextChangedEvent {
|
|
5624
|
+
constructor(requiredFilters, currentFilters, isValid) {
|
|
5625
|
+
this.requiredFilters = requiredFilters;
|
|
5626
|
+
this.currentFilters = currentFilters;
|
|
5627
|
+
this.isValid = isValid;
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
class RequiredFilterEvaluator {
|
|
5529
5631
|
/***************************************************************************
|
|
5530
5632
|
* *
|
|
5531
|
-
*
|
|
5633
|
+
* Fields *
|
|
5532
5634
|
* *
|
|
5533
5635
|
**************************************************************************/
|
|
5534
|
-
constructor(
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
this.
|
|
5541
|
-
|
|
5636
|
+
constructor(filterContext, requiredFilters) {
|
|
5637
|
+
this._requiredFilters = new BehaviorSubject([]);
|
|
5638
|
+
// eslint-disable-next-line eqeqeq
|
|
5639
|
+
if (filterContext == null) {
|
|
5640
|
+
throw new Error('filterContext must not be null!');
|
|
5641
|
+
}
|
|
5642
|
+
this._filterContext = filterContext;
|
|
5643
|
+
if (requiredFilters) {
|
|
5644
|
+
this._requiredFilters.next(requiredFilters);
|
|
5645
|
+
}
|
|
5646
|
+
this.context$ = combineLatest([this._requiredFilters, this._filterContext.filters]).pipe(map(([required, currentFilters]) => this.createEvent(requiredFilters, currentFilters)));
|
|
5542
5647
|
}
|
|
5543
5648
|
/***************************************************************************
|
|
5544
5649
|
* *
|
|
5545
5650
|
* Properties *
|
|
5546
5651
|
* *
|
|
5547
5652
|
**************************************************************************/
|
|
5548
|
-
get
|
|
5549
|
-
return this.
|
|
5653
|
+
get filterContext() {
|
|
5654
|
+
return this._filterContext;
|
|
5655
|
+
}
|
|
5656
|
+
get requiredFilters$() {
|
|
5657
|
+
return this._requiredFilters.asObservable();
|
|
5658
|
+
}
|
|
5659
|
+
get requiredFilters() {
|
|
5660
|
+
return this._requiredFilters.getValue();
|
|
5550
5661
|
}
|
|
5551
5662
|
/***************************************************************************
|
|
5552
5663
|
* *
|
|
5553
5664
|
* Public API *
|
|
5554
5665
|
* *
|
|
5555
5666
|
**************************************************************************/
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
class SortUtil {
|
|
5565
|
-
static toggleDir(dir) {
|
|
5566
|
-
if (dir === 'asc') {
|
|
5567
|
-
return 'desc';
|
|
5568
|
-
}
|
|
5569
|
-
else {
|
|
5570
|
-
return 'asc';
|
|
5571
|
-
}
|
|
5572
|
-
}
|
|
5573
|
-
static toggleSort(sort) {
|
|
5574
|
-
return new Sort(sort.prop, SortUtil.toggleDir(sort.dir));
|
|
5575
|
-
}
|
|
5576
|
-
static sortData(data, sorts, prefix) {
|
|
5577
|
-
if (sorts && sorts.length > 0) {
|
|
5578
|
-
const copy = [...data];
|
|
5579
|
-
const sortFields = sorts.map((s) => (s.dir === 'desc' ? '-' : '') + SortUtil.propertyPath(s, prefix));
|
|
5580
|
-
return copy.sort(ComparatorBuilder.fieldSort(...sortFields));
|
|
5581
|
-
}
|
|
5582
|
-
else {
|
|
5583
|
-
return data;
|
|
5584
|
-
}
|
|
5667
|
+
/***************************************************************************
|
|
5668
|
+
* *
|
|
5669
|
+
* Private methods *
|
|
5670
|
+
* *
|
|
5671
|
+
**************************************************************************/
|
|
5672
|
+
createEvent(requiredFilters, currentFilters) {
|
|
5673
|
+
return new RequiredFilterContextChangedEvent(requiredFilters, currentFilters, this.allRequiredFiltersPresent(requiredFilters, currentFilters));
|
|
5585
5674
|
}
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
* @param other
|
|
5590
|
-
*/
|
|
5591
|
-
static equalsExactRefs(data, other) {
|
|
5592
|
-
// eslint-disable-next-line eqeqeq
|
|
5593
|
-
if (data.length == other.length) {
|
|
5594
|
-
for (let i = 0; i < data.length; i++) {
|
|
5595
|
-
// eslint-disable-next-line eqeqeq
|
|
5596
|
-
if (data[i] != other[i]) {
|
|
5597
|
-
return false;
|
|
5598
|
-
}
|
|
5599
|
-
}
|
|
5600
|
-
return true;
|
|
5675
|
+
allRequiredFiltersPresent(requiredFilters, currentFilters) {
|
|
5676
|
+
if (requiredFilters) {
|
|
5677
|
+
return requiredFilters.some((filterGroup) => this.hasAllFilters(filterGroup, currentFilters));
|
|
5601
5678
|
}
|
|
5602
|
-
return
|
|
5679
|
+
return true;
|
|
5603
5680
|
}
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5681
|
+
hasAllFilters(requiredFilters, currentFilters) {
|
|
5682
|
+
const requiredFilterSet = new Set(requiredFilters);
|
|
5683
|
+
const currentFilterKeySet = new Set(currentFilters.map((f) => f.key));
|
|
5684
|
+
const areAllRequiredFiltersPresent = requiredFilters.every((required) => currentFilterKeySet.has(required));
|
|
5685
|
+
const areAllRequiredFiltersHaveValue = currentFilters
|
|
5686
|
+
.filter((f) => requiredFilterSet.has(f.key))
|
|
5687
|
+
.every((currentFilter) => Filter.hasValue(currentFilter));
|
|
5688
|
+
return areAllRequiredFiltersPresent && areAllRequiredFiltersHaveValue;
|
|
5611
5689
|
}
|
|
5612
5690
|
}
|
|
5613
5691
|
|
|
5614
|
-
class
|
|
5615
|
-
static
|
|
5616
|
-
return
|
|
5617
|
-
map.set(keyFn(value), value);
|
|
5618
|
-
return map;
|
|
5619
|
-
}, new Map());
|
|
5692
|
+
class AutoStartSpec {
|
|
5693
|
+
static asap(sort) {
|
|
5694
|
+
return new AutoStartSpec(null, sort);
|
|
5620
5695
|
}
|
|
5621
|
-
static
|
|
5622
|
-
|
|
5623
|
-
values.forEach((value) => {
|
|
5624
|
-
const key = keyFn(value);
|
|
5625
|
-
let group = groups.get(key);
|
|
5626
|
-
if (!group) {
|
|
5627
|
-
group = [];
|
|
5628
|
-
groups.set(key, group);
|
|
5629
|
-
}
|
|
5630
|
-
group.push(value);
|
|
5631
|
-
});
|
|
5632
|
-
return groups;
|
|
5696
|
+
static requireFiltersAll(filters, sort) {
|
|
5697
|
+
return AutoStartSpec.requireFilters([filters], sort);
|
|
5633
5698
|
}
|
|
5634
|
-
static
|
|
5635
|
-
const
|
|
5636
|
-
|
|
5637
|
-
return newMap;
|
|
5699
|
+
static requireFiltersAny(filters, sort) {
|
|
5700
|
+
const separateFilterArrays = filters.map((singleFilter) => [singleFilter]);
|
|
5701
|
+
return AutoStartSpec.requireFilters(separateFilterArrays, sort);
|
|
5638
5702
|
}
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
class LocalListDataSource extends DataSourceBase {
|
|
5642
|
-
/***************************************************************************
|
|
5643
|
-
* *
|
|
5644
|
-
* Static Builder *
|
|
5645
|
-
* *
|
|
5646
|
-
**************************************************************************/
|
|
5647
|
-
/**
|
|
5648
|
-
* Creates an empty local list data-source.
|
|
5649
|
-
* You can set / modify data by using the data property.
|
|
5650
|
-
* @param idPropertyOrExtractor
|
|
5651
|
-
* @param localSort
|
|
5652
|
-
* @param localFilter
|
|
5653
|
-
*/
|
|
5654
|
-
static empty(idPropertyOrExtractor, localSort, localFilter) {
|
|
5655
|
-
return this.from([], idPropertyOrExtractor, localSort, localFilter);
|
|
5703
|
+
static requireFilters(filters, sort) {
|
|
5704
|
+
return new AutoStartSpec(filters, sort);
|
|
5656
5705
|
}
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
}
|
|
5661
|
-
return new LocalListDataSource(localData, localSort, localFilter, idPropertyOrExtractor);
|
|
5706
|
+
constructor(requiredFilters, initialSort) {
|
|
5707
|
+
this.requiredFilters = requiredFilters;
|
|
5708
|
+
this.initialSort = initialSort;
|
|
5662
5709
|
}
|
|
5710
|
+
}
|
|
5711
|
+
class DataContextAutoStarter extends DataContextLifeCycleBinding {
|
|
5663
5712
|
/***************************************************************************
|
|
5664
5713
|
* *
|
|
5665
5714
|
* Constructor *
|
|
5666
5715
|
* *
|
|
5667
5716
|
**************************************************************************/
|
|
5668
|
-
constructor(
|
|
5669
|
-
super(
|
|
5717
|
+
constructor(dataContext, _autoStartSpec) {
|
|
5718
|
+
super(dataContext);
|
|
5719
|
+
this._autoStartSpec = _autoStartSpec;
|
|
5670
5720
|
/***************************************************************************
|
|
5671
5721
|
* *
|
|
5672
5722
|
* Fields *
|
|
5673
5723
|
* *
|
|
5674
5724
|
**************************************************************************/
|
|
5675
5725
|
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
5676
|
-
|
|
5677
|
-
if (
|
|
5678
|
-
throw new Error('
|
|
5726
|
+
// eslint-disable-next-line eqeqeq
|
|
5727
|
+
if (_autoStartSpec == null) {
|
|
5728
|
+
throw new Error('autoStartSpec must not be null!');
|
|
5679
5729
|
}
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5730
|
+
if (_autoStartSpec.requiredFilters) {
|
|
5731
|
+
this._autoStartConditionFulfilled$ = this.buildRequiredFilterConditionObservable();
|
|
5732
|
+
}
|
|
5733
|
+
else {
|
|
5734
|
+
// no condition defined, try to start immediately
|
|
5735
|
+
this.startDataContext();
|
|
5736
|
+
}
|
|
5737
|
+
this.subscribe();
|
|
5683
5738
|
}
|
|
5684
5739
|
/***************************************************************************
|
|
5685
5740
|
* *
|
|
5686
|
-
*
|
|
5741
|
+
* Public API *
|
|
5687
5742
|
* *
|
|
5688
5743
|
**************************************************************************/
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5744
|
+
subscribe() {
|
|
5745
|
+
const hasRequiredFilters = !!this._autoStartConditionFulfilled$;
|
|
5746
|
+
if (hasRequiredFilters) {
|
|
5747
|
+
const startedTrue$ = this._dataContext.isStarted$.pipe(filter((t) => t));
|
|
5748
|
+
this._subscription = this._autoStartConditionFulfilled$
|
|
5749
|
+
.pipe(takeUntil(startedTrue$), tap((fulfilled) => this.logger.debug(`Got fulfilled event: ${fulfilled}`)), filter((fulfilled) => !!fulfilled))
|
|
5750
|
+
.subscribe(() => this.startDataContext());
|
|
5751
|
+
}
|
|
5694
5752
|
}
|
|
5695
5753
|
/***************************************************************************
|
|
5696
5754
|
* *
|
|
5697
|
-
*
|
|
5755
|
+
* Private methods *
|
|
5698
5756
|
* *
|
|
5699
5757
|
**************************************************************************/
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
throw new Error('findById: id argument required!');
|
|
5703
|
-
}
|
|
5704
|
-
const found = this.data.find((d) => this.getId(d) === id);
|
|
5705
|
-
if (found) {
|
|
5706
|
-
return of(found);
|
|
5707
|
-
}
|
|
5708
|
-
else {
|
|
5709
|
-
return throwError(() => new Error("Could not find local entity by id: '" + id + "'"));
|
|
5710
|
-
}
|
|
5711
|
-
}
|
|
5712
|
-
findByIds(ids) {
|
|
5713
|
-
if (ids === undefined || ids === null) {
|
|
5714
|
-
throw new Error('findByIds: ids array argument required!');
|
|
5715
|
-
}
|
|
5716
|
-
const desiredIds = new Set(ids);
|
|
5717
|
-
return of(this.data.filter((d) => desiredIds.has(this.getId(d))));
|
|
5758
|
+
buildRequiredFilterConditionObservable() {
|
|
5759
|
+
return new RequiredFilterEvaluator(this._dataContext.filter, this._autoStartSpec.requiredFilters).context$.pipe(map((event) => event.isValid));
|
|
5718
5760
|
}
|
|
5719
|
-
|
|
5720
|
-
|
|
5761
|
+
startDataContext() {
|
|
5762
|
+
this.logger.debug(this._dataContext.id + ': Auto starting ...');
|
|
5763
|
+
this._dataContext.start(this._autoStartSpec.initialSort);
|
|
5721
5764
|
}
|
|
5765
|
+
}
|
|
5766
|
+
|
|
5767
|
+
class DeepPartialPatcher {
|
|
5722
5768
|
/***************************************************************************
|
|
5723
5769
|
* *
|
|
5724
5770
|
* Public API *
|
|
5725
5771
|
* *
|
|
5726
5772
|
**************************************************************************/
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
this.deleteAll([entity]);
|
|
5733
|
-
}
|
|
5734
|
-
deleteAll(toDelete) {
|
|
5735
|
-
if (toDelete?.length > 0) {
|
|
5736
|
-
return this.deleteAllById(toDelete.map((e) => this.getId(e)));
|
|
5737
|
-
}
|
|
5738
|
-
}
|
|
5739
|
-
deleteAllById(idsToDelete) {
|
|
5740
|
-
if (idsToDelete?.length > 0) {
|
|
5741
|
-
const existing = this.data;
|
|
5742
|
-
const idsToDeleteSet = new Set(idsToDelete);
|
|
5743
|
-
this.silentReplaceData(existing.filter((e) => !idsToDeleteSet.has(this.getId(e))));
|
|
5744
|
-
this.publishChangeEvent(DataSourceChangeEvent.deleted(idsToDelete));
|
|
5745
|
-
}
|
|
5746
|
-
}
|
|
5747
|
-
saveAll(toSave) {
|
|
5748
|
-
const idDataMap = this.buildIdDataMap(this.data);
|
|
5749
|
-
const createdEntities = [];
|
|
5750
|
-
const modifiedEntities = [];
|
|
5751
|
-
toSave.forEach((entity) => {
|
|
5752
|
-
const id = this.getId(entity);
|
|
5753
|
-
if (!idDataMap.has(id)) {
|
|
5754
|
-
createdEntities.push(entity);
|
|
5755
|
-
}
|
|
5756
|
-
else {
|
|
5757
|
-
modifiedEntities.push(entity);
|
|
5758
|
-
}
|
|
5759
|
-
idDataMap.set(id, entity);
|
|
5760
|
-
});
|
|
5761
|
-
this.silentReplaceData(Array.from(idDataMap.values()));
|
|
5762
|
-
this.publishChanges(createdEntities, modifiedEntities);
|
|
5773
|
+
/**
|
|
5774
|
+
* All modified parts are deep cloned so the original is never modified.
|
|
5775
|
+
*/
|
|
5776
|
+
static patchDeepClone(original, patch) {
|
|
5777
|
+
return this.mapReducePatch(original, patch);
|
|
5763
5778
|
}
|
|
5764
|
-
|
|
5765
|
-
|
|
5779
|
+
/**
|
|
5780
|
+
* Returns a shallow copy of the original, but may modify nested objects.
|
|
5781
|
+
*/
|
|
5782
|
+
static patchShallowCopy(original, patch) {
|
|
5783
|
+
const shallowCopy = { ...original };
|
|
5784
|
+
return this.patchOriginal(shallowCopy, patch);
|
|
5766
5785
|
}
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
if (
|
|
5774
|
-
|
|
5786
|
+
/**
|
|
5787
|
+
* Modifies the original directly. Returns a reference to the original object.
|
|
5788
|
+
*/
|
|
5789
|
+
static patchOriginal(original, patch) {
|
|
5790
|
+
const result = original;
|
|
5791
|
+
for (const key in patch) {
|
|
5792
|
+
if (patch[key] && typeof patch[key] === 'object' && !Array.isArray(patch[key])) {
|
|
5793
|
+
// Recursively apply patch for nested objects
|
|
5794
|
+
result[key] = DeepPartialPatcher.patchOriginal(result[key] ?? new Object(patch[key]), patch[key]);
|
|
5775
5795
|
}
|
|
5776
5796
|
else {
|
|
5777
|
-
|
|
5797
|
+
// Apply patch for non-object fields
|
|
5798
|
+
result[key] = patch[key];
|
|
5778
5799
|
}
|
|
5779
|
-
created = true;
|
|
5780
|
-
}
|
|
5781
|
-
else {
|
|
5782
|
-
newData[existingIndex] = entity;
|
|
5783
5800
|
}
|
|
5784
|
-
|
|
5785
|
-
this.publishChangeEvent(created ? DataSourceChangeEvent.created([entity]) : DataSourceChangeEvent.modified([entity]));
|
|
5801
|
+
return result;
|
|
5786
5802
|
}
|
|
5787
5803
|
/***************************************************************************
|
|
5788
5804
|
* *
|
|
5789
5805
|
* Private methods *
|
|
5790
5806
|
* *
|
|
5791
5807
|
**************************************************************************/
|
|
5792
|
-
|
|
5793
|
-
return
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
this.data$.next(newData);
|
|
5797
|
-
}
|
|
5798
|
-
static guessIdProperty(localData) {
|
|
5799
|
-
const log = LoggerFactory.getLogger('LocalListDataSource');
|
|
5800
|
-
if (localData && localData.length > 0) {
|
|
5801
|
-
const sample = localData[0];
|
|
5802
|
-
if (typeof sample === 'object') {
|
|
5803
|
-
if (Object.prototype.hasOwnProperty.call(sample, 'id')) {
|
|
5804
|
-
log.warn('DataSource without defined id-property => autodetected property id-property as "id"');
|
|
5805
|
-
return 'id'; // Use id
|
|
5806
|
-
}
|
|
5807
|
-
else {
|
|
5808
|
-
log.warn('Local DataSource created without defined id-property and objects. Using object equality!');
|
|
5809
|
-
}
|
|
5808
|
+
static mapReducePatch(obj, pobj) {
|
|
5809
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
5810
|
+
if (!(key in pobj)) {
|
|
5811
|
+
return { ...acc, [key]: value }; // If key doesn't exist in pobj, copy the value from obj
|
|
5810
5812
|
}
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
}
|
|
5813
|
+
if (typeof pobj[key] === 'object' && pobj[key] !== null && !Array.isArray(pobj[key])) {
|
|
5814
|
+
// If it's an object (but not an array), recursively apply rmap
|
|
5815
|
+
return { ...acc, [key]: this.mapReducePatch(obj[key], pobj[key]) };
|
|
5816
|
+
}
|
|
5817
|
+
else {
|
|
5818
|
+
// Otherwise, copy the value from pobj
|
|
5819
|
+
return { ...acc, [key]: pobj[key] };
|
|
5820
|
+
}
|
|
5821
|
+
}, {});
|
|
5821
5822
|
}
|
|
5822
5823
|
}
|
|
5823
5824
|
|
|
5824
|
-
class
|
|
5825
|
-
/***************************************************************************
|
|
5826
|
-
* *
|
|
5827
|
-
* Static Builder *
|
|
5828
|
-
* *
|
|
5829
|
-
**************************************************************************/
|
|
5830
|
-
/**
|
|
5831
|
-
* Creates an empty local list data-source.
|
|
5832
|
-
* You can set / modify data by using the data property.
|
|
5833
|
-
* @param idProperty
|
|
5834
|
-
* @param localSort
|
|
5835
|
-
* @param localFilter
|
|
5836
|
-
*/
|
|
5837
|
-
static empty(idProperty, localSort, localFilter) {
|
|
5838
|
-
return LocalPagedDataSource.of(LocalListDataSource.empty(idProperty, localSort, localFilter));
|
|
5839
|
-
}
|
|
5840
|
-
static of(listDataSource) {
|
|
5841
|
-
return new LocalPagedDataSource(listDataSource);
|
|
5842
|
-
}
|
|
5825
|
+
class DataContextSourceEventBinding extends DataContextLifeCycleBinding {
|
|
5843
5826
|
/***************************************************************************
|
|
5844
5827
|
* *
|
|
5845
5828
|
* Constructor *
|
|
5846
5829
|
* *
|
|
5847
5830
|
**************************************************************************/
|
|
5848
|
-
constructor(
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
this.
|
|
5831
|
+
constructor(dataContext, dataApi, reloadOnChanges) {
|
|
5832
|
+
super(dataContext);
|
|
5833
|
+
this.dataApi = dataApi;
|
|
5834
|
+
this.reloadOnChanges = reloadOnChanges;
|
|
5835
|
+
this.subscribe();
|
|
5853
5836
|
}
|
|
5854
5837
|
/***************************************************************************
|
|
5855
5838
|
* *
|
|
5856
|
-
*
|
|
5839
|
+
* Internal Impl *
|
|
5857
5840
|
* *
|
|
5858
5841
|
**************************************************************************/
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
}
|
|
5862
|
-
findById(id) {
|
|
5863
|
-
return this.localListFetcher.findById(id);
|
|
5864
|
-
}
|
|
5865
|
-
findByIds(ids) {
|
|
5866
|
-
return this.localListFetcher.findByIds(ids);
|
|
5867
|
-
}
|
|
5868
|
-
findAllPaged(pageable, filters) {
|
|
5869
|
-
return this.localListFetcher
|
|
5870
|
-
.findAllFiltered(filters, pageable.sorts)
|
|
5871
|
-
.pipe(map((data) => this.pageSlice(pageable, data)));
|
|
5872
|
-
}
|
|
5873
|
-
getId(entity) {
|
|
5874
|
-
return this.localListFetcher.getId(entity);
|
|
5842
|
+
subscribe() {
|
|
5843
|
+
this._subscription = this.dataApi.dataChanged.subscribe((event) => this.handleDataChangeEvent(event));
|
|
5875
5844
|
}
|
|
5876
5845
|
/***************************************************************************
|
|
5877
5846
|
* *
|
|
5878
5847
|
* Private methods *
|
|
5879
5848
|
* *
|
|
5880
5849
|
**************************************************************************/
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
const end = start + pageable.size;
|
|
5886
|
-
const slice = data.slice(start, end);
|
|
5887
|
-
page = Page.fromPage(slice, data.length, pageable);
|
|
5850
|
+
handleDataChangeEvent(event) {
|
|
5851
|
+
if (this.reloadOnChanges && this.isReloadDesirable(event)) {
|
|
5852
|
+
this._dataContext.reload('DataSourceChangeEvent');
|
|
5853
|
+
return;
|
|
5888
5854
|
}
|
|
5889
|
-
|
|
5890
|
-
|
|
5855
|
+
// We might also be able to perform deletions locally, and avoid reload data
|
|
5856
|
+
if (event.modified) {
|
|
5857
|
+
this.updateExisting(event.modified);
|
|
5891
5858
|
}
|
|
5892
|
-
|
|
5859
|
+
if (event.patches) {
|
|
5860
|
+
this.updateExisting(this.buildPatchUpdates(event.patches));
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
updateExisting(entities) {
|
|
5864
|
+
if (entities.length > 0) {
|
|
5865
|
+
this._dataContext.update(entities);
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
// TODO: check logic and add correct return type (should it coerce to boolean?).
|
|
5869
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
5870
|
+
isReloadDesirable(event) {
|
|
5871
|
+
return event.unknownChanges || event.created || event.deletedIds;
|
|
5872
|
+
}
|
|
5873
|
+
buildPatchUpdates(patches) {
|
|
5874
|
+
return patches
|
|
5875
|
+
.map((patch) => {
|
|
5876
|
+
const existing = this._dataContext.findById(patch.entityId);
|
|
5877
|
+
if (existing) {
|
|
5878
|
+
return DeepPartialPatcher.patchShallowCopy(existing, patch.patch);
|
|
5879
|
+
}
|
|
5880
|
+
else {
|
|
5881
|
+
return null;
|
|
5882
|
+
}
|
|
5883
|
+
})
|
|
5884
|
+
.filter((update) => !!update);
|
|
5893
5885
|
}
|
|
5894
5886
|
}
|
|
5895
5887
|
|
|
@@ -23630,6 +23622,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
23630
23622
|
args: [ElderSelectOptionComponent]
|
|
23631
23623
|
}] } });
|
|
23632
23624
|
|
|
23625
|
+
const DEFAULT_DEBOUNCE_TIME = 150;
|
|
23633
23626
|
class ElderAutocompleteDirective {
|
|
23634
23627
|
/***************************************************************************
|
|
23635
23628
|
* *
|
|
@@ -23644,11 +23637,10 @@ class ElderAutocompleteDirective {
|
|
|
23644
23637
|
* *
|
|
23645
23638
|
**************************************************************************/
|
|
23646
23639
|
this.logger = LoggerFactory.getLogger(this.constructor.name);
|
|
23647
|
-
this.ignoreKeys = ['ArrowRight', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'Tab'];
|
|
23648
|
-
this.inputKeyup$ = new Subject();
|
|
23649
23640
|
this.queryFilter = 'query';
|
|
23650
23641
|
this.filters = [];
|
|
23651
23642
|
this.sorts = [];
|
|
23643
|
+
this.userTextInputChanged$ = new Subject();
|
|
23652
23644
|
this.destroyMatAutoBinding$ = new Subject();
|
|
23653
23645
|
this.destroy$ = new Subject();
|
|
23654
23646
|
}
|
|
@@ -23657,11 +23649,9 @@ class ElderAutocompleteDirective {
|
|
|
23657
23649
|
* Host Listener *
|
|
23658
23650
|
* *
|
|
23659
23651
|
**************************************************************************/
|
|
23660
|
-
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
}
|
|
23664
|
-
this.inputKeyup$.next(event);
|
|
23652
|
+
onUserTextInput(event) {
|
|
23653
|
+
const value = event?.target?.value ?? '';
|
|
23654
|
+
this.userTextInputChanged$.next(value);
|
|
23665
23655
|
}
|
|
23666
23656
|
/***************************************************************************
|
|
23667
23657
|
* *
|
|
@@ -23669,7 +23659,7 @@ class ElderAutocompleteDirective {
|
|
|
23669
23659
|
* *
|
|
23670
23660
|
**************************************************************************/
|
|
23671
23661
|
ngOnInit() {
|
|
23672
|
-
merge(this.
|
|
23662
|
+
merge(this.userTextInputChanged$.pipe(filter(() => this._elderAutocomplete.enabled), debounce(() => timer(this.getDebounceTime())), distinctUntilChanged()), this.autocomplete.triggerReload$)
|
|
23673
23663
|
.pipe(takeUntil(this.destroy$), filter((value) => !value || typeof value === 'string' || typeof value === 'number'))
|
|
23674
23664
|
.subscribe((value) => this.updateSuggestions(value));
|
|
23675
23665
|
}
|
|
@@ -23723,10 +23713,10 @@ class ElderAutocompleteDirective {
|
|
|
23723
23713
|
if (isLocalDataSource(this.dataContext?.dataSource)) {
|
|
23724
23714
|
return 0;
|
|
23725
23715
|
}
|
|
23726
|
-
return
|
|
23716
|
+
return DEFAULT_DEBOUNCE_TIME;
|
|
23727
23717
|
}
|
|
23728
23718
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ElderAutocompleteDirective, deps: [{ token: i1$8.MatAutocompleteTrigger }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
23729
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: ElderAutocompleteDirective, isStandalone: true, selector: "[elderAutocomplete]", inputs: { queryFilter: "queryFilter", filters: "filters", sorts: "sorts", autocomplete: ["elderAutocomplete", "autocomplete"] }, host: { listeners: { "
|
|
23719
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: ElderAutocompleteDirective, isStandalone: true, selector: "[elderAutocomplete]", inputs: { queryFilter: "queryFilter", filters: "filters", sorts: "sorts", autocomplete: ["elderAutocomplete", "autocomplete"] }, host: { listeners: { "input": "onUserTextInput($event)" } }, ngImport: i0 }); }
|
|
23730
23720
|
}
|
|
23731
23721
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ElderAutocompleteDirective, decorators: [{
|
|
23732
23722
|
type: Directive,
|
|
@@ -23739,9 +23729,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
23739
23729
|
type: Input
|
|
23740
23730
|
}], sorts: [{
|
|
23741
23731
|
type: Input
|
|
23742
|
-
}],
|
|
23732
|
+
}], onUserTextInput: [{
|
|
23743
23733
|
type: HostListener,
|
|
23744
|
-
args: ['
|
|
23734
|
+
args: ['input', ['$event']]
|
|
23745
23735
|
}], autocomplete: [{
|
|
23746
23736
|
type: Input,
|
|
23747
23737
|
args: ['elderAutocomplete']
|
|
@@ -23833,25 +23823,25 @@ class ElderSelectOnTabDirective {
|
|
|
23833
23823
|
selectActiveOption() {
|
|
23834
23824
|
// keyboard navigation has precedence over text input
|
|
23835
23825
|
if (this.panelNavigatedWithArrowKeys) {
|
|
23836
|
-
|
|
23837
|
-
if (activeOption) {
|
|
23838
|
-
const entity = activeOption.value;
|
|
23839
|
-
this.writeEntity(entity);
|
|
23840
|
-
}
|
|
23826
|
+
this.writeAutoCompleteActiveOption();
|
|
23841
23827
|
return;
|
|
23842
23828
|
}
|
|
23843
|
-
// text
|
|
23844
|
-
if (this.textTypedAndSettingRespected()) {
|
|
23845
|
-
|
|
23846
|
-
|
|
23847
|
-
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
|
|
23854
|
-
|
|
23829
|
+
// only select if text was typed and the setting is respected
|
|
23830
|
+
if (!this.textTypedAndSettingRespected()) {
|
|
23831
|
+
return;
|
|
23832
|
+
}
|
|
23833
|
+
const matchedOption = this.findMatchingOptionByInputText();
|
|
23834
|
+
if (matchedOption) {
|
|
23835
|
+
this.writeEntity(matchedOption.value);
|
|
23836
|
+
return;
|
|
23837
|
+
}
|
|
23838
|
+
this.writeAutoCompleteActiveOption();
|
|
23839
|
+
}
|
|
23840
|
+
writeAutoCompleteActiveOption() {
|
|
23841
|
+
const activeOption = this.autoTrigger.activeOption;
|
|
23842
|
+
if (activeOption) {
|
|
23843
|
+
const entity = activeOption.value;
|
|
23844
|
+
this.writeEntity(entity);
|
|
23855
23845
|
}
|
|
23856
23846
|
}
|
|
23857
23847
|
writeEntity(entity) {
|
|
@@ -23881,20 +23871,24 @@ class ElderSelectOnTabDirective {
|
|
|
23881
23871
|
findMatchingOptionByInputText() {
|
|
23882
23872
|
const inputText = this.elderSelect.inputRef?.nativeElement?.value;
|
|
23883
23873
|
if (!inputText) {
|
|
23884
|
-
return
|
|
23874
|
+
return undefined;
|
|
23885
23875
|
}
|
|
23886
23876
|
const normalizedInput = inputText.trim().toLowerCase();
|
|
23887
23877
|
if (!normalizedInput) {
|
|
23888
|
-
return
|
|
23878
|
+
return undefined;
|
|
23889
23879
|
}
|
|
23890
23880
|
const options = this.autoTrigger.autocomplete?.options?.toArray() ?? [];
|
|
23891
|
-
return (options
|
|
23881
|
+
return this.findFirstOptionMatchingNormalizedInput(options, normalizedInput);
|
|
23882
|
+
}
|
|
23883
|
+
findFirstOptionMatchingNormalizedInput(options, normalizedInput) {
|
|
23884
|
+
return options.find((option) => {
|
|
23892
23885
|
if (option.disabled) {
|
|
23893
23886
|
return false;
|
|
23894
23887
|
}
|
|
23895
|
-
const optionText =
|
|
23896
|
-
|
|
23897
|
-
|
|
23888
|
+
const optionText = option.viewValue ?? '';
|
|
23889
|
+
const normalizedOptionText = optionText.trim().toLowerCase();
|
|
23890
|
+
return normalizedOptionText.startsWith(normalizedInput);
|
|
23891
|
+
});
|
|
23898
23892
|
}
|
|
23899
23893
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ElderSelectOnTabDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
23900
23894
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: ElderSelectOnTabDirective, isStandalone: true, selector: "[elderSelectOnTab]", host: { listeners: { "keydown.arrowup": "handleVerticalArrowKeyPress()", "keydown.arrowdown": "handleVerticalArrowKeyPress()", "input": "handleInputTyping()", "keydown.tab": "handleTabKeyPress()" } }, ngImport: i0 }); }
|
|
@@ -24073,19 +24067,13 @@ class ElderSuggestionPanelComponent {
|
|
|
24073
24067
|
if (!activeEntity) {
|
|
24074
24068
|
return false;
|
|
24075
24069
|
}
|
|
24076
|
-
|
|
24077
|
-
|
|
24078
|
-
|
|
24079
|
-
|
|
24080
|
-
|
|
24070
|
+
const areIdsEqual = this.areIdsEqual(activeEntity, option);
|
|
24071
|
+
if (areIdsEqual !== undefined) {
|
|
24072
|
+
return areIdsEqual;
|
|
24073
|
+
}
|
|
24074
|
+
else {
|
|
24075
|
+
return this.areLabelsEqual(activeEntity, option);
|
|
24081
24076
|
}
|
|
24082
|
-
// Fallback for non-id data (e.g. translated display-only options).
|
|
24083
|
-
const resolver = this.displayPropertyResolver$.getValue();
|
|
24084
|
-
const activeLabel = resolver
|
|
24085
|
-
? resolver(activeEntity)
|
|
24086
|
-
: this.propertyStringValue(activeEntity, null);
|
|
24087
|
-
const optionLabel = resolver ? resolver(option) : this.propertyStringValue(option, null);
|
|
24088
|
-
return activeLabel === optionLabel;
|
|
24089
24077
|
}
|
|
24090
24078
|
toOptionValue(option) {
|
|
24091
24079
|
if (this.optionValueConverterFn) {
|
|
@@ -24121,6 +24109,10 @@ class ElderSuggestionPanelComponent {
|
|
|
24121
24109
|
calculatePageSize() {
|
|
24122
24110
|
return this.PAGE_SIZE + this.hiddenOptionsCount$.getValue();
|
|
24123
24111
|
}
|
|
24112
|
+
/**
|
|
24113
|
+
* Try to extract the id from the value (to determine active entity)
|
|
24114
|
+
* with graceful fallback to undefined.
|
|
24115
|
+
*/
|
|
24124
24116
|
safeId(value) {
|
|
24125
24117
|
if (value === null || value === undefined) {
|
|
24126
24118
|
return undefined;
|
|
@@ -24129,11 +24121,27 @@ class ElderSuggestionPanelComponent {
|
|
|
24129
24121
|
return this.getId(value);
|
|
24130
24122
|
}
|
|
24131
24123
|
catch {
|
|
24124
|
+
this.logger.debug('Failed to extract id from value', value);
|
|
24125
|
+
return undefined;
|
|
24126
|
+
}
|
|
24127
|
+
}
|
|
24128
|
+
areIdsEqual(activeEntity, option) {
|
|
24129
|
+
const activeId = this.safeId(activeEntity);
|
|
24130
|
+
const optionId = this.safeId(option);
|
|
24131
|
+
if ([activeId, optionId].includes(undefined)) {
|
|
24132
24132
|
return undefined;
|
|
24133
24133
|
}
|
|
24134
|
+
return activeId === optionId;
|
|
24135
|
+
}
|
|
24136
|
+
areLabelsEqual(activeEntity, option) {
|
|
24137
|
+
const resolverFn = this.displayPropertyResolver$.getValue();
|
|
24138
|
+
if (resolverFn) {
|
|
24139
|
+
return resolverFn(activeEntity) === resolverFn(option);
|
|
24140
|
+
}
|
|
24141
|
+
return this.propertyStringValue(activeEntity, null) === this.propertyStringValue(option, null);
|
|
24134
24142
|
}
|
|
24135
24143
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ElderSuggestionPanelComponent, deps: [{ token: i0.NgZone }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
24136
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ElderSuggestionPanelComponent, isStandalone: true, selector: "elder-suggestion-panel", inputs: { isOptionDisabledFn: { classPropertyName: "isOptionDisabledFn", publicName: "isOptionDisabledFn", isSignal: false, isRequired: false, transformFunction: null }, isOptionHiddenFn: { classPropertyName: "isOptionHiddenFn", publicName: "isOptionHiddenFn", isSignal: false, isRequired: false, transformFunction: null }, optionValueConverterFn: { classPropertyName: "optionValueConverterFn", publicName: "optionValueConverterFn", isSignal: false, isRequired: false, transformFunction: null }, activeEntity: { classPropertyName: "activeEntity", publicName: "activeEntity", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: false, isRequired: false, transformFunction: null }, valueTemplate: { classPropertyName: "valueTemplate", publicName: "valueTemplate", isSignal: false, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: false, isRequired: false, transformFunction: null }, displayPropertyResolver: { classPropertyName: "displayPropertyResolver", publicName: "displayPropertyResolver", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { optionSelected: "optionSelected" }, queries: [{ propertyName: "valueTemplateQuery", first: true, predicate: ElderSelectValueDirective, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "matAutocomplete", first: true, predicate: ["auto"], descendants: true }], exportAs: ["elderSuggestionPanel"], ngImport: i0, template: "<mat-autocomplete\n #auto=\"matAutocomplete\"\n panelWidth=\"auto\"\n [autoActiveFirstOption]=\"true\"\n (opened)=\"onAutocompleteOpened($event)\"\n (optionSelected)=\"onOptionSelected($event)\"\n elderInfiniteScroll\n elderElderInfiniteAutocomplete\n (closeToEnd)=\"onAutoCompleteCloseToEnd()\"\n>\n @if (dataContext$ | async; as dc) {\n @if (dc.isClosed) {\n <mat-option disabled>\n <div class=\"layout-row place-start-center gap-sm\">\n <mat-icon color=\"warn\">warning</mat-icon>\n <span class=\"mat-caption\">DataContext Closed!</span>\n </div>\n </mat-option>\n }\n\n @if (availableSuggestions$ | async; as suggestions) {\n @if (suggestions.length === 0) {\n <mat-option disabled>No Data.</mat-option>\n }\n\n @for (suggestion of suggestions; track getIdAsString(suggestion)) {\n @if (isOptionVisible(suggestion)) {\n <mat-option\n [value]=\"toOptionValue(suggestion)\"\n [id]=\"getIdAsString(suggestion)\"\n [disabled]=\"!isOptionAvailable(suggestion)\"\n [class.active-option]=\"isOptionActive(suggestion)\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valueTemplate || simpleValueTemplate;\n context: { $implicit: suggestion }\n \"\n >\n </ng-container>\n <!--\n <span class=\"mat-caption\">value: {{toOptionValue(suggestion)}}</span>\n -->\n </mat-option>\n }\n }\n }\n } @else {\n <mat-option disabled>\n <span class=\"mat-caption\">\n No DataContext!\n @if (dataSource$ | async; as ds) {\n (DataSource: {{ ds ? 'available' : 'missing' }})\n {{ enabled ? 'Autocomplete Enabled' : 'Autocomplete DISABLED' }}\n }\n </span>\n </mat-option>\n }\n\n @if (dataState$ | async; as state) {\n @if (!state.idle || state.loading) {\n <div style=\"position: relative\">\n <div style=\"position: absolute; right: 0; bottom: -8px; left: 0\">\n <mat-progress-bar\n [value]=\"100\"\n [mode]=\"state.loading ? 'query' : 'determinate'\"\n [color]=\"state.error ? 'warn' : 'primary'\"\n ></mat-progress-bar>\n </div>\n </div>\n }\n }\n</mat-autocomplete>\n\n<ng-template #simpleValueTemplate let-value>\n @if (displayPropertyResolver$ | async; as propertyResolver) {\n <span class=\"noselect\">{{ propertyResolver(value) }}</span>\n }\n</ng-template>\n", styles: [".active-option.active-option{font-weight:700}.active-option.active-option:before{
|
|
24144
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ElderSuggestionPanelComponent, isStandalone: true, selector: "elder-suggestion-panel", inputs: { isOptionDisabledFn: { classPropertyName: "isOptionDisabledFn", publicName: "isOptionDisabledFn", isSignal: false, isRequired: false, transformFunction: null }, isOptionHiddenFn: { classPropertyName: "isOptionHiddenFn", publicName: "isOptionHiddenFn", isSignal: false, isRequired: false, transformFunction: null }, optionValueConverterFn: { classPropertyName: "optionValueConverterFn", publicName: "optionValueConverterFn", isSignal: false, isRequired: false, transformFunction: null }, activeEntity: { classPropertyName: "activeEntity", publicName: "activeEntity", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: false, isRequired: false, transformFunction: null }, valueTemplate: { classPropertyName: "valueTemplate", publicName: "valueTemplate", isSignal: false, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: false, isRequired: false, transformFunction: null }, displayPropertyResolver: { classPropertyName: "displayPropertyResolver", publicName: "displayPropertyResolver", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { optionSelected: "optionSelected" }, queries: [{ propertyName: "valueTemplateQuery", first: true, predicate: ElderSelectValueDirective, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "matAutocomplete", first: true, predicate: ["auto"], descendants: true }], exportAs: ["elderSuggestionPanel"], ngImport: i0, template: "<mat-autocomplete\n #auto=\"matAutocomplete\"\n panelWidth=\"auto\"\n [autoActiveFirstOption]=\"true\"\n (opened)=\"onAutocompleteOpened($event)\"\n (optionSelected)=\"onOptionSelected($event)\"\n elderInfiniteScroll\n elderElderInfiniteAutocomplete\n (closeToEnd)=\"onAutoCompleteCloseToEnd()\"\n>\n @if (dataContext$ | async; as dc) {\n @if (dc.isClosed) {\n <mat-option disabled>\n <div class=\"layout-row place-start-center gap-sm\">\n <mat-icon color=\"warn\">warning</mat-icon>\n <span class=\"mat-caption\">DataContext Closed!</span>\n </div>\n </mat-option>\n }\n\n @if (availableSuggestions$ | async; as suggestions) {\n @if (suggestions.length === 0) {\n <mat-option disabled>No Data.</mat-option>\n }\n\n @for (suggestion of suggestions; track getIdAsString(suggestion)) {\n @if (isOptionVisible(suggestion)) {\n <mat-option\n [value]=\"toOptionValue(suggestion)\"\n [id]=\"getIdAsString(suggestion)\"\n [disabled]=\"!isOptionAvailable(suggestion)\"\n [class.active-option]=\"isOptionActive(suggestion)\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valueTemplate || simpleValueTemplate;\n context: { $implicit: suggestion }\n \"\n >\n </ng-container>\n <!--\n <span class=\"mat-caption\">value: {{toOptionValue(suggestion)}}</span>\n -->\n </mat-option>\n }\n }\n }\n } @else {\n <mat-option disabled>\n <span class=\"mat-caption\">\n No DataContext!\n @if (dataSource$ | async; as ds) {\n (DataSource: {{ ds ? 'available' : 'missing' }})\n {{ enabled ? 'Autocomplete Enabled' : 'Autocomplete DISABLED' }}\n }\n </span>\n </mat-option>\n }\n\n @if (dataState$ | async; as state) {\n @if (!state.idle || state.loading) {\n <div style=\"position: relative\">\n <div style=\"position: absolute; right: 0; bottom: -8px; left: 0\">\n <mat-progress-bar\n [value]=\"100\"\n [mode]=\"state.loading ? 'query' : 'determinate'\"\n [color]=\"state.error ? 'warn' : 'primary'\"\n ></mat-progress-bar>\n </div>\n </div>\n }\n }\n</mat-autocomplete>\n\n<ng-template #simpleValueTemplate let-value>\n @if (displayPropertyResolver$ | async; as propertyResolver) {\n <span class=\"noselect\">{{ propertyResolver(value) }}</span>\n }\n</ng-template>\n", styles: [".active-option.active-option{font-weight:700}.active-option.active-option:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:4px;background-color:var(--md-sys-color-primary)}\n"], dependencies: [{ kind: "component", type: MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: ElderInfiniteAutocompleteDirective, selector: "mat-autocomplete[elderElderInfiniteAutocomplete]" }, { kind: "directive", type: ElderInfiniteScrollDirective, selector: "[elderInfiniteScroll]", inputs: ["listenToHost", "eventThrottle", "offsetFactor", "ignoreScrollEvent", "containerId", "scrollContainer"], outputs: ["closeToEnd", "scrolling"] }, { kind: "component", type: MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
24137
24145
|
}
|
|
24138
24146
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ElderSuggestionPanelComponent, decorators: [{
|
|
24139
24147
|
type: Component,
|
|
@@ -24146,7 +24154,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
24146
24154
|
NgTemplateOutlet,
|
|
24147
24155
|
MatProgressBar,
|
|
24148
24156
|
AsyncPipe,
|
|
24149
|
-
], template: "<mat-autocomplete\n #auto=\"matAutocomplete\"\n panelWidth=\"auto\"\n [autoActiveFirstOption]=\"true\"\n (opened)=\"onAutocompleteOpened($event)\"\n (optionSelected)=\"onOptionSelected($event)\"\n elderInfiniteScroll\n elderElderInfiniteAutocomplete\n (closeToEnd)=\"onAutoCompleteCloseToEnd()\"\n>\n @if (dataContext$ | async; as dc) {\n @if (dc.isClosed) {\n <mat-option disabled>\n <div class=\"layout-row place-start-center gap-sm\">\n <mat-icon color=\"warn\">warning</mat-icon>\n <span class=\"mat-caption\">DataContext Closed!</span>\n </div>\n </mat-option>\n }\n\n @if (availableSuggestions$ | async; as suggestions) {\n @if (suggestions.length === 0) {\n <mat-option disabled>No Data.</mat-option>\n }\n\n @for (suggestion of suggestions; track getIdAsString(suggestion)) {\n @if (isOptionVisible(suggestion)) {\n <mat-option\n [value]=\"toOptionValue(suggestion)\"\n [id]=\"getIdAsString(suggestion)\"\n [disabled]=\"!isOptionAvailable(suggestion)\"\n [class.active-option]=\"isOptionActive(suggestion)\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valueTemplate || simpleValueTemplate;\n context: { $implicit: suggestion }\n \"\n >\n </ng-container>\n <!--\n <span class=\"mat-caption\">value: {{toOptionValue(suggestion)}}</span>\n -->\n </mat-option>\n }\n }\n }\n } @else {\n <mat-option disabled>\n <span class=\"mat-caption\">\n No DataContext!\n @if (dataSource$ | async; as ds) {\n (DataSource: {{ ds ? 'available' : 'missing' }})\n {{ enabled ? 'Autocomplete Enabled' : 'Autocomplete DISABLED' }}\n }\n </span>\n </mat-option>\n }\n\n @if (dataState$ | async; as state) {\n @if (!state.idle || state.loading) {\n <div style=\"position: relative\">\n <div style=\"position: absolute; right: 0; bottom: -8px; left: 0\">\n <mat-progress-bar\n [value]=\"100\"\n [mode]=\"state.loading ? 'query' : 'determinate'\"\n [color]=\"state.error ? 'warn' : 'primary'\"\n ></mat-progress-bar>\n </div>\n </div>\n }\n }\n</mat-autocomplete>\n\n<ng-template #simpleValueTemplate let-value>\n @if (displayPropertyResolver$ | async; as propertyResolver) {\n <span class=\"noselect\">{{ propertyResolver(value) }}</span>\n }\n</ng-template>\n", styles: [".active-option.active-option{font-weight:700}.active-option.active-option:before{
|
|
24157
|
+
], template: "<mat-autocomplete\n #auto=\"matAutocomplete\"\n panelWidth=\"auto\"\n [autoActiveFirstOption]=\"true\"\n (opened)=\"onAutocompleteOpened($event)\"\n (optionSelected)=\"onOptionSelected($event)\"\n elderInfiniteScroll\n elderElderInfiniteAutocomplete\n (closeToEnd)=\"onAutoCompleteCloseToEnd()\"\n>\n @if (dataContext$ | async; as dc) {\n @if (dc.isClosed) {\n <mat-option disabled>\n <div class=\"layout-row place-start-center gap-sm\">\n <mat-icon color=\"warn\">warning</mat-icon>\n <span class=\"mat-caption\">DataContext Closed!</span>\n </div>\n </mat-option>\n }\n\n @if (availableSuggestions$ | async; as suggestions) {\n @if (suggestions.length === 0) {\n <mat-option disabled>No Data.</mat-option>\n }\n\n @for (suggestion of suggestions; track getIdAsString(suggestion)) {\n @if (isOptionVisible(suggestion)) {\n <mat-option\n [value]=\"toOptionValue(suggestion)\"\n [id]=\"getIdAsString(suggestion)\"\n [disabled]=\"!isOptionAvailable(suggestion)\"\n [class.active-option]=\"isOptionActive(suggestion)\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valueTemplate || simpleValueTemplate;\n context: { $implicit: suggestion }\n \"\n >\n </ng-container>\n <!--\n <span class=\"mat-caption\">value: {{toOptionValue(suggestion)}}</span>\n -->\n </mat-option>\n }\n }\n }\n } @else {\n <mat-option disabled>\n <span class=\"mat-caption\">\n No DataContext!\n @if (dataSource$ | async; as ds) {\n (DataSource: {{ ds ? 'available' : 'missing' }})\n {{ enabled ? 'Autocomplete Enabled' : 'Autocomplete DISABLED' }}\n }\n </span>\n </mat-option>\n }\n\n @if (dataState$ | async; as state) {\n @if (!state.idle || state.loading) {\n <div style=\"position: relative\">\n <div style=\"position: absolute; right: 0; bottom: -8px; left: 0\">\n <mat-progress-bar\n [value]=\"100\"\n [mode]=\"state.loading ? 'query' : 'determinate'\"\n [color]=\"state.error ? 'warn' : 'primary'\"\n ></mat-progress-bar>\n </div>\n </div>\n }\n }\n</mat-autocomplete>\n\n<ng-template #simpleValueTemplate let-value>\n @if (displayPropertyResolver$ | async; as propertyResolver) {\n <span class=\"noselect\">{{ propertyResolver(value) }}</span>\n }\n</ng-template>\n", styles: [".active-option.active-option{font-weight:700}.active-option.active-option:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:4px;background-color:var(--md-sys-color-primary)}\n"] }]
|
|
24150
24158
|
}], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.DestroyRef }], propDecorators: { valueTemplateQuery: [{
|
|
24151
24159
|
type: ContentChild,
|
|
24152
24160
|
args: [ElderSelectValueDirective, { read: TemplateRef, static: true }]
|
|
@@ -24409,7 +24417,9 @@ class ElderSelectComponent extends ElderSelectBase {
|
|
|
24409
24417
|
if (this.isOptionDisabledFn) {
|
|
24410
24418
|
return this.isOptionDisabledFn(option);
|
|
24411
24419
|
}
|
|
24412
|
-
|
|
24420
|
+
else {
|
|
24421
|
+
return false;
|
|
24422
|
+
}
|
|
24413
24423
|
};
|
|
24414
24424
|
}
|
|
24415
24425
|
get isOptionHiddenInternalFn() {
|
|
@@ -24460,9 +24470,11 @@ class ElderSelectComponent extends ElderSelectBase {
|
|
|
24460
24470
|
}
|
|
24461
24471
|
const selectedEntity = optionSelected.entity;
|
|
24462
24472
|
if (this.isEntitySelected(selectedEntity)) {
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
24473
|
+
/*
|
|
24474
|
+
* Force signal emission when re-selecting the same entity.
|
|
24475
|
+
* Signals skip emission when the value hasn't changed (by reference),
|
|
24476
|
+
* therefore we need to null and then restore the value to trigger subscribers.
|
|
24477
|
+
*/
|
|
24466
24478
|
this.writeValueInternal(null);
|
|
24467
24479
|
queueMicrotask(() => {
|
|
24468
24480
|
this.updateValueByEntity(selectedEntity);
|
|
@@ -26408,7 +26420,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
26408
26420
|
}], propDecorators: { stateColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateColor", required: false }] }], levelColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelColor", required: false }] }], namedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "namedColor", required: false }] }], themeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], chipSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipSize", required: false }] }] } });
|
|
26409
26421
|
|
|
26410
26422
|
class ChipModel {
|
|
26411
|
-
constructor(id, value, displayText, colorSpec, removable, avatarSpec, trailingSpec) {
|
|
26423
|
+
constructor(id, value, displayText, colorSpec, removable, avatarSpec, trailingSpec, indicatorSpec) {
|
|
26412
26424
|
this.id = id;
|
|
26413
26425
|
this.value = value;
|
|
26414
26426
|
this.displayText = displayText;
|
|
@@ -26416,6 +26428,7 @@ class ChipModel {
|
|
|
26416
26428
|
this.removable = removable;
|
|
26417
26429
|
this.avatarSpec = avatarSpec;
|
|
26418
26430
|
this.trailingSpec = trailingSpec;
|
|
26431
|
+
this.indicatorSpec = indicatorSpec;
|
|
26419
26432
|
}
|
|
26420
26433
|
}
|
|
26421
26434
|
class ElderMultiSelectChipsComponent extends ElderMultiSelectBase {
|
|
@@ -26590,7 +26603,7 @@ class ElderMultiSelectChipsComponent extends ElderMultiSelectBase {
|
|
|
26590
26603
|
removable = true;
|
|
26591
26604
|
}
|
|
26592
26605
|
}
|
|
26593
|
-
return new ChipModel(this.getEntityId(e), e, dPR(e), chipSpec?.colorSpec, removable, chipSpec.avatarSpec, chipSpec.trailingSpec);
|
|
26606
|
+
return new ChipModel(this.getEntityId(e), e, dPR(e), chipSpec?.colorSpec, removable, chipSpec.avatarSpec, chipSpec.trailingSpec, chipSpec.indicatorSpec);
|
|
26594
26607
|
}
|
|
26595
26608
|
reduceDisplayedChips(models, count) {
|
|
26596
26609
|
const reducedChips = models.slice(0, count);
|
|
@@ -26611,7 +26624,7 @@ class ElderMultiSelectChipsComponent extends ElderMultiSelectBase {
|
|
|
26611
26624
|
provide: ELDER_SELECT_BASE,
|
|
26612
26625
|
useExisting: forwardRef(() => ElderMultiSelectChipsComponent),
|
|
26613
26626
|
},
|
|
26614
|
-
], queries: [{ propertyName: "_customChipInput", first: true, predicate: MatFormFieldControl, descendants: true }, { propertyName: "chipTemplateQuery", first: true, predicate: ElderSelectChipDirective, descendants: true, read: TemplateRef }, { propertyName: "chipAvatarTemplateQuery", first: true, predicate: ElderSelectChipAvatarDirective, descendants: true, read: TemplateRef }, { propertyName: "customInputTemplateQuery", first: true, predicate: ElderSelectCustomInputDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "_chipInput", first: true, predicate: ElderSelectComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"elder-flex-control\" [matTooltip]=\"state()?.error\">\n <mat-chip-set\n #chips\n [class.mat-mdc-chip-set-stacked]=\"stacked\"\n cdkDropList\n [cdkDropListOrientation]=\"stacked ? 'vertical' : 'horizontal'\"\n (cdkDropListDropped)=\"drop($event)\"\n [cdkDropListDisabled]=\"!allowSorting\"\n >\n @if (icon) {\n <div class=\"elder-input-prefix-icon-container flex-none\">\n <mat-icon\n disabled\n class=\"elder-prefix-icon elder-mdc-control-icon elder-icon-small noselect\"\n [class.loading]=\"state()?.loading\"\n >\n {{ icon }}\n </mat-icon>\n </div>\n }\n\n @for (chipModel of selectChips(); track chipModel.id) {\n <mat-chip-row\n elderChipLabel\n highlighted\n class=\"noselect clickable-chip\"\n [value]=\"resolveChipValue(chipModel.value)\"\n [color]=\"chipModel.colorSpec?.themeColor\"\n [levelColor]=\"chipModel.colorSpec?.levelColor\"\n [stateColor]=\"chipModel.colorSpec?.stateColor\"\n [namedColor]=\"chipModel.colorSpec?.namedColor\"\n [removable]=\"chipModel.removable\"\n (keydown)=\"onChipKeyDown($event, chipModel.value)\"\n (click)=\"onCurrentClicked(chipModel.value)\"\n cdkDrag\n [cdkDragData]=\"chipModel.value\"\n [cdkDragDisabled]=\"!allowSorting\"\n >\n @if (templates()?.avatar && !chipModel.avatarSpec?.hide) {\n <mat-chip-avatar [class.chip-avatar-xl]=\"chipModel.avatarSpec?.large\">\n <ng-container *ngTemplateOutlet=\"templates().avatar; context: { $implicit: chipModel }\">\n </ng-container>\n </mat-chip-avatar>\n }\n\n <ng-container\n *ngTemplateOutlet=\"\n templates()?.chip || simpleChipTemplate;\n context: { $implicit: chipModel }\n \"\n >\n </ng-container>\n\n @if (chipModel.trailingSpec?.icon; as trailingIcon) {\n <mat-icon\n matChipTrailingIcon\n class=\"elder-trailing-icon\"\n [fontSet]=\"chipModel.trailingSpec?.iconFontSet\"\n >{{ trailingIcon }}</mat-icon\n >\n }\n\n @if (chipModel.removable) {\n <mat-icon matChipRemove (click)=\"onClickRemoveChip($event, chipModel.value)\">\n cancel\n </mat-icon>\n }\n </mat-chip-row>\n }\n\n <div class=\"layout-row place-start-center elder-chip-input\">\n <!-- [matChipInputFor]=\"chips\" -->\n <ng-container *ngTemplateOutlet=\"templates()?.input || selectInput\"> </ng-container>\n\n @if (selectionPopup) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"elder-control-icon-button elder-browse-icon\"\n [disabled]=\"isLocked\"\n (click)=\"openSelectionPopup($event)\"\n aria-label=\"Search\"\n elderStopEventPropagation\n tabIndex=\"-1\"\n >\n <mat-icon class=\"elder-mdc-control-icon\">search</mat-icon>\n </button>\n }\n </div>\n </mat-chip-set>\n</div>\n\n<ng-template #selectInput>\n <!-- mat-mdc-chip-input -->\n <elder-select\n autocomplete\n elderClearSelect\n class=\"elder-chip-input-select flex\"\n [data]=\"dataContextS()\"\n [disabled]=\"!!disabledS()\"\n [required]=\"!!requiredS()\"\n [readonly]=\"!!readonlyS()\"\n [placeholder]=\"!readonlyS() ? placeholderS() : undefined\"\n (entityUpdated)=\"appendEntity($event)\"\n [displayPropertyResolver]=\"displayPropertyResolverS()\"\n [valueTemplate]=\"valueTemplate\"\n [queryFilter]=\"queryFilter\"\n [filters]=\"filters\"\n [sorts]=\"sorts\"\n [isOptionDisabledFn]=\"isOptionDisabledInternalFn\"\n [isOptionHiddenFn]=\"isOptionHiddenInternalFn\"\n ></elder-select>\n</ng-template>\n\n<ng-template #simpleChipTemplate let-chipModel>\n <span\n class=\"elder-chip-text\"\n [matTooltip]=\"chipModel.displayText\"\n [matTooltipDisabled]=\"chipModel.displayText?.length < 20\"\n >{{ chipModel.displayText | elderTruncate: 20 }}\n </span>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "directive", type: ElderChipLabelDirective, selector: "[elderChipLabel]", inputs: ["stateColor", "levelColor", "namedColor", "color", "chipSize"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: MatChipAvatar, selector: "mat-chip-avatar, [matChipAvatar]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: MatChipTrailingIcon, selector: "mat-chip-trailing-icon, [matChipTrailingIcon]" }, { kind: "directive", type: MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: ElderStopEventPropagationDirective, selector: "[elderStopEventPropagation]" }, { kind: "component", type: ElderSelectComponent, selector: "elder-select", inputs: ["nullDisplay", "autocomplete", "allowNull", "entity", "entityId"], outputs: ["entityIdChange", "entityIdUpdated", "entityChange", "entityUpdated", "entity"] }, { kind: "directive", type: ElderClearSelectDirective, selector: "[elderClearSelect]" }, { kind: "pipe", type: ElderTruncatePipe, name: "elderTruncate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
26627
|
+
], queries: [{ propertyName: "_customChipInput", first: true, predicate: MatFormFieldControl, descendants: true }, { propertyName: "chipTemplateQuery", first: true, predicate: ElderSelectChipDirective, descendants: true, read: TemplateRef }, { propertyName: "chipAvatarTemplateQuery", first: true, predicate: ElderSelectChipAvatarDirective, descendants: true, read: TemplateRef }, { propertyName: "customInputTemplateQuery", first: true, predicate: ElderSelectCustomInputDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "_chipInput", first: true, predicate: ElderSelectComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"elder-flex-control\" [matTooltip]=\"state()?.error\">\n <mat-chip-set\n #chips\n [class.mat-mdc-chip-set-stacked]=\"stacked\"\n cdkDropList\n [cdkDropListOrientation]=\"stacked ? 'vertical' : 'horizontal'\"\n (cdkDropListDropped)=\"drop($event)\"\n [cdkDropListDisabled]=\"!allowSorting\"\n >\n @if (icon) {\n <div class=\"elder-input-prefix-icon-container flex-none\">\n <mat-icon\n disabled\n class=\"elder-prefix-icon elder-mdc-control-icon elder-icon-small noselect\"\n [class.loading]=\"state()?.loading\"\n >\n {{ icon }}\n </mat-icon>\n </div>\n }\n\n @for (chipModel of selectChips(); track chipModel.id) {\n <mat-chip-row\n elderChipLabel\n highlighted\n class=\"noselect clickable-chip\"\n [value]=\"resolveChipValue(chipModel.value)\"\n [color]=\"chipModel.colorSpec?.themeColor\"\n [levelColor]=\"chipModel.colorSpec?.levelColor\"\n [stateColor]=\"chipModel.colorSpec?.stateColor\"\n [namedColor]=\"chipModel.colorSpec?.namedColor\"\n [removable]=\"chipModel.removable\"\n (keydown)=\"onChipKeyDown($event, chipModel.value)\"\n (click)=\"onCurrentClicked(chipModel.value)\"\n cdkDrag\n [cdkDragData]=\"chipModel.value\"\n [cdkDragDisabled]=\"!allowSorting\"\n [matBadge]=\"chipModel.indicatorSpec?.content\"\n [matBadgeColor]=\"chipModel.indicatorSpec?.themeColor || 'warn'\"\n [matBadgeSize]=\"chipModel.indicatorSpec?.size || 'small'\"\n [matBadgePosition]=\"chipModel.indicatorSpec?.position || 'above after'\"\n [matBadgeHidden]=\"!chipModel.indicatorSpec?.content\"\n >\n @if (templates()?.avatar && !chipModel.avatarSpec?.hide) {\n <mat-chip-avatar [class.chip-avatar-xl]=\"chipModel.avatarSpec?.large\">\n <ng-container *ngTemplateOutlet=\"templates().avatar; context: { $implicit: chipModel }\">\n </ng-container>\n </mat-chip-avatar>\n }\n\n <ng-container\n *ngTemplateOutlet=\"\n templates()?.chip || simpleChipTemplate;\n context: { $implicit: chipModel }\n \"\n >\n </ng-container>\n\n @if (chipModel.trailingSpec?.icon; as trailingIcon) {\n <mat-icon\n matChipTrailingIcon\n class=\"elder-trailing-icon\"\n [fontSet]=\"chipModel.trailingSpec?.iconFontSet\"\n >{{ trailingIcon }}</mat-icon\n >\n }\n\n @if (chipModel.removable) {\n <mat-icon matChipRemove (click)=\"onClickRemoveChip($event, chipModel.value)\">\n cancel\n </mat-icon>\n }\n </mat-chip-row>\n }\n\n <div class=\"layout-row place-start-center elder-chip-input\">\n <!-- [matChipInputFor]=\"chips\" -->\n <ng-container *ngTemplateOutlet=\"templates()?.input || selectInput\"> </ng-container>\n\n @if (selectionPopup) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"elder-control-icon-button elder-browse-icon\"\n [disabled]=\"isLocked\"\n (click)=\"openSelectionPopup($event)\"\n aria-label=\"Search\"\n elderStopEventPropagation\n tabIndex=\"-1\"\n >\n <mat-icon class=\"elder-mdc-control-icon\">search</mat-icon>\n </button>\n }\n </div>\n </mat-chip-set>\n</div>\n\n<ng-template #selectInput>\n <!-- mat-mdc-chip-input -->\n <elder-select\n autocomplete\n elderClearSelect\n class=\"elder-chip-input-select flex\"\n [data]=\"dataContextS()\"\n [disabled]=\"!!disabledS()\"\n [required]=\"!!requiredS()\"\n [readonly]=\"!!readonlyS()\"\n [placeholder]=\"!readonlyS() ? placeholderS() : undefined\"\n (entityUpdated)=\"appendEntity($event)\"\n [displayPropertyResolver]=\"displayPropertyResolverS()\"\n [valueTemplate]=\"valueTemplate\"\n [queryFilter]=\"queryFilter\"\n [filters]=\"filters\"\n [sorts]=\"sorts\"\n [isOptionDisabledFn]=\"isOptionDisabledInternalFn\"\n [isOptionHiddenFn]=\"isOptionHiddenInternalFn\"\n ></elder-select>\n</ng-template>\n\n<ng-template #simpleChipTemplate let-chipModel>\n <span\n class=\"elder-chip-text\"\n [matTooltip]=\"chipModel.displayText\"\n [matTooltipDisabled]=\"chipModel.displayText?.length < 20\"\n >{{ chipModel.displayText | elderTruncate: 20 }}\n </span>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "directive", type: ElderChipLabelDirective, selector: "[elderChipLabel]", inputs: ["stateColor", "levelColor", "namedColor", "color", "chipSize"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: MatChipAvatar, selector: "mat-chip-avatar, [matChipAvatar]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: MatChipTrailingIcon, selector: "mat-chip-trailing-icon, [matChipTrailingIcon]" }, { kind: "directive", type: MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: ElderStopEventPropagationDirective, selector: "[elderStopEventPropagation]" }, { kind: "component", type: ElderSelectComponent, selector: "elder-select", inputs: ["nullDisplay", "autocomplete", "allowNull", "entity", "entityId"], outputs: ["entityIdChange", "entityIdUpdated", "entityChange", "entityUpdated", "entity"] }, { kind: "directive", type: ElderClearSelectDirective, selector: "[elderClearSelect]" }, { kind: "directive", type: MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "pipe", type: ElderTruncatePipe, name: "elderTruncate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
26615
26628
|
}
|
|
26616
26629
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ElderMultiSelectChipsComponent, decorators: [{
|
|
26617
26630
|
type: Component,
|
|
@@ -26638,7 +26651,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
26638
26651
|
ElderSelectComponent,
|
|
26639
26652
|
ElderClearSelectDirective,
|
|
26640
26653
|
ElderTruncatePipe,
|
|
26641
|
-
|
|
26654
|
+
MatBadge,
|
|
26655
|
+
], template: "<div class=\"elder-flex-control\" [matTooltip]=\"state()?.error\">\n <mat-chip-set\n #chips\n [class.mat-mdc-chip-set-stacked]=\"stacked\"\n cdkDropList\n [cdkDropListOrientation]=\"stacked ? 'vertical' : 'horizontal'\"\n (cdkDropListDropped)=\"drop($event)\"\n [cdkDropListDisabled]=\"!allowSorting\"\n >\n @if (icon) {\n <div class=\"elder-input-prefix-icon-container flex-none\">\n <mat-icon\n disabled\n class=\"elder-prefix-icon elder-mdc-control-icon elder-icon-small noselect\"\n [class.loading]=\"state()?.loading\"\n >\n {{ icon }}\n </mat-icon>\n </div>\n }\n\n @for (chipModel of selectChips(); track chipModel.id) {\n <mat-chip-row\n elderChipLabel\n highlighted\n class=\"noselect clickable-chip\"\n [value]=\"resolveChipValue(chipModel.value)\"\n [color]=\"chipModel.colorSpec?.themeColor\"\n [levelColor]=\"chipModel.colorSpec?.levelColor\"\n [stateColor]=\"chipModel.colorSpec?.stateColor\"\n [namedColor]=\"chipModel.colorSpec?.namedColor\"\n [removable]=\"chipModel.removable\"\n (keydown)=\"onChipKeyDown($event, chipModel.value)\"\n (click)=\"onCurrentClicked(chipModel.value)\"\n cdkDrag\n [cdkDragData]=\"chipModel.value\"\n [cdkDragDisabled]=\"!allowSorting\"\n [matBadge]=\"chipModel.indicatorSpec?.content\"\n [matBadgeColor]=\"chipModel.indicatorSpec?.themeColor || 'warn'\"\n [matBadgeSize]=\"chipModel.indicatorSpec?.size || 'small'\"\n [matBadgePosition]=\"chipModel.indicatorSpec?.position || 'above after'\"\n [matBadgeHidden]=\"!chipModel.indicatorSpec?.content\"\n >\n @if (templates()?.avatar && !chipModel.avatarSpec?.hide) {\n <mat-chip-avatar [class.chip-avatar-xl]=\"chipModel.avatarSpec?.large\">\n <ng-container *ngTemplateOutlet=\"templates().avatar; context: { $implicit: chipModel }\">\n </ng-container>\n </mat-chip-avatar>\n }\n\n <ng-container\n *ngTemplateOutlet=\"\n templates()?.chip || simpleChipTemplate;\n context: { $implicit: chipModel }\n \"\n >\n </ng-container>\n\n @if (chipModel.trailingSpec?.icon; as trailingIcon) {\n <mat-icon\n matChipTrailingIcon\n class=\"elder-trailing-icon\"\n [fontSet]=\"chipModel.trailingSpec?.iconFontSet\"\n >{{ trailingIcon }}</mat-icon\n >\n }\n\n @if (chipModel.removable) {\n <mat-icon matChipRemove (click)=\"onClickRemoveChip($event, chipModel.value)\">\n cancel\n </mat-icon>\n }\n </mat-chip-row>\n }\n\n <div class=\"layout-row place-start-center elder-chip-input\">\n <!-- [matChipInputFor]=\"chips\" -->\n <ng-container *ngTemplateOutlet=\"templates()?.input || selectInput\"> </ng-container>\n\n @if (selectionPopup) {\n <button\n mat-icon-button\n type=\"button\"\n class=\"elder-control-icon-button elder-browse-icon\"\n [disabled]=\"isLocked\"\n (click)=\"openSelectionPopup($event)\"\n aria-label=\"Search\"\n elderStopEventPropagation\n tabIndex=\"-1\"\n >\n <mat-icon class=\"elder-mdc-control-icon\">search</mat-icon>\n </button>\n }\n </div>\n </mat-chip-set>\n</div>\n\n<ng-template #selectInput>\n <!-- mat-mdc-chip-input -->\n <elder-select\n autocomplete\n elderClearSelect\n class=\"elder-chip-input-select flex\"\n [data]=\"dataContextS()\"\n [disabled]=\"!!disabledS()\"\n [required]=\"!!requiredS()\"\n [readonly]=\"!!readonlyS()\"\n [placeholder]=\"!readonlyS() ? placeholderS() : undefined\"\n (entityUpdated)=\"appendEntity($event)\"\n [displayPropertyResolver]=\"displayPropertyResolverS()\"\n [valueTemplate]=\"valueTemplate\"\n [queryFilter]=\"queryFilter\"\n [filters]=\"filters\"\n [sorts]=\"sorts\"\n [isOptionDisabledFn]=\"isOptionDisabledInternalFn\"\n [isOptionHiddenFn]=\"isOptionHiddenInternalFn\"\n ></elder-select>\n</ng-template>\n\n<ng-template #simpleChipTemplate let-chipModel>\n <span\n class=\"elder-chip-text\"\n [matTooltip]=\"chipModel.displayText\"\n [matTooltipDisabled]=\"chipModel.displayText?.length < 20\"\n >{{ chipModel.displayText | elderTruncate: 20 }}\n </span>\n</ng-template>\n" }]
|
|
26642
26656
|
}], ctorParameters: () => [], propDecorators: { defaultChipSpec: [{
|
|
26643
26657
|
type: Input
|
|
26644
26658
|
}], chipSpecFn: [{
|