@sumaris-net/ngx-components 2.3.6 → 2.4.0-rc1

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.
@@ -140,7 +140,7 @@ import { isDataSource, SelectionModel } from '@angular/cdk/collections';
140
140
  import { Camera, CameraResultType } from '@capacitor/camera';
141
141
  import { Geolocation } from '@capacitor/geolocation';
142
142
  import * as i3$2 from '@e-is/ngx-material-table';
143
- import { ValidatorService, TableDataSource } from '@e-is/ngx-material-table';
143
+ import { ValidatorService, TableDataSource, AsyncTableDataSource } from '@e-is/ngx-material-table';
144
144
 
145
145
  const ENVIRONMENT = new InjectionToken('ENV');
146
146
  class Environment {
@@ -6439,7 +6439,7 @@ class MatDateTime {
6439
6439
  }
6440
6440
  else {
6441
6441
  // Reset hour
6442
- const day = date.startOf('day');
6442
+ const day = date.clone().startOf('day');
6443
6443
  const dayStr = this.dateAdapter.format(day, this.dayPattern);
6444
6444
  // Format time
6445
6445
  let timeStr;
@@ -25100,6 +25100,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
25100
25100
  type: Directive
25101
25101
  }], ctorParameters: function () { return [{ type: GraphqlService }, { type: PlatformService }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
25102
25102
 
25103
+ const SETTINGS_DISPLAY_COLUMNS = 'displayColumns';
25104
+ const SETTINGS_SORTED_COLUMN = 'sortedColumn';
25105
+ const SETTINGS_FILTER = 'filter';
25106
+ const SETTINGS_PAGE_SIZE = 'pageSize';
25107
+ const DEFAULT_PAGE_SIZE = 20;
25108
+ const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100, 200, 500];
25109
+ const RESERVED_START_COLUMNS = ['select', 'id'];
25110
+ const RESERVED_END_COLUMNS = ['actions'];
25111
+ const DEFAULT_REQUIRED_COLUMNS = ['id'];
25112
+ class CellValueChangeListener {
25113
+ }
25114
+
25103
25115
  class AppTableUtils {
25104
25116
  static waitIdle(table) {
25105
25117
  if (!table || !table.dataSource) {
@@ -25501,17 +25513,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
25501
25513
  type: Directive
25502
25514
  }], ctorParameters: function () { return [{ type: undefined }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
25503
25515
 
25504
- const SETTINGS_DISPLAY_COLUMNS = 'displayColumns';
25505
- const SETTINGS_SORTED_COLUMN = 'sortedColumn';
25506
- const SETTINGS_FILTER = 'filter';
25507
- const SETTINGS_PAGE_SIZE = 'pageSize';
25508
- const DEFAULT_PAGE_SIZE = 20;
25509
- const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100, 200, 500];
25510
- const RESERVED_START_COLUMNS = ['select', 'id'];
25511
- const RESERVED_END_COLUMNS = ['actions'];
25512
- const DEFAULT_REQUIRED_COLUMNS = ['id'];
25513
- class CellValueChangeListener {
25514
- }
25515
25516
  // @dynamic
25516
25517
  // eslint-disable-next-line @angular-eslint/directive-class-suffix
25517
25518
  class AppTable {
@@ -28835,6 +28836,1968 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
28835
28836
  type: Input
28836
28837
  }] } });
28837
28838
 
28839
+ // noinspection DuplicatedCode
28840
+ // @dynamic
28841
+ // eslint-disable-next-line @angular-eslint/directive-class-suffix
28842
+ class EntitiesAsyncTableDataSource extends AsyncTableDataSource {
28843
+ /**
28844
+ * Creates a new TableDataSource instance, that can be used as datasource of `@angular/cdk` data-table.
28845
+ *
28846
+ * @param dataService A service to load and save data
28847
+ * @param dataType Type of data contained by the Table. If not specified, then `data` with at least one element must be specified.
28848
+ * @param environment
28849
+ * @param validatorService Service that create instances of the FormGroup used to validate row fields.
28850
+ * @param config Additional configuration for table.
28851
+ */
28852
+ constructor(dataType, dataService, validatorService, options) {
28853
+ super([], dataType, validatorService, {
28854
+ keepOriginalDataAfterConfirm: false,
28855
+ readOnly: false,
28856
+ saveOnlyDirtyRows: false,
28857
+ ...options
28858
+ });
28859
+ this.dataService = dataService;
28860
+ this._debug = false;
28861
+ this._creating = false;
28862
+ this._saving = false;
28863
+ this._fetchMoreFn = null;
28864
+ this._stopWatchSubject = new Subject();
28865
+ this.loadingSubject = new BehaviorSubject(undefined);
28866
+ this._entityName = removeEnd((new dataType()).__typename || 'UnknownVO', 'VO');
28867
+ this._debug = options?.suppressErrors === false && !environment.production;
28868
+ }
28869
+ get watchAllOptions() {
28870
+ return this.config.watchAllOptions;
28871
+ }
28872
+ set watchAllOptions(value) {
28873
+ this.config.watchAllOptions = value;
28874
+ }
28875
+ get saveAllOptions() {
28876
+ return this.config.saveAllOptions;
28877
+ }
28878
+ set saveAllOptions(value) {
28879
+ this.config.saveAllOptions = value;
28880
+ }
28881
+ get loaded() {
28882
+ return this.loadingSubject.value === false; // Should be false when undefined (initial state)
28883
+ }
28884
+ get loading() {
28885
+ return this.loadingSubject.value !== false; // Should be true when undefined (initial state)
28886
+ }
28887
+ ngOnDestroy() {
28888
+ this.disconnect();
28889
+ }
28890
+ watchAll(offset, size, sortBy, sortDirection, filter) {
28891
+ this._stopWatchSubject.next();
28892
+ this._fetchMoreFn = null;
28893
+ this.markAsLoading();
28894
+ return this.dataService.watchAll(offset, size, sortBy, sortDirection, filter, this.watchAllOptions)
28895
+ .pipe(catchError(err => this.handleError(err, 'ERROR.LOAD_DATA_ERROR')), map((res) => {
28896
+ if (this._saving) {
28897
+ console.info(`[entities-table-datasource] Received ${this._entityName} data (from service), but still saving: skip`);
28898
+ }
28899
+ else if (this.hasSomeEditingRow()) {
28900
+ console.warn(`[entities-table-datasource] Received ${this._entityName} data, while some row still editing: skip; Please check save() implementation in the table!`);
28901
+ }
28902
+ else {
28903
+ this.updateDatasource((res.data || []));
28904
+ this._fetchMoreFn = res.fetchMore;
28905
+ }
28906
+ return res;
28907
+ }),
28908
+ // Stop this pipe next time we call watchAll()
28909
+ takeUntil(this._stopWatchSubject)
28910
+ // ⚠ Notice: Don't put any operator after takeUntil to avoid potential subscription leaks
28911
+ );
28912
+ }
28913
+ updateDatasourceFromRows(rows) {
28914
+ // Avoid to update dataSourceSubject, when not need
28915
+ if (this.datasourceSubject.observers?.length) {
28916
+ if (!this.config.suppressErrors)
28917
+ console.warn('[entities-table-datasource] Update datasource subject. Please prefer using \'rowsSubject\' instead of \'datasourceSubject\'');
28918
+ super.updateDatasourceFromRows(rows);
28919
+ }
28920
+ else {
28921
+ console.debug('[entities-table-datasource] Skipping datasourceSubject update (not used yet).');
28922
+ }
28923
+ }
28924
+ async save() {
28925
+ if (this.config.readOnly) {
28926
+ console.error('[entities-table-datasource] Enable to save, because config.readOnly=true');
28927
+ return false;
28928
+ }
28929
+ // Saving twice (should never occur)
28930
+ if (this._saving) {
28931
+ console.warn(`[entities-table-datasource] Trying to save ${this._entityName} rows twice. Skip`);
28932
+ return false;
28933
+ }
28934
+ this._saving = true;
28935
+ this.markAsLoading();
28936
+ const onlyDirtyRows = this.config.saveOnlyDirtyRows;
28937
+ try {
28938
+ if (this._debug)
28939
+ console.debug(`[entities-table-datasource] Saving ${this._entityName} rows... {onlyDirtyRows: ${onlyDirtyRows}}`);
28940
+ // Get all rows
28941
+ const rows = this.getRows();
28942
+ // Finish editing all rows
28943
+ const invalidRows = (await Promise.all(this.getEditingRows()
28944
+ .map(row => row.confirmEditCreate().then(confirmed => confirmed === false ? row : null))))
28945
+ .filter(isNotNil);
28946
+ // Cannot finish some rows: error
28947
+ if (invalidRows.length) {
28948
+ // log errors
28949
+ if (this._debug)
28950
+ invalidRows.forEach(row => AppTableUtils.logRowErrors(row, `[entities-table-datasource] ${this._entityName} row #${row.id}`));
28951
+ // Stop with an error
28952
+ throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
28953
+ }
28954
+ let data;
28955
+ let dataToSave;
28956
+ if (this.validatorService) {
28957
+ dataToSave = [];
28958
+ data = rows.map(row => {
28959
+ const currentData = new this.dataConstructor();
28960
+ currentData.fromObject(row.currentData);
28961
+ // Filter to keep only dirty row
28962
+ if (onlyDirtyRows && row.validator.dirty)
28963
+ dataToSave.push(currentData);
28964
+ return currentData;
28965
+ });
28966
+ if (!onlyDirtyRows)
28967
+ dataToSave = data;
28968
+ }
28969
+ // Or use the current data without conversion (when no validator service used)
28970
+ else {
28971
+ data = rows.map(row => row.currentData);
28972
+ // save all data, as we don't have any dirty marker
28973
+ dataToSave = data;
28974
+ }
28975
+ // If no data to save: exit
28976
+ if (onlyDirtyRows && !dataToSave.length) {
28977
+ if (this._debug)
28978
+ console.debug(`[entities-table-datasource] No ${this._entityName} data to save. Skip`);
28979
+ return false;
28980
+ }
28981
+ if (this._debug)
28982
+ console.debug(`[entities-table-datasource] Asking service to save this ${this._entityName} data:`, dataToSave);
28983
+ await this.dataService.saveAll(dataToSave, this.saveAllOptions);
28984
+ if (this._debug)
28985
+ console.debug(`[entities-table-datasource] Saving ${this._entityName} data [OK]`);
28986
+ // LP 23/03/2021: update datasource is necessary but can be changed to a refetch() on QueryRef (must be created and registered in GraphqlService.watchQuery)
28987
+ this.updateDatasource(data, { emitEvent: false });
28988
+ return true;
28989
+ }
28990
+ catch (error) {
28991
+ if (this._debug)
28992
+ console.error('[entities-table-datasource] Error while saving: ' + error && error.message || error);
28993
+ throw error;
28994
+ }
28995
+ finally {
28996
+ this._saving = false;
28997
+ this.markAsLoaded();
28998
+ }
28999
+ }
29000
+ updateDatasource(data, opts) {
29001
+ if (this._debug)
29002
+ console.debug(`[entities-table-datasource] Updating datasource with data:`, data);
29003
+ super.updateDatasource(data, opts);
29004
+ if (!opts || opts.emitEvent !== false) {
29005
+ this.markAsLoaded();
29006
+ }
29007
+ }
29008
+ connect(collectionViewer) {
29009
+ // DEBUG
29010
+ //console.debug("[entities-datasource] connect");
29011
+ return super.connect(collectionViewer);
29012
+ }
29013
+ disconnect(collectionViewer) {
29014
+ if (this._debug)
29015
+ console.debug('[entities-table-datasource] Disconnecting...');
29016
+ super.disconnect(collectionViewer);
29017
+ if (!this._stopWatchSubject.closed) {
29018
+ if (this._debug)
29019
+ console.debug('[entities-table-datasource] Closing...');
29020
+ this._stopWatchSubject.next();
29021
+ this._stopWatchSubject.complete();
29022
+ this._stopWatchSubject.unsubscribe();
29023
+ this.loadingSubject.complete();
29024
+ this.loadingSubject.unsubscribe();
29025
+ }
29026
+ }
29027
+ waitIdle(debounceTimeMs) {
29028
+ return firstFalsePromise(this.loadingSubject
29029
+ .asObservable()
29030
+ .pipe(debounceTime(debounceTimeMs || 100) // if not started yet, wait
29031
+ ));
29032
+ }
29033
+ async confirmCreate(row) {
29034
+ const confirmed = await super.confirmCreate(row);
29035
+ if (!confirmed)
29036
+ return false;
29037
+ if (row.editing && row.validator) {
29038
+ console.warn('[entities-table-datasource] Row still has {editing: true} after confirmCreate()! Force editing to false');
29039
+ row.validator.disable({ onlySelf: true, emitEvent: false });
29040
+ }
29041
+ return confirmed;
29042
+ }
29043
+ async confirmEdit(row) {
29044
+ const confirmed = await super.confirmEdit(row);
29045
+ if (!confirmed)
29046
+ return false;
29047
+ if (row.editing && row.validator) {
29048
+ console.warn('[entities-table-datasource] Row still has {editing: true} after confirmEdit()! Force editing to false');
29049
+ row.validator.disable({ onlySelf: true, emitEvent: false });
29050
+ }
29051
+ return true;
29052
+ }
29053
+ async startEdit(row) {
29054
+ const editing = await super.startEdit(row);
29055
+ if (!editing)
29056
+ return false;
29057
+ if (!row.editing && row.validator) {
29058
+ console.warn('[entities-table-datasource] Row still has {editing: false} after startEdit()! Force editing');
29059
+ row.validator.enable({ onlySelf: true, emitEvent: false });
29060
+ }
29061
+ return true;
29062
+ }
29063
+ handleError(error, message) {
29064
+ const errorMsg = error && error.message || error;
29065
+ console.error(`[entities-table-datasource] Service ${this._entityName} sent error: ${errorMsg}`, error);
29066
+ this.markAsLoaded();
29067
+ throw new Error(message || errorMsg);
29068
+ }
29069
+ handleServiceError(error) {
29070
+ const errorMsg = error && error.message || error;
29071
+ console.error(`[entities-table-datasource] Service ${this._entityName} sent error: ${errorMsg}`, error);
29072
+ this.markAsLoaded();
29073
+ throw error;
29074
+ }
29075
+ async delete(id) {
29076
+ // If new row: not need to propagate to the dataService
29077
+ if (id === -1) {
29078
+ return super.delete(id);
29079
+ }
29080
+ const row = this.getRow(id);
29081
+ if (!row) {
29082
+ console.error(`[entities-table-datasource] Row to delete with id=${id} not found`);
29083
+ return;
29084
+ }
29085
+ this.markAsLoading();
29086
+ try {
29087
+ await this.dataService.deleteAll([row.currentData], this.saveAllOptions);
29088
+ // Wait cache update, then table update
29089
+ await sleep(300);
29090
+ // make sure row has been deleted (because GraphQl cache remove can fail)
29091
+ const present = this.getRow(id) === row;
29092
+ if (present)
29093
+ await super.delete(id);
29094
+ this.markAsLoaded();
29095
+ }
29096
+ catch (err) {
29097
+ this.handleServiceError(err);
29098
+ }
29099
+ }
29100
+ async deleteAll(rows) {
29101
+ this.markAsLoading();
29102
+ const data = this.getDataFromRows(rows);
29103
+ try {
29104
+ // Call service deletion
29105
+ await this.dataService.deleteAll(data, this.saveAllOptions);
29106
+ // Workaround, to be sure all rows have been deleted
29107
+ // Sometime, the service miss deletion, or GraphQl cache remove failed.
29108
+ // In this case, apply missing deletion using the parent delete() function
29109
+ const rowNotDeleted = this.getRows().filter(row => rows.includes(row));
29110
+ const deleteFn = super.delete;
29111
+ if (isNotEmptyArray(rowNotDeleted)) {
29112
+ console.warn(`[entities-table-datasource] Force deletion of ${rowNotDeleted.length} rows! Please check that data service update the cache, after deletion`);
29113
+ rowNotDeleted
29114
+ // Start at the end
29115
+ .sort((a, b) => a.id > b.id ? -1 : 1)
29116
+ .forEach(r => deleteFn(r.id));
29117
+ }
29118
+ }
29119
+ catch (err) {
29120
+ // Handle service error
29121
+ this.handleServiceError(err);
29122
+ }
29123
+ finally {
29124
+ this.markAsLoaded();
29125
+ }
29126
+ }
29127
+ getRow(id) {
29128
+ return super.getRow(id);
29129
+ }
29130
+ getRows() {
29131
+ return this.rowsSubject.value || [];
29132
+ }
29133
+ getEditingRows() {
29134
+ return this.getRows().filter(row => row.editing);
29135
+ }
29136
+ getSingleEditingRow() {
29137
+ const rows = this.getRows();
29138
+ return rows.length === 1 ? rows[0] : undefined;
29139
+ }
29140
+ hasSomeEditingRow() {
29141
+ return this.getRows().some(row => row.editing);
29142
+ }
29143
+ async createNew(insertAt, opts = { editing: true }) {
29144
+ // Avoid multiple call (only one editing row is allowed)
29145
+ if (this._creating && opts.editing)
29146
+ return;
29147
+ this._creating = true;
29148
+ try {
29149
+ const row = await super.createNew(insertAt, opts);
29150
+ if (!row)
29151
+ return undefined; // Stop here
29152
+ // Call observers
29153
+ if (this.config?.onRowCreated) {
29154
+ try {
29155
+ await this.config.onRowCreated(row);
29156
+ }
29157
+ catch (err) {
29158
+ // Log, then continue
29159
+ console.error(err && err.message || err, err);
29160
+ }
29161
+ }
29162
+ return row;
29163
+ }
29164
+ finally {
29165
+ this._creating = false;
29166
+ }
29167
+ }
29168
+ getData() {
29169
+ const rows = this.getRows();
29170
+ return this.getDataFromRows(rows);
29171
+ }
29172
+ async fetchMore(opts) {
29173
+ if (!this._fetchMoreFn)
29174
+ return false; // Avoid multiple call
29175
+ if (this.hasSomeEditingRow()) {
29176
+ console.warn(`Cannot fetch more ${this._entityName} because some row) still editing`);
29177
+ return;
29178
+ }
29179
+ console.debug(`Will fetching more row(s) still...`);
29180
+ // Forget the fetchMore function, to avoid multiple call
29181
+ const fetchMoreFn = this._fetchMoreFn;
29182
+ this._fetchMoreFn = null;
29183
+ // Fetch next page
29184
+ const res = await fetchMoreFn();
29185
+ // Skip if empty (no more data)
29186
+ if (isEmptyArray(res?.data))
29187
+ return false;
29188
+ // Update the data source
29189
+ super.updateDatasource((this.currentData || []).concat(...res.data), opts);
29190
+ // Remember fetchMore
29191
+ this._fetchMoreFn = res.fetchMore;
29192
+ return true;
29193
+ }
29194
+ /* -- protected method -- */
29195
+ markAsLoading() {
29196
+ if (this.loadingSubject.value !== true) {
29197
+ this.loadingSubject.next(true);
29198
+ }
29199
+ }
29200
+ markAsLoaded() {
29201
+ if (this.loadingSubject.value !== false) {
29202
+ this.loadingSubject.next(false);
29203
+ }
29204
+ }
29205
+ }
29206
+ EntitiesAsyncTableDataSource.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EntitiesAsyncTableDataSource, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
29207
+ EntitiesAsyncTableDataSource.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: EntitiesAsyncTableDataSource, usesInheritance: true, ngImport: i0 });
29208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: EntitiesAsyncTableDataSource, decorators: [{
29209
+ type: Directive
29210
+ }], ctorParameters: function () { return [{ type: undefined }, { type: undefined }, { type: undefined }, { type: undefined }]; } });
29211
+
29212
+ // @dynamic
29213
+ // noinspection DuplicatedCode
29214
+ // eslint-disable-next-line @angular-eslint/directive-class-suffix
29215
+ class AppAsyncTable {
29216
+ constructor(injector, columns, _dataSource, _filter) {
29217
+ this.columns = columns;
29218
+ this._dataSource = _dataSource;
29219
+ this._filter = _filter;
29220
+ this._initialized = false;
29221
+ this._subscription = new Subscription();
29222
+ this._cellValueChangesDefs = {};
29223
+ this._enabled = true;
29224
+ this.allowRowDetail = true;
29225
+ this.destroySubject = new Subject();
29226
+ this.excludesColumns = [];
29227
+ this.totalRowCount = null;
29228
+ this.readySubject = new BehaviorSubject(false);
29229
+ this.loadingSubject = new BehaviorSubject(true);
29230
+ this.savingSubject = new BehaviorSubject(false);
29231
+ this.touchedSubject = new BehaviorSubject(false);
29232
+ this.dirtySubject = new BehaviorSubject(false);
29233
+ this.errorSubject = new BehaviorSubject(undefined);
29234
+ this.selection = new SelectionModel(true, []);
29235
+ this.i18nColumnPrefix = 'COMMON.';
29236
+ this.autoLoad = true;
29237
+ this.focusFirstColumn = false;
29238
+ this.confirmBeforeDelete = false;
29239
+ this.confirmBeforeCancel = false;
29240
+ this.undoableDeletion = false;
29241
+ this.propagateRowError = false;
29242
+ this.defaultPageSize = DEFAULT_PAGE_SIZE;
29243
+ this.defaultPageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS;
29244
+ this.onRefresh = new EventEmitter();
29245
+ this.onOpenRow = new EventEmitter();
29246
+ this.onNewRow = new EventEmitter();
29247
+ this.onStartEditingRow = new EventEmitter();
29248
+ this.onConfirmEditCreateRow = new EventEmitter();
29249
+ this.onCancelOrDeleteRow = new EventEmitter();
29250
+ this.onBeforeDeleteRows = createPromiseEventEmitter();
29251
+ this.onBeforeCancelRows = createPromiseEventEmitter();
29252
+ this.onBeforeSave = createPromiseEventEmitter();
29253
+ this.onAfterDeletedRows = new EventEmitter();
29254
+ this.onSort = new EventEmitter();
29255
+ this.onDirty = new EventEmitter();
29256
+ this.onError = new EventEmitter();
29257
+ this._paginator = null;
29258
+ this.route = injector.get(ActivatedRoute);
29259
+ this.router = injector.get(Router);
29260
+ this.location = injector.get(Location);
29261
+ this.settings = injector.get(LocalSettingsService);
29262
+ this.translate = injector.get(TranslateService);
29263
+ this.modalCtrl = injector.get(ModalController);
29264
+ this.alertCtrl = injector.get(AlertController);
29265
+ this.toastController = injector.get(ToastController);
29266
+ this.formErrorAdapter = injector.get(FormErrorTranslator);
29267
+ this.mobile = this.settings.mobile;
29268
+ // Autocomplete fields
29269
+ this._autocompleteConfigHolder = new MatAutocompleteConfigHolder({
29270
+ getUserAttributes: (a, b) => this.settings.getFieldDisplayAttributes(a, b)
29271
+ });
29272
+ this.autocompleteFields = this._autocompleteConfigHolder.fields;
29273
+ }
29274
+ get error() {
29275
+ return this.errorSubject.value;
29276
+ }
29277
+ set error(error) {
29278
+ this.setError(error);
29279
+ }
29280
+ get firstUserColumn() {
29281
+ return this.displayedColumns[RESERVED_START_COLUMNS.length];
29282
+ }
29283
+ get lastUserColumn() {
29284
+ return this.displayedColumns[this.displayedColumns.length - RESERVED_END_COLUMNS.length - 1];
29285
+ }
29286
+ set dataSource(value) {
29287
+ this.setDatasource(value);
29288
+ }
29289
+ get dataSource() {
29290
+ return this._dataSource;
29291
+ }
29292
+ set filter(value) {
29293
+ this.setFilter(value);
29294
+ }
29295
+ get filter() {
29296
+ return this._filter;
29297
+ }
29298
+ get empty() {
29299
+ return this.loading || this.totalRowCount === 0;
29300
+ }
29301
+ get dirty() {
29302
+ return this.dirtySubject.value;
29303
+ }
29304
+ get valid() {
29305
+ return this.dataSource.getRows().every(row => row.editing ? row.valid : true);
29306
+ }
29307
+ get invalid() {
29308
+ return this.dataSource.getRows().some(row => row.editing ? row.invalid : false);
29309
+ }
29310
+ get pending() {
29311
+ return this.dataSource.getRows().some(row => row.editing ? row.pending : false);
29312
+ }
29313
+ get touched() {
29314
+ return this.touchedSubject.value;
29315
+ }
29316
+ get untouched() {
29317
+ return !this.touchedSubject.value;
29318
+ }
29319
+ disable(opts) {
29320
+ if (this.sort)
29321
+ this.sort.disabled = true;
29322
+ this._enabled = false;
29323
+ if (!opts || opts.emitEvent != false)
29324
+ this.markForCheck();
29325
+ }
29326
+ enable(opts) {
29327
+ if (this.sort)
29328
+ this.sort.disabled = false;
29329
+ this._enabled = true;
29330
+ if (!opts || opts.emitEvent != false)
29331
+ this.markForCheck();
29332
+ }
29333
+ get enabled() {
29334
+ return this._enabled;
29335
+ }
29336
+ // FIXME: need to hidden buttons (in HTML), etc. when disabled
29337
+ set disabled(value) {
29338
+ if (value !== !this._enabled) {
29339
+ if (value)
29340
+ this.disable({ emitEvent: false });
29341
+ else
29342
+ this.enable({ emitEvent: false });
29343
+ }
29344
+ }
29345
+ get disabled() {
29346
+ return !this._enabled;
29347
+ }
29348
+ markAsDirty(opts) {
29349
+ if (this.dirtySubject.value !== true) {
29350
+ this.dirtySubject.next(true);
29351
+ if (!opts || opts.emitEvent !== false) {
29352
+ this.markForCheck();
29353
+ }
29354
+ }
29355
+ }
29356
+ markAsPristine(opts) {
29357
+ if (this.dirtySubject.value !== false) {
29358
+ this.dirtySubject.next(false);
29359
+ if (!opts || opts.emitEvent !== false) {
29360
+ this.markForCheck();
29361
+ }
29362
+ }
29363
+ }
29364
+ async markAsUntouched(opts) {
29365
+ let needEmitEvent = false;
29366
+ if (this.touchedSubject.value) {
29367
+ this.touchedSubject.next(false);
29368
+ needEmitEvent = true;
29369
+ }
29370
+ if (this.dirty || this.dataSource.hasSomeEditingRow()) {
29371
+ for (const row of this.dataSource.getEditingRows()) {
29372
+ // Cancel the current editing row only if editing and if it was not previously saved
29373
+ await this.dataSource.cancelOrDelete(row);
29374
+ // Mark row as pristine
29375
+ await this.checkIfRowPristine(row, { emitEvent: false });
29376
+ }
29377
+ needEmitEvent = true;
29378
+ }
29379
+ this.previouslyEditedRowId = undefined;
29380
+ if (needEmitEvent && (!opts || opts.emitEvent !== false))
29381
+ this.markForCheck();
29382
+ }
29383
+ /**
29384
+ * @deprecated prefer to use markAllAsTouched()
29385
+ * @param opts
29386
+ */
29387
+ markAsTouched(opts) {
29388
+ console.warn('TODO: Replace this call by markAllAsTouched() - because of changes in ngx-components >= 0.16.0');
29389
+ if (this.dataSource.hasSomeEditingRow()) {
29390
+ this.dataSource.getEditingRows().forEach(row => row.validator?.markAllAsTouched());
29391
+ if (!opts || opts.emitEvent !== false) {
29392
+ this.markForCheck();
29393
+ }
29394
+ }
29395
+ }
29396
+ markAllAsTouched(opts) {
29397
+ if (this.touchedSubject.value !== true) {
29398
+ this.touchedSubject.next(true);
29399
+ if (!opts || opts.emitEvent !== false)
29400
+ this.markForCheck();
29401
+ }
29402
+ if (this.dataSource.hasSomeEditingRow()) {
29403
+ this.dataSource.getEditingRows().forEach(row => row.validator?.markAllAsTouched());
29404
+ }
29405
+ }
29406
+ markAsSaving(opts) {
29407
+ if (this.savingSubject.value !== true) {
29408
+ this.focusColumn = undefined; // unselect focus column
29409
+ this.savingSubject.next(true);
29410
+ if (!opts || opts.emitEvent !== false)
29411
+ this.markForCheck();
29412
+ }
29413
+ }
29414
+ markAsSaved(opts) {
29415
+ if (this.savingSubject.value !== false) {
29416
+ this.savingSubject.next(false);
29417
+ if (!opts || opts.emitEvent !== false)
29418
+ this.markForCheck();
29419
+ }
29420
+ }
29421
+ markAsLoading(opts) {
29422
+ this.setLoading(true, opts);
29423
+ }
29424
+ markAsLoaded(opts) {
29425
+ this.setLoading(false, opts);
29426
+ }
29427
+ markAsReady(opts) {
29428
+ if (this.readySubject.value !== true) {
29429
+ this.readySubject.next(true);
29430
+ // If subclasses implements OnReady
29431
+ if (typeof this['ngOnReady'] === 'function') {
29432
+ this.ngOnReady();
29433
+ }
29434
+ }
29435
+ }
29436
+ get loading() {
29437
+ return this.loadingSubject.value;
29438
+ }
29439
+ get loaded() {
29440
+ return !this.loadingSubject.value;
29441
+ }
29442
+ enableSort() {
29443
+ if (this.sort)
29444
+ this.sort.disabled = false;
29445
+ }
29446
+ disableSort() {
29447
+ if (this.sort)
29448
+ this.sort.disabled = true;
29449
+ }
29450
+ set pageSize(value) {
29451
+ this.defaultPageSize = value;
29452
+ if (this.paginator) {
29453
+ this.paginator.pageSize = value;
29454
+ }
29455
+ }
29456
+ get pageSize() {
29457
+ return this.paginator && this.paginator.pageSize || this.defaultPageSize || DEFAULT_PAGE_SIZE;
29458
+ }
29459
+ get pageOffset() {
29460
+ return this.paginator && this.paginator.pageIndex * this.paginator.pageSize || 0;
29461
+ }
29462
+ get sortActive() {
29463
+ return this.sort && this.sort.active;
29464
+ }
29465
+ get sortDirection() {
29466
+ return this.sort && this.sort.direction && (this.sort.direction === 'desc' ? 'desc' : 'asc') || undefined;
29467
+ }
29468
+ set paginator(value) {
29469
+ this._paginator = value;
29470
+ }
29471
+ get paginator() {
29472
+ return this._paginator || this.childPaginator;
29473
+ }
29474
+ get destroyed() {
29475
+ return this.destroySubject?.closed !== false;
29476
+ }
29477
+ ngOnInit() {
29478
+ if (this._initialized)
29479
+ return; // Init only once
29480
+ this._initialized = true;
29481
+ // Set defaults
29482
+ this.readOnly = toBoolean(this.readOnly, this.dataSource?.config.readOnly || false); // read/write by default
29483
+ this.inlineEdition = !this.readOnly && toBoolean(this.inlineEdition, false); // force to false when readonly
29484
+ this.saveBeforeDelete = toBoolean(this.saveBeforeDelete, !this.readOnly); // force to false when readonly
29485
+ this.saveBeforeSort = toBoolean(this.saveBeforeSort, !this.readOnly); // force to false when readonly
29486
+ this.saveBeforeFilter = toBoolean(this.saveBeforeFilter, !this.readOnly); // force to false when readonly
29487
+ this.keepEditedRowOnSave = toBoolean(this.keepEditedRowOnSave, this.inlineEdition);
29488
+ this.errorTranslatorOptions = this.errorTranslatorOptions || { separator: ', ', controlPathTranslator: this }; // Can be override in subclasses constructors
29489
+ // Check ask user confirmation is possible
29490
+ if (this.confirmBeforeDelete && !this.alertCtrl)
29491
+ throw Error('Missing \'alertCtrl\' or \'injector\' in component\'s constructor.');
29492
+ // Defined unique id for settings for the page
29493
+ this.settingsId = this.settingsId || this.generateTableId();
29494
+ this.displayedColumns = this.getDisplayColumns();
29495
+ // Load the sorted columns, from settings
29496
+ {
29497
+ const sortedColumn = this.getSortedColumn();
29498
+ this.defaultSortBy = sortedColumn.id;
29499
+ this.defaultSortDirection = sortedColumn.start;
29500
+ }
29501
+ this.defaultPageSize = this.getPageSize();
29502
+ // Propagate error to event emitter
29503
+ this.registerSubscription(this.errorSubject
29504
+ .subscribe(value => this.onError.emit(value)));
29505
+ // Propagate dirty to event emitter
29506
+ this.registerSubscription(this.dirtySubject
29507
+ .subscribe(value => this.onDirty.emit(value)));
29508
+ // Propagate row dirty state to table
29509
+ this.registerSubscription(this.onStartEditingRow
29510
+ .pipe(filter(row => row?.validator && true), mergeMap(row => row.validator.valueChanges
29511
+ .pipe(filter(row => row.dirty), first(),
29512
+ // DEBUG
29513
+ //tap(() => console.debug("Propagate row's dirty to table..."))
29514
+ // Stop if next another row, or destroying
29515
+ takeUntil(this.onStartEditingRow), takeUntil(this.destroySubject))))
29516
+ .subscribe(() => this.markAsDirty()));
29517
+ // Call datasource refresh, on each refresh events
29518
+ this.registerSubscription(this.onRefresh
29519
+ .pipe(startWith((this.autoLoad ? {} : 'skip')), switchMap((event) => {
29520
+ this.dirtySubject.next(false);
29521
+ this.selection.clear();
29522
+ if (event === 'skip') {
29523
+ return of(undefined);
29524
+ }
29525
+ if (!this._dataSource) {
29526
+ if (this.debug)
29527
+ console.debug('[table] Skipping data load: no dataSource defined');
29528
+ return of(undefined);
29529
+ }
29530
+ if (this.debug)
29531
+ console.debug('[table] Calling dataSource.watchAll()...');
29532
+ return this._dataSource.watchAll(this.pageOffset, this.pageSize, this.sortActive, this.sortDirection, this._filter);
29533
+ }), catchError(err => {
29534
+ if (this.debug)
29535
+ console.error(err);
29536
+ this.setError(err && err.message || err);
29537
+ return of(undefined); // Continue
29538
+ }))
29539
+ .subscribe(res => this.updateView(res)));
29540
+ // Listen dataSource loading events
29541
+ if (this._dataSource)
29542
+ this.listenDatasourceLoading(this._dataSource);
29543
+ }
29544
+ ngAfterViewInit() {
29545
+ // Detect when parent ngOnInit() not call
29546
+ if (this.debug && !this.displayedColumns)
29547
+ console.warn(`[table] Missing 'displayedColumns'. Did you call parent ngOnInit() in component ${this.constructor.name} ?`);
29548
+ // Start listening sort and paginator events
29549
+ // noinspection JSIgnoredPromiseFromCall
29550
+ this.listenSortAndPaginationEvents();
29551
+ }
29552
+ ngOnDestroy() {
29553
+ this._subscription.unsubscribe();
29554
+ // Unsubscribe column value changes
29555
+ Object.keys(this._cellValueChangesDefs).forEach(col => this.stopCellValueChanges(col, true));
29556
+ this._cellValueChangesDefs = {};
29557
+ this.readySubject.unsubscribe();
29558
+ this.loadingSubject.unsubscribe();
29559
+ this.savingSubject.unsubscribe();
29560
+ this.errorSubject.unsubscribe();
29561
+ this.dirtySubject.unsubscribe();
29562
+ this.onRefresh.unsubscribe();
29563
+ this.onOpenRow.unsubscribe();
29564
+ this.onNewRow.unsubscribe();
29565
+ this.onStartEditingRow.unsubscribe();
29566
+ this.onConfirmEditCreateRow.unsubscribe();
29567
+ this.onCancelOrDeleteRow.unsubscribe();
29568
+ this.onBeforeDeleteRows.unsubscribe();
29569
+ this.onBeforeCancelRows.unsubscribe();
29570
+ this.onBeforeSave.unsubscribe();
29571
+ this.onAfterDeletedRows.unsubscribe();
29572
+ this.onSort.unsubscribe();
29573
+ this.onDirty.unsubscribe();
29574
+ this.onError.unsubscribe();
29575
+ this.destroySubject.next();
29576
+ this.destroySubject.unsubscribe();
29577
+ }
29578
+ async updateView(res, opts) {
29579
+ if (!res)
29580
+ return; // Skip (e.g error)
29581
+ if (res && res.data) {
29582
+ this.visibleRowCount = res.data.length;
29583
+ this.totalRowCount = isNotNil(res.total) ? res.total : ((this.paginator && this.paginator.pageIndex * (this.paginator.pageSize || DEFAULT_PAGE_SIZE) || 0) + this.visibleRowCount);
29584
+ if (this.debug)
29585
+ console.debug(`[table] ${res.data.length} rows loaded`);
29586
+ }
29587
+ else {
29588
+ //if (this.debug) console.debug('[table] NO rows loaded');
29589
+ this.totalRowCount = 0;
29590
+ this.visibleRowCount = 0;
29591
+ }
29592
+ if (!opts || opts.emitEvent !== false) {
29593
+ await this.markAsUntouched({ emitEvent: false });
29594
+ this.markAsPristine({ emitEvent: false });
29595
+ this.markAsLoaded({ emitEvent: false });
29596
+ }
29597
+ this.markForCheck();
29598
+ }
29599
+ setDatasource(datasource) {
29600
+ if (this._dataSource)
29601
+ throw new Error('[table] dataSource already set !');
29602
+ if (datasource && this._dataSource !== datasource) {
29603
+ this._dataSource = datasource;
29604
+ if (this._initialized)
29605
+ this.listenDatasourceLoading(datasource);
29606
+ }
29607
+ }
29608
+ resetDataSource() {
29609
+ if (this._dataSourceLoadingSubscription) {
29610
+ this._dataSourceLoadingSubscription.unsubscribe();
29611
+ this._subscription.remove(this._dataSourceLoadingSubscription);
29612
+ }
29613
+ //this._dataSource?.close();
29614
+ this._dataSource = null;
29615
+ }
29616
+ addColumnDef(column) {
29617
+ this.table.addColumnDef(column);
29618
+ }
29619
+ removeColumnDef(column) {
29620
+ this.table.removeColumnDef(column);
29621
+ }
29622
+ setFilter(filter, opts) {
29623
+ opts = opts || { emitEvent: true };
29624
+ if (this.saveBeforeFilter) {
29625
+ // if a dirty table is to be saved before filter
29626
+ if (this.dirty) {
29627
+ // Save
29628
+ this.saveBeforeAction('filter').then(saved => {
29629
+ // Apply filter only if user didn't cancel the save or the save is ok
29630
+ if (saved) {
29631
+ this.applyFilter(filter, opts);
29632
+ }
29633
+ });
29634
+ }
29635
+ else {
29636
+ // apply filter on non-dirty table
29637
+ this.applyFilter(filter, opts);
29638
+ }
29639
+ }
29640
+ else {
29641
+ // apply filter directly
29642
+ this.applyFilter(filter, opts);
29643
+ }
29644
+ }
29645
+ async confirmAndAdd(event, row) {
29646
+ if (!await this.confirmEditCreate(event, row)) {
29647
+ return false;
29648
+ }
29649
+ // Add row
29650
+ return await this.addRow(event);
29651
+ }
29652
+ async confirmAndBackward(event, row) {
29653
+ // Deleting edited row, if empty and not dirty
29654
+ if (this.dataSource.hasSomeEditingRow()) {
29655
+ for (const editingRow of this.dataSource.getEditingRows().filter(row => row.id === -1 && row.invalid && !row.dirty)) {
29656
+ await this.deleteNewRow(event, editingRow);
29657
+ }
29658
+ // Wait deletion is done, then edit previous row (by id, because of reloading)
29659
+ await this.waitIdle();
29660
+ await this.editRowById(event, row.id, { focusColumn: this.lastUserColumn });
29661
+ return true;
29662
+ }
29663
+ // Edit previous row
29664
+ await this.editRow(event, row, { focusColumn: this.lastUserColumn });
29665
+ return true;
29666
+ }
29667
+ async confirmAndForward(event, row) {
29668
+ if (!this.inlineEdition)
29669
+ return false;
29670
+ await this.confirmEditCreate(event, row);
29671
+ // Edit next row
29672
+ await this.editRowById(event, row.id + 1, { focusColumn: this.firstUserColumn });
29673
+ return true;
29674
+ }
29675
+ /**
29676
+ * Confirm the creation of the given row, or if not specified the currently edited row
29677
+ *
29678
+ * @param event
29679
+ * @param row
29680
+ */
29681
+ async confirmEditCreate(event, row) {
29682
+ row = row || this.dataSource.getSingleEditingRow();
29683
+ if (!row || !row.editing)
29684
+ return true; // no row to confirm
29685
+ // Stop event
29686
+ event?.stopPropagation();
29687
+ // Confirmation edition or creation
29688
+ const confirmed = await row.confirmEditCreate();
29689
+ if (confirmed) {
29690
+ // Mark table as dirty (if row is dirty)
29691
+ if (row.dirty) {
29692
+ this.markAsDirty({ emitEvent: false /* because of resetError() */ });
29693
+ }
29694
+ // Clear error
29695
+ this.resetError();
29696
+ // Emit the confirm event
29697
+ this.onConfirmEditCreateRow.next(row);
29698
+ return true; // Continue
29699
+ }
29700
+ if (row.validator) {
29701
+ // If pending: Wait end of validation
29702
+ // TODO: remove when using async isValid() function
29703
+ if (row.pending) {
29704
+ await AppFormUtils.waitWhilePending(row.validator);
29705
+ }
29706
+ // NOT confirmed = row has error
29707
+ if (this.debug) {
29708
+ console.warn('[table] Cannot confirm row, because invalid');
29709
+ AppFormUtils.logFormErrors(row.validator, '[table] ');
29710
+ }
29711
+ // fix: mark all controls as touched to show errors
29712
+ row.validator.markAllAsTouched();
29713
+ // Compute row error, and propagate to table's error
29714
+ if (this.propagateRowError) {
29715
+ const error = this.getRowError(row);
29716
+ this.setError(error);
29717
+ }
29718
+ }
29719
+ // Not confirmed
29720
+ return false;
29721
+ }
29722
+ async cancelOrDelete(event, row, opts) {
29723
+ // Delete new row
29724
+ if (row.id === -1) {
29725
+ await this.deleteNewRow(event, row);
29726
+ }
29727
+ // Delete existing (but not editing) row
29728
+ else if (!row.editing) {
29729
+ await this.deleteExistingRow(event, row, opts);
29730
+ }
29731
+ // Cancel existing (and editing) row
29732
+ else {
29733
+ await this.cancelExistingRow(event, row, opts);
29734
+ }
29735
+ }
29736
+ async addRow(event, insertAt, opts) {
29737
+ if (this.debug)
29738
+ console.debug('[table] Asking for new row...');
29739
+ if (!this._enabled)
29740
+ return false;
29741
+ // Use modal if inline edition is disabled
29742
+ if (!this.inlineEdition) {
29743
+ await this.openNewRowDetail(event);
29744
+ return false;
29745
+ }
29746
+ // Try to finish edited row first
29747
+ if (!await this.confirmEditCreate()) {
29748
+ return false;
29749
+ }
29750
+ // Add new row
29751
+ const row = await this.addRowToTable(insertAt, opts);
29752
+ return !!row;
29753
+ }
29754
+ async save(opts) {
29755
+ opts = {
29756
+ keepEditing: this.keepEditedRowOnSave,
29757
+ ...opts
29758
+ };
29759
+ if (this.readOnly) {
29760
+ throw { code: ErrorCodes.TABLE_READ_ONLY, message: 'ERROR.TABLE_READ_ONLY' };
29761
+ }
29762
+ this.resetError();
29763
+ // Keep edited row id (should be done BEFORE confirmEditCreate() )
29764
+ const editedRow = this.dataSource.getSingleEditingRow();
29765
+ this.previouslyEditedRowId = opts.keepEditing ? ((editedRow?.editing ? editedRow.id : undefined) || this.singleSelectedRow?.id) : undefined;
29766
+ const previouslyEditedData = opts.keepEditing ? (isNotNil(this.previouslyEditedRowId) && editedRow?.currentData || this.singleSelectedRow?.currentData) : undefined;
29767
+ if (!await this.confirmEditCreate()) {
29768
+ throw { code: ErrorCodes.TABLE_INVALID_ROW_ERROR, message: 'ERROR.TABLE_INVALID_ROW_ERROR' };
29769
+ }
29770
+ // Mark as saving
29771
+ this.markAsSaving();
29772
+ try {
29773
+ // Calling service save()
29774
+ if (this.debug)
29775
+ console.debug('[table] Calling dataSource.save()...');
29776
+ const isOK = await this._dataSource.save();
29777
+ if (isOK)
29778
+ this.markAsPristine();
29779
+ return isOK;
29780
+ }
29781
+ catch (err) {
29782
+ this.setError(err && err.message || err);
29783
+ throw err;
29784
+ }
29785
+ finally {
29786
+ this.markAsSaved();
29787
+ // Restore previous row
29788
+ if (isNotNil(this.previouslyEditedRowId)) {
29789
+ await this.selectRowByIdOrData(this.previouslyEditedRowId, previouslyEditedData);
29790
+ }
29791
+ }
29792
+ }
29793
+ async cancel(event, opts) {
29794
+ // Check confirmation
29795
+ if ((!opts || opts.interactive !== false) && this.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
29796
+ event?.stopPropagation();
29797
+ if (!await this.canCancelRows()) {
29798
+ return;
29799
+ }
29800
+ }
29801
+ this.onRefresh.emit();
29802
+ }
29803
+ async duplicateRow(event, row, opts) {
29804
+ event?.stopPropagation();
29805
+ row = row || this.singleSelectedRow;
29806
+ if (!row || !await this.confirmEditCreate(event, row)) {
29807
+ return false;
29808
+ }
29809
+ const newRow = await this.addRowToTable(row.id + 1);
29810
+ if (!newRow)
29811
+ throw new Error('Cannot add new row to table');
29812
+ const json = { ...row.currentData, id: null };
29813
+ // Reset some properties (e.g. rankOrder, etc)
29814
+ if (opts && opts.skipProperties) {
29815
+ const newData = newRow.currentData;
29816
+ opts.skipProperties.forEach(key => json[key] = newData[key]);
29817
+ }
29818
+ if (newRow.validator) {
29819
+ newRow.validator.patchValue(json);
29820
+ newRow.validator.markAsDirty();
29821
+ }
29822
+ else {
29823
+ if (newRow.currentData?.fromObject) {
29824
+ newRow.currentData.fromObject(json);
29825
+ }
29826
+ else {
29827
+ newRow.currentData = json;
29828
+ }
29829
+ this.markAsDirty();
29830
+ }
29831
+ // select
29832
+ await this.clickRow(undefined, newRow);
29833
+ }
29834
+ /** Whether the number of selected elements matches the total number of rows. */
29835
+ isAllSelected() {
29836
+ // DEBUG
29837
+ //console.debug('isAllSelected. lengths', this.selection.selected.length, this.totalRowCount);
29838
+ return this.selection.selected.length === this.totalRowCount ||
29839
+ this.selection.selected.length === this.visibleRowCount;
29840
+ }
29841
+ /** Selects all rows if they are not all selected; otherwise clear selection. */
29842
+ async masterToggle() {
29843
+ if (this.loading)
29844
+ return;
29845
+ if (this.isAllSelected()) {
29846
+ this.selection.clear();
29847
+ }
29848
+ else {
29849
+ const rows = this._dataSource.getRows();
29850
+ rows.forEach(row => this.selection.select(row));
29851
+ }
29852
+ }
29853
+ deleteSelection(event, opts) {
29854
+ return this.deleteRows(event, this.selection.selected, opts);
29855
+ }
29856
+ /**
29857
+ *
29858
+ * @param event
29859
+ * @param row
29860
+ * @param opts Use interactive=false to avoid user interaction (e.g. user confirmation)
29861
+ * And to force deletion even if table is busy
29862
+ */
29863
+ async deleteRow(event, row, opts) {
29864
+ const deleteCount = await this.deleteRows(event, [row], opts);
29865
+ return deleteCount === 1;
29866
+ }
29867
+ /**
29868
+ *
29869
+ * @param event
29870
+ * @param rows
29871
+ * @param opts Use interactive=false to avoid user interaction (e.g. user confirmation)
29872
+ * And to force deletion even if table is busy
29873
+ */
29874
+ async deleteRows(event, rows, opts) {
29875
+ if (this.readOnly) {
29876
+ throw { code: ErrorCodes.TABLE_READ_ONLY, message: 'ERROR.TABLE_READ_ONLY' };
29877
+ }
29878
+ if (event?.defaultPrevented)
29879
+ return 0; // SKip
29880
+ event?.preventDefault();
29881
+ if (!this._enabled || isEmptyArray(rows))
29882
+ return 0; // Skip is disabled, or no rows to delete
29883
+ if (this.loading && (!opts || opts.interactive !== false)) {
29884
+ console.warn('[app-table] Skip deleteRows() because table is busy (loading). Use opts.interactive = false to force deletion');
29885
+ return 0; // Skip if loading
29886
+ }
29887
+ // Make sure to keep newly created row
29888
+ const editedRow = this.dataSource.getSingleEditingRow();
29889
+ if (editedRow?.id === -1 && editedRow.editing && !rows.includes(editedRow)) {
29890
+ const confirmed = await this.confirmEditCreate();
29891
+ if (!confirmed)
29892
+ return 0; // Cannot delete (e.g. edited row is invalid)
29893
+ }
29894
+ // Check if it can delete
29895
+ const canDelete = await this.canDeleteRows(rows, opts);
29896
+ if (!canDelete)
29897
+ return 0; // Cannot delete
29898
+ // Reverse row order (on a copy)
29899
+ // This is a workaround, need because row.delete() has async execution
29900
+ // and index cache is updated with a delay
29901
+ let tempRows = rows.slice()
29902
+ .sort((a, b) => a.id > b.id ? -1 : 1);
29903
+ let deletedRows = [];
29904
+ // If data need to be saved first
29905
+ if (this.saveBeforeDelete) {
29906
+ // Exclude invalid rows (because of save() will fail, when exists some invalid rows)
29907
+ tempRows = tempRows.filter(row => {
29908
+ // Delete the row :
29909
+ // - if newly created row (id = -1),
29910
+ // - or if invalid and not editing (= not cancellable)
29911
+ if (row.id === -1 || (!row.editing && row.invalid /*do not use !valid because if row is disabled, it will be always !valid */)) {
29912
+ if (this.debug)
29913
+ console.debug(`[table] Delete row #${row.id}`);
29914
+ row.delete();
29915
+ this.visibleRowCount--;
29916
+ this.totalRowCount--;
29917
+ deletedRows.push(row);
29918
+ return false; // Exclude from the list to delete using the service
29919
+ }
29920
+ // Cancel the row (and mark as pristine), when not valid but in edition
29921
+ else if (row.editing && !row.valid) {
29922
+ row.cancel();
29923
+ // Mark row as pristine (if possible)
29924
+ this.checkIfRowPristine(row, { emitEvent: false, onlySelf: true /*avoid propagation to table*/ });
29925
+ return true; // Keep the row. the row will be deleted after the save
29926
+ }
29927
+ return true;
29928
+ });
29929
+ // Apply save, only if there is still some rows to delete
29930
+ // WARN: If no more rows (e.g. because all has been cancelled) then continue anyway to clear editedRow and selection (issue IMAGINE-669)
29931
+ if (deletedRows.length || tempRows.length) {
29932
+ // Save data (e.g. when using memory service)
29933
+ const saved = await this.saveBeforeAction('delete');
29934
+ if (!saved) {
29935
+ // Stop if save cancelled or save failed
29936
+ return;
29937
+ }
29938
+ }
29939
+ }
29940
+ try {
29941
+ // Apply deletion on datasource
29942
+ // If no more rows to delete, continue anyway to clear editedRow and selection (issue IMAGINE-669)
29943
+ if (tempRows.length) {
29944
+ if (this.debug)
29945
+ console.debug(`[table] Delete ${tempRows.length} rows...`);
29946
+ await this._dataSource.deleteAll(tempRows);
29947
+ }
29948
+ // DO not update manually, because watchALl().subscribe() will update this count
29949
+ //this.totalRowCount -= deleteCount;
29950
+ //this.visibleRowCount -= deleteCount;
29951
+ this.selection.clear();
29952
+ this.markAsDirty({ emitEvent: false /*markForCheck() is called just after*/ });
29953
+ this.markForCheck();
29954
+ this.onAfterDeletedRows.next(rows);
29955
+ return rows.length;
29956
+ }
29957
+ catch (err) {
29958
+ this.setError(err && err.message || err);
29959
+ throw err;
29960
+ }
29961
+ }
29962
+ async selectRowById(id) {
29963
+ if (id === undefined)
29964
+ return false;
29965
+ await this.waitIdle();
29966
+ const row = this.dataSource.getRow(id);
29967
+ if (!row)
29968
+ return false;
29969
+ return this.clickRow(null, row);
29970
+ }
29971
+ /**
29972
+ * Try to select row by data. Will use dataEquals() (that call EntityUtils.equals()) to find same row's data
29973
+ * @param data
29974
+ */
29975
+ async selectRowByData(data) {
29976
+ if (data === undefined)
29977
+ return Promise.resolve(false);
29978
+ await this.waitIdle();
29979
+ const row = this.dataSource.getRows()
29980
+ .find(row => this.equals(row.currentData, data));
29981
+ if (!row)
29982
+ return false;
29983
+ return this.clickRow(null, row);
29984
+ }
29985
+ async clickRow(event, row) {
29986
+ if (this.loading) {
29987
+ // Wait while loading, and loop
29988
+ if (this.debug)
29989
+ console.debug('[table] Waiting before apply clickRow() (datasource is busy)...');
29990
+ await this.waitIdle({ timeout: 2000 });
29991
+ }
29992
+ // DEBUG
29993
+ //console.debug("[table] Detect click on row");
29994
+ if (row.id === -1 || row.editing)
29995
+ return true; // Already in edition
29996
+ if (event?.defaultPrevented)
29997
+ return false; // Cancelled by event
29998
+ // Open the detail page (if not inline editing)
29999
+ if (!this.inlineEdition) {
30000
+ if (event) {
30001
+ event.stopPropagation();
30002
+ event.preventDefault();
30003
+ }
30004
+ this.markAsLoading();
30005
+ this.selection.clear();
30006
+ this.openRow(row.currentData.id, row)
30007
+ .then(() => this.markAsLoaded())
30008
+ .catch(() => this.markAsLoaded());
30009
+ return true;
30010
+ }
30011
+ // Start editing row
30012
+ return this.editRow(event, row, { focusColumn: undefined /*force to use the click target*/ });
30013
+ }
30014
+ async moveRow(id, direction) {
30015
+ await this.dataSource.move(id, direction);
30016
+ }
30017
+ ready(opts) {
30018
+ return waitForTrue(this.readySubject, opts);
30019
+ }
30020
+ waitIdle(opts) {
30021
+ return waitForFalse(this.loadingSubject, opts);
30022
+ }
30023
+ async openSelectColumnsModal(event) {
30024
+ event?.preventDefault();
30025
+ // Copy current columns (deep copy)
30026
+ const columns = this.getCurrentColumns();
30027
+ const hasTopModal = !!(await this.modalCtrl.getTop());
30028
+ const modal = await this.modalCtrl.create({
30029
+ component: TableSelectColumnsComponent,
30030
+ componentProps: { columns },
30031
+ cssClass: hasTopModal && 'stack-modal'
30032
+ });
30033
+ // Open the modal
30034
+ await modal.present();
30035
+ // On dismiss
30036
+ const { data } = await modal.onDidDismiss();
30037
+ if (!data)
30038
+ return; // CANCELLED
30039
+ // Apply columns
30040
+ const userColumns = (data || []).filter(c => c.canHide === false || c.visible).map(c => c.name) || [];
30041
+ this.displayedColumns = RESERVED_START_COLUMNS.concat(userColumns).concat(RESERVED_END_COLUMNS);
30042
+ this.markForCheck();
30043
+ // Update user settings
30044
+ await this.settings.savePageSetting(this.settingsId, userColumns, SETTINGS_DISPLAY_COLUMNS);
30045
+ }
30046
+ trackByFn(index, row) {
30047
+ return row.id;
30048
+ }
30049
+ doRefresh(event) {
30050
+ this.onRefresh.emit(event);
30051
+ // When target wait for a complete (e.g. IonRefresher)
30052
+ if (event?.target && event.target.complete) {
30053
+ setTimeout(async () => {
30054
+ await this.waitIdle();
30055
+ event.target.complete();
30056
+ });
30057
+ }
30058
+ }
30059
+ getCurrentColumns() {
30060
+ const hiddenColumns = this.columns.slice(RESERVED_START_COLUMNS.length)
30061
+ .filter(name => this.displayedColumns.indexOf(name) === -1);
30062
+ return this.displayedColumns
30063
+ .concat(hiddenColumns)
30064
+ .filter(name => !RESERVED_START_COLUMNS.includes(name) && !RESERVED_END_COLUMNS.includes(name)
30065
+ && !this.excludesColumns.includes(name))
30066
+ .map(name => ({
30067
+ name,
30068
+ label: this.getI18nColumnName(name),
30069
+ visible: this.displayedColumns.indexOf(name) !== -1,
30070
+ canHide: this.getRequiredColumns().indexOf(name) === -1
30071
+ }));
30072
+ }
30073
+ async escapeEditingRow(event, row) {
30074
+ row = row || this.dataSource.getSingleEditingRow();
30075
+ if (!row || !row.editing)
30076
+ return;
30077
+ // DEBUG
30078
+ //console.debug('[app-table] Cancel the row (keydown.escape)');
30079
+ if (event) {
30080
+ // Avoid to cancel the editor
30081
+ event.preventDefault();
30082
+ event.stopPropagation();
30083
+ }
30084
+ // If new row (no id)
30085
+ if (row.id === -1) {
30086
+ if (row.validator) {
30087
+ // If pending: Wait end of validation, then loop
30088
+ if (row.pending) {
30089
+ await AppFormUtils.waitWhilePending(row.validator);
30090
+ }
30091
+ // Row is invalid: delete the row
30092
+ if (row.invalid) {
30093
+ await this.deleteNewRow(event, row);
30094
+ return;
30095
+ }
30096
+ }
30097
+ }
30098
+ // If the row exists (has an id)
30099
+ else {
30100
+ // need to call cancel function (if confirmation will be called before)
30101
+ if (this.confirmBeforeCancel) {
30102
+ await this.cancelExistingRow(event, row, { keepEditing: false });
30103
+ return;
30104
+ }
30105
+ }
30106
+ // By default, try to confirm the row
30107
+ await this.confirmEditCreate(event, row);
30108
+ }
30109
+ translateControlPath(path) {
30110
+ // Can be overridden by subclasses, to resolve all field name
30111
+ // Use columns key, has default name
30112
+ const i18nColumnKey = this.getI18nColumnName(path);
30113
+ return this.translate?.instant(i18nColumnKey) || i18nColumnKey;
30114
+ }
30115
+ /* -- protected method -- */
30116
+ async editRow(event, row, opts) {
30117
+ if (!this._enabled || !this.inlineEdition)
30118
+ return false;
30119
+ if (this.dataSource.getEditingRows().includes(row))
30120
+ return true; // Already the edited row
30121
+ if (event?.defaultPrevented)
30122
+ return false;
30123
+ if (!await this.confirmEditCreate()) {
30124
+ return false;
30125
+ }
30126
+ if (!row.editing && !this.loading) {
30127
+ this.focusColumn = opts && opts.focusColumn || this.focusColumn;
30128
+ await this._dataSource.startEdit(row);
30129
+ }
30130
+ this.onStartEditingRow.emit(row);
30131
+ return true;
30132
+ }
30133
+ /**
30134
+ * Try to select row, by row.id, or by data. WIll use EntityUtils.equals()
30135
+ * @param id
30136
+ * @param data
30137
+ * @protected
30138
+ */
30139
+ async selectRowByIdOrData(id, data) {
30140
+ let done = false;
30141
+ try {
30142
+ // Select by row id (if NOT a new row)
30143
+ if (isNotNil(id) || id !== -1) {
30144
+ done = await this.selectRowById(id);
30145
+ if (done)
30146
+ return true;
30147
+ console.warn('[app-table] Save: Cannot reselect row by row.id: ', id);
30148
+ }
30149
+ // Try by data
30150
+ if (data) {
30151
+ done = await this.selectRowByData(data);
30152
+ if (done)
30153
+ return true;
30154
+ console.warn('[app-table] Save: Cannot reselect row by data: ', data);
30155
+ }
30156
+ return false;
30157
+ }
30158
+ catch (err) {
30159
+ // Log, but continue
30160
+ console.error(err && err.message || err);
30161
+ return false;
30162
+ }
30163
+ }
30164
+ /**
30165
+ * return the selected row if unique in selection
30166
+ */
30167
+ get singleSelectedRow() {
30168
+ return this.selection.selected?.length === 1 ? this.selection.selected[0] : undefined;
30169
+ }
30170
+ async canDeleteRows(rows, opts) {
30171
+ // Check using emitter
30172
+ if (this.onBeforeDeleteRows.observers.length > 0) {
30173
+ try {
30174
+ const canDelete = await emitPromiseEvent(this.onBeforeDeleteRows, 'canDelete', {
30175
+ detail: { rows }
30176
+ });
30177
+ if (!canDelete)
30178
+ return false;
30179
+ }
30180
+ catch (err) {
30181
+ if (err === 'CANCELLED')
30182
+ return false; // User cancel
30183
+ console.error('Error while checking if can delete rows', err);
30184
+ throw err;
30185
+ }
30186
+ }
30187
+ // Ask user confirmation
30188
+ if (this.confirmBeforeDelete && (!opts || opts.interactive !== false)) {
30189
+ return this.askDeleteConfirmation(null, rows);
30190
+ }
30191
+ return true;
30192
+ }
30193
+ async canCancelRows(rows, opts) {
30194
+ // Get dirty rows
30195
+ rows = rows || this.dataSource.getRows().filter(row => row.validator?.dirty);
30196
+ if (isEmptyArray(rows))
30197
+ return true; // No dirty: OK
30198
+ // Check using emitter
30199
+ if (this.onBeforeCancelRows.observers.length > 0) {
30200
+ try {
30201
+ const isCancel = await emitPromiseEvent(this.onBeforeCancelRows, 'canCancel', {
30202
+ detail: { rows }
30203
+ });
30204
+ if (!isCancel)
30205
+ return false;
30206
+ }
30207
+ catch (err) {
30208
+ if (err === 'CANCELLED')
30209
+ return false; // User cancel
30210
+ console.error('Error while checking if can cancel rows', err);
30211
+ throw err;
30212
+ }
30213
+ }
30214
+ // Ask user confirmation
30215
+ if (this.confirmBeforeCancel && (!opts || opts.interactive !== false)) {
30216
+ return this.askCancelConfirmation(null, rows);
30217
+ }
30218
+ return true;
30219
+ }
30220
+ async saveBeforeAction(saveAction) {
30221
+ if (!this.dirty) {
30222
+ // Continue without save
30223
+ return true;
30224
+ }
30225
+ let save;
30226
+ switch (saveAction) {
30227
+ case 'delete':
30228
+ save = this.saveBeforeDelete;
30229
+ break;
30230
+ case 'filter':
30231
+ save = this.saveBeforeFilter;
30232
+ break;
30233
+ case 'sort':
30234
+ save = this.saveBeforeSort;
30235
+ break;
30236
+ default:
30237
+ save = true;
30238
+ }
30239
+ // Default behavior
30240
+ let confirmed = true;
30241
+ if (save) {
30242
+ if (this.onBeforeSave.observers.length > 0) {
30243
+ // Ask confirmation
30244
+ try {
30245
+ const res = await emitPromiseEvent(this.onBeforeSave, 'beforeSave', {
30246
+ detail: { action: saveAction, valid: this.valid }
30247
+ });
30248
+ confirmed = res.confirmed;
30249
+ save = res.save;
30250
+ }
30251
+ catch (err) {
30252
+ if (err === 'CANCELLED')
30253
+ return false; // User cancel
30254
+ console.error('Error while checking if can delete rows', err);
30255
+ throw err;
30256
+ }
30257
+ }
30258
+ }
30259
+ if (confirmed) {
30260
+ if (save) {
30261
+ // User confirmed save
30262
+ const saved = await this.save();
30263
+ this.markAsDirty(); // Restore dirty flag
30264
+ return saved;
30265
+ }
30266
+ return true; // No save but continue action
30267
+ }
30268
+ return false; // User cancel the action
30269
+ }
30270
+ /**
30271
+ * Open a row detail view. By default, will to open row detail page.
30272
+ * Can be overridden by subclasses, BUT prefer to subscribe on onOpenRow
30273
+ * @param id
30274
+ * @param row
30275
+ * @protected
30276
+ */
30277
+ async openRow(id, row) {
30278
+ if (this.allowRowDetail) {
30279
+ if (this.debug && this.dirty) {
30280
+ console.warn('[table] Opening row details, but table has unsaved changes!');
30281
+ }
30282
+ if (this.onOpenRow.observers.length) {
30283
+ this.onOpenRow.emit(row);
30284
+ return true;
30285
+ }
30286
+ // No ID defined: unable to open details
30287
+ if (isNil(id)) {
30288
+ console.warn('[table] Opening row details, but data has no id!');
30289
+ return false;
30290
+ }
30291
+ return this.router.navigate(['.', id.toString()], {
30292
+ relativeTo: this.route,
30293
+ queryParams: {}
30294
+ });
30295
+ }
30296
+ return false;
30297
+ }
30298
+ async openNewRowDetail(event) {
30299
+ if (!this.allowRowDetail)
30300
+ return false;
30301
+ if (this.onNewRow.observers.length > 0) {
30302
+ this.onNewRow.emit(event);
30303
+ return true;
30304
+ }
30305
+ return await this.router.navigate(['new'], {
30306
+ relativeTo: this.route
30307
+ });
30308
+ }
30309
+ // can be overridden to add more required columns
30310
+ getRequiredColumns() {
30311
+ return DEFAULT_REQUIRED_COLUMNS;
30312
+ }
30313
+ getUserColumns() {
30314
+ return this.settings.getPageSettings(this.settingsId, SETTINGS_DISPLAY_COLUMNS);
30315
+ }
30316
+ getSortedColumn() {
30317
+ const data = this.settings.getPageSettings(this.settingsId, SETTINGS_SORTED_COLUMN);
30318
+ const parts = data && data.split(':');
30319
+ if (parts && parts.length === 2 && this.columns.includes(parts[0])) {
30320
+ return { id: parts[0], start: parts[1] === 'desc' ? 'desc' : 'asc', disableClear: false };
30321
+ }
30322
+ if (this.defaultSortBy) {
30323
+ return { id: this.defaultSortBy, start: this.defaultSortDirection || 'asc', disableClear: false };
30324
+ }
30325
+ return { id: 'id', start: 'asc', disableClear: false };
30326
+ }
30327
+ getPageSize() {
30328
+ const pageSize = this.settings.getPageSettings(this.settingsId, SETTINGS_PAGE_SIZE);
30329
+ return pageSize || this.defaultPageSize;
30330
+ }
30331
+ getDisplayColumns() {
30332
+ let userColumns = this.getUserColumns();
30333
+ // No user override
30334
+ if (!userColumns) {
30335
+ // Return default, without columns to hide
30336
+ return this.columns.filter(column => !this.excludesColumns.includes(column));
30337
+ }
30338
+ // Get fixed start columns
30339
+ const fixedStartColumns = this.columns.filter(c => RESERVED_START_COLUMNS.includes(c));
30340
+ // Remove end columns
30341
+ const fixedEndColumns = this.columns.filter(c => RESERVED_END_COLUMNS.includes(c));
30342
+ // Remove fixed columns from user columns
30343
+ userColumns = userColumns.filter(c => !fixedStartColumns.includes(c) && !fixedEndColumns.includes(c) && this.columns.includes(c));
30344
+ // Add required columns if missing
30345
+ userColumns.push(...this.getRequiredColumns().filter(c => !fixedStartColumns.includes(c) && !fixedEndColumns.includes(c) && !userColumns.includes(c)));
30346
+ return fixedStartColumns
30347
+ .concat(userColumns)
30348
+ .concat(fixedEndColumns)
30349
+ // Remove columns to hide
30350
+ .filter(column => !this.excludesColumns.includes(column));
30351
+ }
30352
+ /**
30353
+ * Recompute display columns
30354
+ *
30355
+ * @protected
30356
+ */
30357
+ updateColumns() {
30358
+ this.displayedColumns = this.getDisplayColumns();
30359
+ if (!this.loading)
30360
+ this.markForCheck();
30361
+ }
30362
+ registerSubscription(sub) {
30363
+ this._subscription.add(sub);
30364
+ }
30365
+ unregisterSubscription(sub) {
30366
+ this._subscription.remove(sub);
30367
+ }
30368
+ registerAutocompleteField(fieldName, options) {
30369
+ return this._autocompleteConfigHolder.add(fieldName, options);
30370
+ }
30371
+ getI18nColumnName(columnName) {
30372
+ return (this.i18nColumnPrefix || '') + changeCaseToUnderscore(columnName).toUpperCase();
30373
+ }
30374
+ generateTableId() {
30375
+ // noinspection JSNonASCIINames
30376
+ const id = this.location.path(true)
30377
+ .replace(/[?].*$/g, '')
30378
+ .replace(/\/\d+/g, '_id')
30379
+ + '_'
30380
+ // Get a component unique name - See https://stackoverflow.com/questions/60114682/how-to-access-components-unique-encapsulation-id-in-angular-9
30381
+ + (this.constructor['ɵcmp']?.id || this.constructor.name);
30382
+ //if (this.debug) console.debug("[table] id = " + id);
30383
+ return id;
30384
+ }
30385
+ async addRowToTable(insertAt, opts) {
30386
+ // Try to finish edited row first
30387
+ if (!await this.confirmEditCreate()) {
30388
+ console.warn('[table] Cannot add new row, because the previous edited row cannot be confirmed');
30389
+ return undefined;
30390
+ }
30391
+ const editing = this.inlineEdition && (!opts || opts.editing !== false); // true by default, if inlineEdition
30392
+ const row = await this._dataSource.createNew(insertAt, { editing });
30393
+ if (!row)
30394
+ return undefined;
30395
+ if (row.editing) {
30396
+ // Update focused column
30397
+ this.focusFirstColumn = true;
30398
+ this.focusColumn = opts?.focusColumn || this.firstUserColumn;
30399
+ // Emit start editing event
30400
+ this.onStartEditingRow.emit(row);
30401
+ }
30402
+ this.totalRowCount++;
30403
+ this.visibleRowCount++;
30404
+ this.markAsDirty({ emitEvent: false /*markForCheck() is called just after*/ });
30405
+ // Emit event
30406
+ if (!opts || opts.emitEvent !== false)
30407
+ this.markForCheck();
30408
+ return row;
30409
+ }
30410
+ registerCellValueChanges(name, formPath, emitInitialValue) {
30411
+ formPath = formPath || name;
30412
+ emitInitialValue = emitInitialValue || false;
30413
+ let def = this._cellValueChangesDefs[name];
30414
+ if (def && (def.formPath !== formPath || def.emitInitialValue !== emitInitialValue)) {
30415
+ throw Error('Already register a cell value change for this name, with different \'formPath\' or \'emitInitialValue\'. Please use same arguments.');
30416
+ }
30417
+ // Not exists: register new definition
30418
+ if (!def) {
30419
+ if (this.debug)
30420
+ console.debug(`[table] New listener {${name}} for value changes on path ${formPath}`);
30421
+ def = {
30422
+ subject: new Subject(),
30423
+ subscription: null,
30424
+ formPath,
30425
+ emitInitialValue
30426
+ };
30427
+ this._cellValueChangesDefs[name] = def;
30428
+ // Start the listener, when editing starts
30429
+ this.registerSubscription(this.onStartEditingRow.subscribe(row => this.startCellValueChanges(name, row)));
30430
+ }
30431
+ return def.subject;
30432
+ }
30433
+ setShowColumn(columnName, show, opts) {
30434
+ if (!this.excludesColumns.includes(columnName) !== show) {
30435
+ if (!show) {
30436
+ this.excludesColumns.push(columnName);
30437
+ }
30438
+ else {
30439
+ const index = this.excludesColumns.findIndex(value => value === columnName);
30440
+ if (index >= 0)
30441
+ this.excludesColumns.splice(index, 1);
30442
+ }
30443
+ // Recompute display columns
30444
+ if (this.displayedColumns && (!opts || opts.emitEvent !== false)) {
30445
+ this.updateColumns();
30446
+ }
30447
+ }
30448
+ }
30449
+ getShowColumn(columnName) {
30450
+ return !this.excludesColumns.includes(columnName);
30451
+ }
30452
+ startsWithUpperCase(input, search) {
30453
+ return input && input.toUpperCase().startsWith(search);
30454
+ }
30455
+ markForCheck() {
30456
+ // Should be overridden by subclasses, depending on ChangeDetectionStrategy
30457
+ }
30458
+ async askDeleteConfirmation(event, rows) {
30459
+ if (this.undoableDeletion) {
30460
+ // Special message, for undoable deletion
30461
+ return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.DELETE_ROW' : 'CONFIRM.DELETE_ROWS', this.alertCtrl, this.translate, event);
30462
+ }
30463
+ // Immediate deletion action
30464
+ return Alerts.askActionConfirmation(this.alertCtrl, this.translate, true, event);
30465
+ }
30466
+ async askCancelConfirmation(event, rows) {
30467
+ return Alerts.askConfirmation(rows?.length === 1 ? 'CONFIRM.CANCEL_ROW' : 'CONFIRM.CANCEL_ROWS', this.alertCtrl, this.translate, event);
30468
+ }
30469
+ async askRestoreConfirmation(event) {
30470
+ return Alerts.askActionConfirmation(this.alertCtrl, this.translate, false, event);
30471
+ }
30472
+ async showToast(opts) {
30473
+ if (!this.toastController)
30474
+ throw new Error('Missing toastController in component\'s constructor');
30475
+ return Toasts.show(this.toastController, this.translate, opts);
30476
+ }
30477
+ resetError(opts) {
30478
+ this.setError(undefined, opts);
30479
+ }
30480
+ getRowError(row, opts) {
30481
+ row = row || this.dataSource.getSingleEditingRow();
30482
+ if (!row || !this.formErrorAdapter)
30483
+ return undefined;
30484
+ return this.formErrorAdapter.translateFormErrors(row.validator, {
30485
+ ...this.errorTranslatorOptions,
30486
+ ...opts
30487
+ });
30488
+ }
30489
+ setError(value, opts) {
30490
+ if (this.errorSubject.value !== value) {
30491
+ this.errorSubject.next(value);
30492
+ if (!opts || opts.emitEvent !== false) {
30493
+ this.markForCheck();
30494
+ }
30495
+ }
30496
+ }
30497
+ /**
30498
+ * Compare data equality (default by id)
30499
+ * Can be overridden to add additional properties to compare
30500
+ *
30501
+ * @param d1
30502
+ * @param d2
30503
+ * @protected
30504
+ */
30505
+ equals(d1, d2) {
30506
+ return EntityUtils.equals(d1, d2, 'id');
30507
+ }
30508
+ markRowAsDirty(row, opts) {
30509
+ row = row || this.dataSource.getSingleEditingRow();
30510
+ if (row)
30511
+ row.validator?.markAsDirty(opts);
30512
+ this.markAsDirty(opts);
30513
+ }
30514
+ /* -- private method -- */
30515
+ async listenSortAndPaginationEvents() {
30516
+ if (!this.table) {
30517
+ // DEBUG only -- alert user that table not found in template
30518
+ if (this.debug) {
30519
+ setTimeout(() => !this.table && console.warn(`[table] Missing <mat-table> in the HTML template (after waiting 500ms)! Component: ${this.constructor.name}`), 500);
30520
+ }
30521
+ // Make sure to wait the table
30522
+ await waitFor(() => !!this.table, { stop: this.destroySubject, stopError: false /*avoid error when destorying the table*/ });
30523
+ }
30524
+ this.registerSubscription(merge(
30525
+ // Listen sort events
30526
+ this.sort && this.sort.sortChange
30527
+ .pipe(filter(() => !this.sort.disabled), mergeMap(async () => this.saveBeforeAction('sort')), filter(res => res === true),
30528
+ // Save sort in settings
30529
+ tap(() => {
30530
+ const value = [this.sort.active, this.sort.direction || 'asc'].join(':');
30531
+ this.settings.savePageSetting(this.settingsId, value, SETTINGS_SORTED_COLUMN);
30532
+ }))
30533
+ || EMPTY,
30534
+ // Listen paginator events
30535
+ this.paginator && this.paginator.page
30536
+ .pipe(mergeMap((_) => this.saveBeforeAction('sort')), filter(saved => saved === true),
30537
+ // Save page size in settings
30538
+ tap(() => this.settings.savePageSetting(this.settingsId, this.paginator.pageSize, SETTINGS_PAGE_SIZE))) || EMPTY)
30539
+ // Refresh on any sort or paginator events
30540
+ .subscribe(value => {
30541
+ this.onSort.emit(value);
30542
+ this.onRefresh.emit(value);
30543
+ }));
30544
+ // If the user changes the sort order, reset back to the first page.
30545
+ if (this.sort && this.paginator) {
30546
+ this.registerSubscription(this.sort.sortChange
30547
+ .pipe(filter(() => !this.sort.disabled))
30548
+ .subscribe(() => this.paginator.pageIndex = 0));
30549
+ }
30550
+ }
30551
+ async editRowById(event, id, opts) {
30552
+ if (id < 0)
30553
+ return;
30554
+ if (id >= this.visibleRowCount) {
30555
+ await this.addRow(event, undefined, { ...opts, editing: true });
30556
+ }
30557
+ else {
30558
+ const row = await this.dataSource.getRow(id);
30559
+ await this.editRow(event, row, opts);
30560
+ }
30561
+ }
30562
+ setLoading(value, opts) {
30563
+ if (this.loadingSubject.value !== value) {
30564
+ this.loadingSubject.next(value);
30565
+ if (!opts || opts.emitEvent !== false) {
30566
+ this.markForCheck();
30567
+ }
30568
+ }
30569
+ }
30570
+ async deleteNewRow(event, row) {
30571
+ if (row.id !== -1)
30572
+ throw new Error('Row must have id = -1');
30573
+ event?.stopPropagation();
30574
+ this.selection.clear();
30575
+ await this._dataSource.cancelOrDelete(row);
30576
+ this.onCancelOrDeleteRow.next(row);
30577
+ this.resetError();
30578
+ this.totalRowCount--;
30579
+ this.visibleRowCount--;
30580
+ }
30581
+ async deleteExistingRow(event, row, opts) {
30582
+ // Make sure row will be cancelled, and NOT deleted
30583
+ if (row.id === -1)
30584
+ throw new Error('Row must have an id');
30585
+ if (event?.defaultPrevented)
30586
+ return 0; // SKip
30587
+ event?.preventDefault();
30588
+ await this.deleteRow(null, row, opts);
30589
+ }
30590
+ async cancelExistingRow(event, row, opts) {
30591
+ // Make sure row will be cancelled, and NOT deleted
30592
+ if (row.id === -1 || !row.editing)
30593
+ throw new Error('Row cannot be canceling, but only deleting');
30594
+ const confirmed = (!opts || opts.interactive !== false);
30595
+ // Ask user confirmation, if cancel
30596
+ if (!confirmed && row.dirty && (this.confirmBeforeCancel || this.onBeforeCancelRows.observers.length > 0)) {
30597
+ event.stopPropagation();
30598
+ if (!await this.canCancelRows([row], opts)) {
30599
+ return;
30600
+ }
30601
+ }
30602
+ const keepEditing = row.editing && (!opts || opts.keepEditing !== false);
30603
+ await this._dataSource.cancelOrDelete(row);
30604
+ this.onCancelOrDeleteRow.next(row);
30605
+ // Mark row as pristine
30606
+ await this.checkIfRowPristine(row);
30607
+ // Restore editing state
30608
+ if (keepEditing) {
30609
+ await this.editRow(undefined, row);
30610
+ }
30611
+ }
30612
+ async checkIfRowPristine(row, opts) {
30613
+ // Mark row as pristine
30614
+ const markRowAsPristine = this.dataSource?.config.restoreOriginalDataOnCancel === true;
30615
+ if (markRowAsPristine) {
30616
+ row.validator?.markAsPristine();
30617
+ // Check if table is now pristine
30618
+ if (!opts || opts.onlySelf !== true) {
30619
+ await this.checkIfPristine(opts);
30620
+ }
30621
+ }
30622
+ }
30623
+ async checkIfPristine(opts) {
30624
+ if (!this.dirty)
30625
+ return; // Already pristine
30626
+ const rows = this._dataSource.getRows();
30627
+ const pristine = (rows || []).findIndex(row => row.dirty) === -1;
30628
+ if (pristine)
30629
+ this.markAsPristine(opts);
30630
+ }
30631
+ applyFilter(filter, opts) {
30632
+ if (this.debug)
30633
+ console.debug('[table] Applying filter', filter);
30634
+ this._filter = filter;
30635
+ if (opts && opts.emitEvent) {
30636
+ if (this.paginator && this.paginator.pageIndex > 0) {
30637
+ this.paginator.pageIndex = 0;
30638
+ }
30639
+ this.onRefresh.emit();
30640
+ }
30641
+ }
30642
+ listenDatasourceLoading(dataSource) {
30643
+ if (!dataSource)
30644
+ throw new Error('[table] dataSource not set !');
30645
+ // Cleaning previous subscription on datasource
30646
+ if (isNotNil(this._dataSourceLoadingSubscription)) {
30647
+ if (this.debug)
30648
+ console.debug('[table] Many call to listenDatasource(): Cleaning previous subscriptions...');
30649
+ this._dataSourceLoadingSubscription.unsubscribe();
30650
+ this.unregisterSubscription(this._dataSourceLoadingSubscription);
30651
+ }
30652
+ // Propage loading to table
30653
+ this._dataSourceLoadingSubscription = this._dataSource.loadingSubject
30654
+ .pipe(distinctUntilChanged(),
30655
+ // If changed to True: propagate as soon as possible
30656
+ tap((loading) => loading && this.setLoading(true)),
30657
+ // If changed to False: wait 250ms before propagate (to make sure the spinner has been displayed)
30658
+ debounceTime(250), tap(loading => !loading && this.setLoading(false)))
30659
+ .subscribe();
30660
+ this.registerSubscription(this._dataSourceLoadingSubscription);
30661
+ }
30662
+ startCellValueChanges(name, row) {
30663
+ const def = this._cellValueChangesDefs[name];
30664
+ if (!def) {
30665
+ console.warn('[table] Listener with name {' + name + '} not registered! Please call registerCellValueChanges() before;');
30666
+ return;
30667
+ }
30668
+ // Stop previous subscription
30669
+ if (def.subscription) {
30670
+ def.subscription.unsubscribe();
30671
+ def.subscription = null;
30672
+ }
30673
+ else {
30674
+ if (this.debug)
30675
+ console.debug(`[table] Start values changes on row path {${def.formPath}}`);
30676
+ }
30677
+ // Listen value changes, and redirect to event emitter
30678
+ const control = row.validator && AppFormUtils.getControlFromPath(row.validator, def.formPath);
30679
+ if (!control) {
30680
+ console.warn(`[table] Could not listen cell changes: no validator or invalid form path {${def.formPath}}`);
30681
+ }
30682
+ else {
30683
+ def.subscription = control.valueChanges
30684
+ .pipe(
30685
+ // don't emit if control is disabled
30686
+ filter(() => control.enabled))
30687
+ .subscribe((value) => def.subject.next(value));
30688
+ // Emit the actual value
30689
+ if (def.emitInitialValue !== false) {
30690
+ def.subject.next(control.value);
30691
+ }
30692
+ }
30693
+ }
30694
+ stopCellValueChanges(name, destroy) {
30695
+ const def = this._cellValueChangesDefs[name];
30696
+ if (!def)
30697
+ return;
30698
+ if (def.subscription) {
30699
+ if (this.debug)
30700
+ console.debug('[table] Stop value changes on row path {' + def.formPath + '}');
30701
+ def.subscription.unsubscribe();
30702
+ def.subscription = null;
30703
+ }
30704
+ if (destroy && def.subject) {
30705
+ def.subject.complete();
30706
+ def.subject.unsubscribe();
30707
+ }
30708
+ }
30709
+ }
30710
+ AppAsyncTable.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AppAsyncTable, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
30711
+ AppAsyncTable.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: AppAsyncTable, inputs: { settingsId: "settingsId", debug: "debug", i18nColumnPrefix: "i18nColumnPrefix", i18nColumnSuffix: "i18nColumnSuffix", autoLoad: "autoLoad", readOnly: "readOnly", inlineEdition: "inlineEdition", focusFirstColumn: "focusFirstColumn", confirmBeforeDelete: "confirmBeforeDelete", confirmBeforeCancel: "confirmBeforeCancel", undoableDeletion: "undoableDeletion", saveBeforeDelete: "saveBeforeDelete", keepEditedRowOnSave: "keepEditedRowOnSave", saveBeforeSort: "saveBeforeSort", saveBeforeFilter: "saveBeforeFilter", propagateRowError: "propagateRowError", defaultSortBy: "defaultSortBy", defaultSortDirection: "defaultSortDirection", defaultPageSize: "defaultPageSize", defaultPageSizeOptions: "defaultPageSizeOptions", focusColumn: "focusColumn", dataSource: "dataSource", filter: "filter", disabled: "disabled", paginator: "paginator" }, outputs: { onRefresh: "onRefresh", onOpenRow: "onOpenRow", onNewRow: "onNewRow", onStartEditingRow: "onStartEditingRow", onConfirmEditCreateRow: "onConfirmEditCreateRow", onCancelOrDeleteRow: "onCancelOrDeleteRow", onBeforeDeleteRows: "onBeforeDeleteRows", onBeforeCancelRows: "onBeforeCancelRows", onBeforeSave: "onBeforeSave", onAfterDeletedRows: "onAfterDeletedRows", onSort: "onSort", onDirty: "onDirty", onError: "onError" }, viewQueries: [{ propertyName: "table", first: true, predicate: MatTable, descendants: true }, { propertyName: "childPaginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0 });
30712
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AppAsyncTable, decorators: [{
30713
+ type: Directive
30714
+ }], ctorParameters: function () { return [{ type: i0.Injector }, { type: undefined }, { type: EntitiesAsyncTableDataSource }, { type: undefined }]; }, propDecorators: { settingsId: [{
30715
+ type: Input
30716
+ }], debug: [{
30717
+ type: Input
30718
+ }], i18nColumnPrefix: [{
30719
+ type: Input
30720
+ }], i18nColumnSuffix: [{
30721
+ type: Input
30722
+ }], autoLoad: [{
30723
+ type: Input
30724
+ }], readOnly: [{
30725
+ type: Input
30726
+ }], inlineEdition: [{
30727
+ type: Input
30728
+ }], focusFirstColumn: [{
30729
+ type: Input
30730
+ }], confirmBeforeDelete: [{
30731
+ type: Input
30732
+ }], confirmBeforeCancel: [{
30733
+ type: Input
30734
+ }], undoableDeletion: [{
30735
+ type: Input
30736
+ }], saveBeforeDelete: [{
30737
+ type: Input
30738
+ }], keepEditedRowOnSave: [{
30739
+ type: Input
30740
+ }], saveBeforeSort: [{
30741
+ type: Input
30742
+ }], saveBeforeFilter: [{
30743
+ type: Input
30744
+ }], propagateRowError: [{
30745
+ type: Input
30746
+ }], defaultSortBy: [{
30747
+ type: Input
30748
+ }], defaultSortDirection: [{
30749
+ type: Input
30750
+ }], defaultPageSize: [{
30751
+ type: Input
30752
+ }], defaultPageSizeOptions: [{
30753
+ type: Input
30754
+ }], focusColumn: [{
30755
+ type: Input
30756
+ }], dataSource: [{
30757
+ type: Input
30758
+ }], filter: [{
30759
+ type: Input
30760
+ }], onRefresh: [{
30761
+ type: Output
30762
+ }], onOpenRow: [{
30763
+ type: Output
30764
+ }], onNewRow: [{
30765
+ type: Output
30766
+ }], onStartEditingRow: [{
30767
+ type: Output
30768
+ }], onConfirmEditCreateRow: [{
30769
+ type: Output
30770
+ }], onCancelOrDeleteRow: [{
30771
+ type: Output
30772
+ }], onBeforeDeleteRows: [{
30773
+ type: Output
30774
+ }], onBeforeCancelRows: [{
30775
+ type: Output
30776
+ }], onBeforeSave: [{
30777
+ type: Output
30778
+ }], onAfterDeletedRows: [{
30779
+ type: Output
30780
+ }], onSort: [{
30781
+ type: Output
30782
+ }], onDirty: [{
30783
+ type: Output
30784
+ }], onError: [{
30785
+ type: Output
30786
+ }], disabled: [{
30787
+ type: Input
30788
+ }], paginator: [{
30789
+ type: Input
30790
+ }], table: [{
30791
+ type: ViewChild,
30792
+ args: [MatTable, { static: false }]
30793
+ }], childPaginator: [{
30794
+ type: ViewChild,
30795
+ args: [MatPaginator, { static: false }]
30796
+ }], sort: [{
30797
+ type: ViewChild,
30798
+ args: [MatSort, { static: false }]
30799
+ }] } });
30800
+
28838
30801
  // @dynamic
28839
30802
  // eslint-disable-next-line @angular-eslint/directive-class-suffix
28840
30803
  class AppInMemoryTable extends AppTable {
@@ -33400,5 +35363,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
33400
35363
  * Generated bundle index. Do not edit.
33401
35364
  */
33402
35365
 
33403
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_STORAGE, APP_TESTING_PAGES, APP_USER_EVENT_SERVICE, AboutModal, AbstractDateFormat, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AnimationState, AppAboutModalModule, AppAccountModule, AppAuthForm, AppAuthModal, AppAuthModule, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppRegisterModule, AppSelectPeerModule, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayJoinPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, JobModule, JobProgression, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBooleanField, MatChipsField, MatColorPipe, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuOptions, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, NumpadTestPage, ObservableTestPage, OddPipe, PEER_URL_REGEXP, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, SCRYPT_PARAMS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, StartableService, StatusById, StatusIds, StatusList, StorageDrivers, StorageService, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEventModule, UserEventNotificationIcon, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, initArrayControlsFromValues, isAndroid, isBlankString, isCapacitor, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
35366
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_STORAGE, APP_TESTING_PAGES, APP_USER_EVENT_SERVICE, AboutModal, AbstractDateFormat, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AnimationState, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHelpModal, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppRegisterModule, AppSelectPeerModule, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendToInputDirective, ArrayFilterPipe, ArrayFirstPipe, ArrayIncludesPipe, ArrayJoinPipe, ArrayLengthPipe, ArrayPluckPipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CellValueChangeListener, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigService, Configuration, CoreModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DragAndDropDirective, DurationPipe, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, ErrorCodes, EvenPipe, FileResponse, FileService, FileSizePipe, FilesUtils, FormArrayHelper, FormButtonsBarComponent, FormButtonsBarToken, FormErrorTranslatePipe, FormErrorTranslator, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, Fragments, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsLoginAccountPipe, IsNilOrBlankPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsNotOnFieldModePipe, IsOnFieldModePipe, JobModule, JobProgression, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_DEFAULT_MAX_DECIMALS, LAT_LONG_PATTERNS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LongitudeFormatPipe, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapValuesPipe, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBooleanField, MatChipsField, MatColorPipe, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatNumpadComponent, MatNumpadContainerComponent, MatNumpadContent, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItems, MenuOptions, MenuService, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NetworkService, NgInitDirective, NgVarDirective, NotEmptyArrayPipe, NumberFormatPipe, NumpadDirective, NumpadTestPage, ObservableTestPage, OddPipe, PEER_URL_REGEXP, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialUtils, ReferentialValidatorService, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, SCRYPT_PARAMS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SelectPeerModal, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatNumpadModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, StartableService, StatusById, StatusIds, StatusList, StorageDrivers, StorageService, StrIncludesPipe, StrLengthPipe, SwipeTestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TextForm, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UserEventModule, UserEventNotificationIcon, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UsersPage, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayGroupBy, arraySize, asInputElement, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableControls, emitPromiseEvent, entityToString, equals, equalsOrNil, escapeRegExp, fadeInAnimation, fadeInOutAnimation, filterFalse, filterNotNil, filterNumberInput, filterTrue, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getConnectionType, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, hexToRgb, hexToRgbArray, initArrayControlsFromValues, isAndroid, isBlankString, isCapacitor, isControlHasInput, isCordova, isEmptyArray, isIOS, isInputElement, isInstanceOf, isInt, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isProgressEvent, isResponseEvent, isTouchUi, isWindows, joinProperties, joinPropertiesPath, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, noTrailingSlash, notNilOrDefault, nullIfUndefined, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputRange, setCalculatedValue, setTabIndex, sleep, slideInOutAnimation, slideUpDownAnimation, sort, splitById, splitByProperty, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, uncapitalizeFirstLetter, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
33404
35367
  //# sourceMappingURL=sumaris-net.ngx-components.mjs.map