@universis/ngx-tables 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +163 -0
  2. package/README.md +121 -0
  3. package/bundles/universis-ngx-tables.umd.js +4591 -0
  4. package/bundles/universis-ngx-tables.umd.js.map +1 -0
  5. package/bundles/universis-ngx-tables.umd.min.js +2 -0
  6. package/bundles/universis-ngx-tables.umd.min.js.map +1 -0
  7. package/esm2015/lib/components/add-items/add-items.component.js +142 -0
  8. package/esm2015/lib/components/advanced-aggregator/advanced-aggregator.component.js +120 -0
  9. package/esm2015/lib/components/advanced-aggregator-list/advanced-aggregator-list.component.js +53 -0
  10. package/esm2015/lib/components/advanced-list/advanced-list.component.js +173 -0
  11. package/esm2015/lib/components/advanced-row-action/advanced-row-action.component.js +175 -0
  12. package/esm2015/lib/components/advanced-search-form/advanced-search-form.component.js +134 -0
  13. package/esm2015/lib/components/advanced-select/advanced-select.component.js +51 -0
  14. package/esm2015/lib/components/advanced-select/advanced-select.service.js +40 -0
  15. package/esm2015/lib/components/advanced-table/advanced-filter-value-provider.service.js +37 -0
  16. package/esm2015/lib/components/advanced-table/advanced-table-resolvers.js +44 -0
  17. package/esm2015/lib/components/advanced-table/advanced-table-search-base.js +23 -0
  18. package/esm2015/lib/components/advanced-table/advanced-table-search.component.js +519 -0
  19. package/esm2015/lib/components/advanced-table/advanced-table-settings.component.js +109 -0
  20. package/esm2015/lib/components/advanced-table/advanced-table.component.js +1007 -0
  21. package/esm2015/lib/components/advanced-table/advanced-table.formatters.interface.js +16 -0
  22. package/esm2015/lib/components/advanced-table/advanced-table.formatters.js +271 -0
  23. package/esm2015/lib/components/advanced-table/advanced-table.interfaces.js +2 -0
  24. package/esm2015/lib/components/advanced-table-modal/advanced-table-modal-base.component.js +379 -0
  25. package/esm2015/lib/components/table-simple-header/table-simple-header.component.js +52 -0
  26. package/esm2015/lib/directives/advanced-table-editor.directive.js +157 -0
  27. package/esm2015/lib/directives/btnexp.directive.js +225 -0
  28. package/esm2015/lib/i18n/tables.el.js +104 -0
  29. package/esm2015/lib/i18n/tables.en.js +103 -0
  30. package/esm2015/lib/pipes/advanced-aggregator.pipe.js +35 -0
  31. package/esm2015/lib/pipes/advansed-aggregator-list-title-parse.pipe.js +46 -0
  32. package/esm2015/lib/services/advanced-aggregator/advanced-aggregator.service.js +117 -0
  33. package/esm2015/lib/tables.activated-table.service.js +36 -0
  34. package/esm2015/lib/tables.module.js +134 -0
  35. package/esm2015/public_api.js +22 -0
  36. package/esm2015/universis-ngx-tables.js +14 -0
  37. package/esm5/lib/components/add-items/add-items.component.js +153 -0
  38. package/esm5/lib/components/advanced-aggregator/advanced-aggregator.component.js +169 -0
  39. package/esm5/lib/components/advanced-aggregator-list/advanced-aggregator-list.component.js +56 -0
  40. package/esm5/lib/components/advanced-list/advanced-list.component.js +185 -0
  41. package/esm5/lib/components/advanced-row-action/advanced-row-action.component.js +179 -0
  42. package/esm5/lib/components/advanced-search-form/advanced-search-form.component.js +137 -0
  43. package/esm5/lib/components/advanced-select/advanced-select.component.js +56 -0
  44. package/esm5/lib/components/advanced-select/advanced-select.service.js +43 -0
  45. package/esm5/lib/components/advanced-table/advanced-filter-value-provider.service.js +50 -0
  46. package/esm5/lib/components/advanced-table/advanced-table-resolvers.js +48 -0
  47. package/esm5/lib/components/advanced-table/advanced-table-search-base.js +29 -0
  48. package/esm5/lib/components/advanced-table/advanced-table-search.component.js +556 -0
  49. package/esm5/lib/components/advanced-table/advanced-table-settings.component.js +115 -0
  50. package/esm5/lib/components/advanced-table/advanced-table.component.js +1035 -0
  51. package/esm5/lib/components/advanced-table/advanced-table.formatters.interface.js +16 -0
  52. package/esm5/lib/components/advanced-table/advanced-table.formatters.js +313 -0
  53. package/esm5/lib/components/advanced-table/advanced-table.interfaces.js +2 -0
  54. package/esm5/lib/components/advanced-table-modal/advanced-table-modal-base.component.js +295 -0
  55. package/esm5/lib/components/table-simple-header/table-simple-header.component.js +58 -0
  56. package/esm5/lib/directives/advanced-table-editor.directive.js +175 -0
  57. package/esm5/lib/directives/btnexp.directive.js +253 -0
  58. package/esm5/lib/i18n/tables.el.js +104 -0
  59. package/esm5/lib/i18n/tables.en.js +103 -0
  60. package/esm5/lib/pipes/advanced-aggregator.pipe.js +39 -0
  61. package/esm5/lib/pipes/advansed-aggregator-list-title-parse.pipe.js +49 -0
  62. package/esm5/lib/services/advanced-aggregator/advanced-aggregator.service.js +130 -0
  63. package/esm5/lib/tables.activated-table.service.js +43 -0
  64. package/esm5/lib/tables.module.js +139 -0
  65. package/esm5/public_api.js +22 -0
  66. package/esm5/universis-ngx-tables.js +14 -0
  67. package/fesm2015/universis-ngx-tables.js +4127 -0
  68. package/fesm2015/universis-ngx-tables.js.map +1 -0
  69. package/fesm5/universis-ngx-tables.js +4313 -0
  70. package/fesm5/universis-ngx-tables.js.map +1 -0
  71. package/lib/advanced-aggregator.d.ts +124 -0
  72. package/lib/components/add-items/add-items.component.d.ts +44 -0
  73. package/lib/components/advanced-aggregator/advanced-aggregator.component.d.ts +45 -0
  74. package/lib/components/advanced-aggregator-list/advanced-aggregator-list.component.d.ts +25 -0
  75. package/lib/components/advanced-list/advanced-list.component.d.ts +42 -0
  76. package/lib/components/advanced-row-action/advanced-row-action.component.d.ts +42 -0
  77. package/lib/components/advanced-search-form/advanced-search-form.component.d.ts +27 -0
  78. package/lib/components/advanced-select/advanced-select.component.d.ts +14 -0
  79. package/lib/components/advanced-select/advanced-select.service.d.ts +9 -0
  80. package/lib/components/advanced-table/advanced-filter-value-provider.service.d.ts +8 -0
  81. package/lib/components/advanced-table/advanced-table-resolvers.d.ts +12 -0
  82. package/lib/components/advanced-table/advanced-table-search-base.d.ts +7 -0
  83. package/lib/components/advanced-table/advanced-table-search.component.d.ts +80 -0
  84. package/lib/components/advanced-table/advanced-table-settings.component.d.ts +35 -0
  85. package/lib/components/advanced-table/advanced-table.component.d.ts +142 -0
  86. package/lib/components/advanced-table/advanced-table.formatters.d.ts +62 -0
  87. package/lib/components/advanced-table/advanced-table.formatters.interface.d.ts +15 -0
  88. package/lib/components/advanced-table/advanced-table.interfaces.d.ts +27 -0
  89. package/lib/components/advanced-table-modal/advanced-table-modal-base.component.d.ts +82 -0
  90. package/lib/components/table-simple-header/table-simple-header.component.d.ts +14 -0
  91. package/lib/directives/advanced-table-editor.directive.d.ts +25 -0
  92. package/lib/directives/btnexp.directive.d.ts +68 -0
  93. package/lib/i18n/tables.el.d.ts +102 -0
  94. package/lib/i18n/tables.en.d.ts +101 -0
  95. package/lib/pipes/advanced-aggregator.pipe.d.ts +21 -0
  96. package/lib/pipes/advansed-aggregator-list-title-parse.pipe.d.ts +21 -0
  97. package/lib/services/advanced-aggregator/advanced-aggregator.service.d.ts +32 -0
  98. package/lib/tables.activated-table.service.d.ts +9 -0
  99. package/lib/tables.module.d.ts +7 -0
  100. package/package.json +51 -0
  101. package/public_api.d.ts +18 -0
  102. package/universis-ngx-tables.d.ts +12 -0
  103. package/universis-ngx-tables.metadata.json +1 -0
@@ -0,0 +1,4127 @@
1
+ import { at } from 'lodash';
2
+ import { getNativeName } from 'iso-639-1/build';
3
+ import * as jQuery_ from 'jquery';
4
+ import 'datatables.net';
5
+ import 'datatables.net-scroller';
6
+ import 'rxjs/add/observable/combineLatest';
7
+ import { HttpClient } from '@angular/common/http';
8
+ import { DatePipe, CommonModule } from '@angular/common';
9
+ import { ClientDataQueryable, ResponseError, TextUtils } from '@themost/client';
10
+ import { FormArray, FormBuilder, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
11
+ import { FormioModule } from 'angular-formio';
12
+ import { BsModalService, BsModalRef, ModalModule } from 'ngx-bootstrap/modal';
13
+ import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
14
+ import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
15
+ import { __awaiter } from 'tslib';
16
+ import { GroupByPipe, NgPipesModule } from 'ngx-pipes';
17
+ import { Injectable, Pipe, EventEmitter, Input, Output, Component, ElementRef, ViewChild, ViewEncapsulation, Directive, ChangeDetectorRef, Inject, InjectionToken, Injector, HostListener, defineInjectable, inject, NgModule, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy } from '@angular/core';
18
+ import { RouterModalOkCancel, RouterModalModule } from '@universis/common/routing';
19
+ import { ActivatedRoute, Router, NavigationStart, RouterModule } from '@angular/router';
20
+ import { BehaviorSubject } from 'rxjs';
21
+ import { TranslateService, TranslatePipe, TranslateModule } from '@ngx-translate/core';
22
+ import { AngularDataContext } from '@themost/angular';
23
+ import { ServiceUrlPreProcessor, TranslatePreProcessor, AdvancedFormsModule } from '@universis/forms';
24
+ import { TemplatePipe, LoadingService, ErrorService, DIALOG_BUTTONS, ModalService, UserActivityService, ToastService, AppEventService, UserStorageService, ConfigurationService, SharedModule } from '@universis/common';
25
+
26
+ class AdvancedColumnFormatter {
27
+ }
28
+ class NestedPropertyFormatter extends AdvancedColumnFormatter {
29
+ render(data, type, row, meta) {
30
+ // get column
31
+ const column = meta.settings.aoColumns[meta.col];
32
+ if (column && column.data) {
33
+ return at(row, column.data.replace(/\//g, '.'));
34
+ }
35
+ return data;
36
+ }
37
+ constructor() {
38
+ super();
39
+ }
40
+ }
41
+ class ButtonFormatter extends AdvancedColumnFormatter {
42
+ render(data, type, row, meta) {
43
+ if (data == null) {
44
+ return '';
45
+ }
46
+ // get column
47
+ const column = meta.settings.aoColumns[meta.col];
48
+ // get format options
49
+ const formatOptions = Object.assign({
50
+ buttonClass: 'btn btn-default'
51
+ }, this.formatOptions);
52
+ // get text content
53
+ let buttonText;
54
+ let buttonTitle = '';
55
+ if (formatOptions.buttonText) {
56
+ buttonText = this.injector.get(TranslateService).instant(formatOptions.buttonText);
57
+ }
58
+ else {
59
+ buttonText = formatOptions.buttonContent;
60
+ }
61
+ if (formatOptions.buttonTitle) {
62
+ buttonTitle = this.injector.get(TranslateService).instant(formatOptions.buttonTitle);
63
+ }
64
+ if (Array.isArray(this.formatOptions.commands)) {
65
+ const activatedRoute = this.injector.get(ActivatedRoute);
66
+ const router = this.injector.get(Router);
67
+ const commands = this.formatOptions.commands.map(command => {
68
+ if (typeof command === 'string') {
69
+ return new TemplatePipe().transform(command, row);
70
+ }
71
+ if (command.outlets) {
72
+ const commandOutlets = {};
73
+ Object.keys(command.outlets).forEach((key) => {
74
+ if (Object.prototype.hasOwnProperty.call(command.outlets, key)) {
75
+ const outlet = command.outlets[key];
76
+ if (Array.isArray(outlet)) {
77
+ Object.defineProperty(commandOutlets, key, {
78
+ enumerable: true,
79
+ configurable: true,
80
+ writable: true,
81
+ value: outlet.map((x) => {
82
+ return new TemplatePipe().transform(x, row);
83
+ })
84
+ });
85
+ }
86
+ else {
87
+ Object.defineProperty(commandOutlets, key, {
88
+ enumerable: true,
89
+ configurable: true,
90
+ writable: true,
91
+ value: outlet
92
+ });
93
+ }
94
+ }
95
+ });
96
+ return {
97
+ outlets: commandOutlets
98
+ };
99
+ }
100
+ return command;
101
+ });
102
+ const queryParams = {};
103
+ let replaceUrl = true;
104
+ if (this.formatOptions.navigationExtras && this.formatOptions.navigationExtras.queryParams) {
105
+ Object.keys(this.formatOptions.navigationExtras.queryParams).map(key => {
106
+ queryParams[key] = new TemplatePipe().transform(this.formatOptions.navigationExtras.queryParams[key], row);
107
+ });
108
+ }
109
+ if (this.formatOptions.navigationExtras && typeof this.formatOptions.navigationExtras.replaceUrl !== 'undefined') {
110
+ replaceUrl = this.formatOptions.navigationExtras.replaceUrl;
111
+ }
112
+ let skipLocationChange = false;
113
+ if (this.formatOptions.navigationExtras && typeof this.formatOptions.navigationExtras.skipLocationChange !== 'undefined') {
114
+ skipLocationChange = this.formatOptions.navigationExtras.skipLocationChange;
115
+ }
116
+ const routerLink = router.createUrlTree(commands, {
117
+ relativeTo: activatedRoute,
118
+ queryParams: queryParams,
119
+ replaceUrl: replaceUrl,
120
+ skipLocationChange: skipLocationChange
121
+ }).toString();
122
+ return `<a class="${formatOptions.buttonClass}" title="${buttonTitle}" href="#${routerLink}">${buttonText}</a>`;
123
+ }
124
+ // and return button
125
+ const href = new TemplatePipe().transform(this.formatString, row);
126
+ return `<a class="${formatOptions.buttonClass}" title="${buttonTitle}" href="${href}">${buttonText}</a>`;
127
+ }
128
+ constructor() {
129
+ super();
130
+ }
131
+ }
132
+ class LinkFormatter extends AdvancedColumnFormatter {
133
+ render(data, type, row, meta) {
134
+ // get column
135
+ const column = meta.settings.aoColumns[meta.col];
136
+ if (column && column.data) {
137
+ return `<a href="${(new TemplatePipe().transform(this.formatString, row))}">${data}</a>`;
138
+ }
139
+ return data;
140
+ }
141
+ constructor() {
142
+ super();
143
+ }
144
+ }
145
+ class ActionLinkFormatter extends AdvancedColumnFormatter {
146
+ render(data, type, row, meta) {
147
+ // get column
148
+ const column = meta.settings.aoColumns[meta.col];
149
+ if (column && column.data) {
150
+ this.actions = this.actions || [];
151
+ const allowedActions = this.actions.filter(x => {
152
+ if (x.access && x.access.length > 0) {
153
+ let access = x.access;
154
+ access = access.filter(y => {
155
+ if (y.studentStatuses) {
156
+ return y.studentStatuses.find(z => z.alternateName === row.studentStatus);
157
+ }
158
+ });
159
+ if (access && access.length > 0) {
160
+ return x;
161
+ }
162
+ }
163
+ else {
164
+ return x;
165
+ }
166
+ });
167
+ // return the dropdown menu with 3 dots
168
+ return new TemplatePipe().transform(`<div class='dropdown details-control' data-id='${data}' data-type='student' >
169
+ <a id="dropdownMenuButton-${data}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
170
+ <span class="fa-2x text-dots"></span></a>
171
+ <div class="dropdown-menu" aria-labelledby="dropdownMenuButton-${data}">
172
+ ${allowedActions.map(x => `<a data-name='${x.name}' class="dropdown-item" href="${x.href}">
173
+ ${this.injector.get(TranslateService).instant(new TemplatePipe().transform(x.title, ({ value: data })))}
174
+ </a>`)
175
+ .join('')}
176
+ </div>
177
+ </div>`, row);
178
+ }
179
+ return data;
180
+ }
181
+ constructor() {
182
+ super();
183
+ }
184
+ }
185
+ class SelectRowFormatter extends AdvancedColumnFormatter {
186
+ render(data, type, row, meta) {
187
+ // get column
188
+ const column = meta.settings.aoColumns[meta.col];
189
+ if (column && column.data) {
190
+ let _tpl = `<input class="checkbox checkbox-theme" type="checkbox"
191
+ id="s-${meta.row}" /><label for="s-${meta.row}">&nbsp;</label>`;
192
+ return new TemplatePipe().transform(_tpl, row);
193
+ }
194
+ return data;
195
+ }
196
+ constructor() {
197
+ super();
198
+ }
199
+ }
200
+ class TemplateFormatter extends AdvancedColumnFormatter {
201
+ render(data, type, row, meta) {
202
+ // get column
203
+ const column = meta.settings.aoColumns[meta.col];
204
+ if (column && column.data) {
205
+ return new TemplatePipe().transform(this.formatString, row);
206
+ }
207
+ return data;
208
+ }
209
+ constructor() {
210
+ super();
211
+ }
212
+ }
213
+ class TrueFalseFormatter extends AdvancedColumnFormatter {
214
+ render(data, type, row, meta) {
215
+ return data ? this.injector.get(TranslateService).instant('TrueFalse.long.Yes') :
216
+ this.injector.get(TranslateService).instant('TrueFalse.long.No');
217
+ }
218
+ constructor() {
219
+ super();
220
+ }
221
+ }
222
+ class TranslationFormatter extends AdvancedColumnFormatter {
223
+ render(data, type, row, meta) {
224
+ // get column
225
+ const column = meta.settings.aoColumns[meta.col];
226
+ if (column && data != null) {
227
+ return this.injector.get(TranslateService).instant(new TemplatePipe().transform(this.formatString, { value: data }));
228
+ }
229
+ return this.injector.get(TranslateService).instant('Unknown');
230
+ }
231
+ constructor() {
232
+ super();
233
+ }
234
+ }
235
+ class DateTimeFormatter extends AdvancedColumnFormatter {
236
+ render(data, type, row, meta) {
237
+ // get column
238
+ const column = meta.settings.aoColumns[meta.col];
239
+ if (column && column.data) {
240
+ const datePipe = new DatePipe(this.injector.get(TranslateService).currentLang);
241
+ if (typeof data === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}$/.test(data)) {
242
+ return datePipe.transform(new Date(data + ':00'), this.formatString);
243
+ }
244
+ return datePipe.transform(data, this.formatString);
245
+ }
246
+ return data;
247
+ }
248
+ constructor() {
249
+ super();
250
+ }
251
+ }
252
+ class NgClassFormatter extends AdvancedColumnFormatter {
253
+ render(data, type, row, meta) {
254
+ // get column
255
+ const column = meta.settings.aoColumns[meta.col];
256
+ if (column && column.data) {
257
+ // get formatOptions
258
+ if (this.formatOptions) {
259
+ const ngClass = this.formatOptions.ngClass;
260
+ // check condition for each key
261
+ for (const key of Object.keys(ngClass)) {
262
+ const result = new TemplatePipe().transform(ngClass[key], row);
263
+ const fn = new Function(`return ${result};`);
264
+ // if condition returns true add class
265
+ if (fn() === true) {
266
+ return `<span class="${key}">${data}</span>`;
267
+ }
268
+ }
269
+ }
270
+ return data;
271
+ }
272
+ return data;
273
+ }
274
+ constructor() {
275
+ super();
276
+ }
277
+ }
278
+ class LanguageFormatter extends AdvancedColumnFormatter {
279
+ render(data, type, row, meta) {
280
+ if (data) {
281
+ return getNativeName(data);
282
+ }
283
+ return data;
284
+ }
285
+ constructor() {
286
+ super();
287
+ }
288
+ }
289
+
290
+ class AdvancedFilterValueProvider {
291
+ constructor() {
292
+ this.values = {};
293
+ this._template = new TemplatePipe();
294
+ }
295
+ buildFilter(filter) {
296
+ if (filter) {
297
+ return this._template.transform(filter, this.values);
298
+ }
299
+ }
300
+ asyncBuildFilter(filter) {
301
+ return __awaiter(this, void 0, void 0, function* () {
302
+ if (filter) {
303
+ return this._template.transform(filter, yield this.getValues());
304
+ }
305
+ });
306
+ }
307
+ getValues() {
308
+ return __awaiter(this, void 0, void 0, function* () {
309
+ return this.values;
310
+ });
311
+ }
312
+ }
313
+ AdvancedFilterValueProvider.decorators = [
314
+ { type: Injectable, args: [{
315
+ providedIn: 'root'
316
+ },] }
317
+ ];
318
+ /** @nocollapse */
319
+ AdvancedFilterValueProvider.ctorParameters = () => [];
320
+ AdvancedFilterValueProvider.ngInjectableDef = defineInjectable({ factory: function AdvancedFilterValueProvider_Factory() { return new AdvancedFilterValueProvider(); }, token: AdvancedFilterValueProvider, providedIn: "root" });
321
+
322
+ // restore jquery import to avoid Cannot call a namespace ('jquery') error
323
+ const jQuery = jQuery_;
324
+ const DataTablesApi = jQuery.fn.dataTable.Api;
325
+ DataTablesApi.register('processing()', function (show) {
326
+ return this.iterator('table', function (ctx) {
327
+ ctx.oApi._fnProcessingDisplay(ctx, show);
328
+ });
329
+ });
330
+ class TableTranslationChangeDetector extends ChangeDetectorRef {
331
+ checkNoChanges() {
332
+ }
333
+ detach() {
334
+ }
335
+ detectChanges() {
336
+ }
337
+ markForCheck() {
338
+ }
339
+ reattach() {
340
+ }
341
+ }
342
+ let COLUMN_FORMATTERS = new InjectionToken('column.formatters');
343
+ class ColumnFormatter extends AdvancedColumnFormatter {
344
+ constructor(injector) {
345
+ super();
346
+ this.injector = injector;
347
+ }
348
+ render(data, type, row, meta) {
349
+ //
350
+ }
351
+ }
352
+ function forceCast(input) {
353
+ return input;
354
+ }
355
+ class AdvancedTableConfiguration {
356
+ static cast(input, clone) {
357
+ if (clone) {
358
+ return forceCast(JSON.parse(JSON.stringify(input)));
359
+ }
360
+ return forceCast(input);
361
+ }
362
+ }
363
+ function getRandomIdentifier() {
364
+ return (Number(String(Math.random()).slice(2))).toString(36);
365
+ }
366
+ class AdvancedTableComponent {
367
+ constructor(_context, _columnFormatters, _activatedRoute, _translateService, _injector, _advancedFilterValueProvider, _element, _template, _loading, _http) {
368
+ this._context = _context;
369
+ this._columnFormatters = _columnFormatters;
370
+ this._activatedRoute = _activatedRoute;
371
+ this._translateService = _translateService;
372
+ this._injector = _injector;
373
+ this._advancedFilterValueProvider = _advancedFilterValueProvider;
374
+ this._element = _element;
375
+ this._template = _template;
376
+ this._loading = _loading;
377
+ this._http = _http;
378
+ this._firstLoad = true;
379
+ this._showFooter = true;
380
+ this._showPaging = false;
381
+ this._id = getRandomIdentifier();
382
+ this.smartSelect = false;
383
+ this.configChanges = new BehaviorSubject(null);
384
+ this.height = 800;
385
+ this.showActions = true;
386
+ this.autoLoad = true;
387
+ this.load = new EventEmitter();
388
+ this.init = new EventEmitter();
389
+ this.reload = new EventEmitter();
390
+ this.select = new EventEmitter();
391
+ this.selectedItems = new EventEmitter();
392
+ this.scrollable = true;
393
+ this.serverSide = true;
394
+ this.lengthMenu = [50, 100, 200, 500];
395
+ this.paging = true;
396
+ this.pageLength = 25;
397
+ this.scrollY = 800;
398
+ this.scrollX = true;
399
+ /**
400
+ * Enables row selection. The defaulr value is false
401
+ */
402
+ this.selectable = false;
403
+ /**
404
+ * Enables or disable multiple selection. The default values is true.
405
+ */
406
+ this.multipleSelect = true;
407
+ /**
408
+ * Enables custom selection and hides select column. The default value is false
409
+ */
410
+ this.customSelect = false;
411
+ this.selected = [];
412
+ this.unselected = [];
413
+ // initialize translate pipe
414
+ this.translator = new TranslatePipe(this._translateService, new TableTranslationChangeDetector());
415
+ jQuery.fn.dataTable.ext.classes.sPageButton = 'btn';
416
+ jQuery.fn.dataTable.ext.classes.sPageButtonActive = 'btn btn-light';
417
+ this.reloadSubscription = this.reload.subscribe(data => {
418
+ this.fetch();
419
+ });
420
+ }
421
+ /**
422
+ * Gets or sets table configuration
423
+ */
424
+ set config(value) {
425
+ this._config = value;
426
+ // raise subscription
427
+ this.configChanges.next(this._config);
428
+ }
429
+ get config() {
430
+ return this._config;
431
+ }
432
+ /**
433
+ * Indicates whether table footer will be visible or not
434
+ */
435
+ set showFooter(value) {
436
+ this._showFooter = value;
437
+ // show or hide footer
438
+ if (this._element && this._element.nativeElement) {
439
+ const element = this._element.nativeElement
440
+ .querySelector('.dataTables_info');
441
+ if (element == null) {
442
+ return;
443
+ }
444
+ if (value) {
445
+ element.classList.remove('d-none');
446
+ }
447
+ else {
448
+ element.classList.add('d-none');
449
+ }
450
+ }
451
+ }
452
+ get showFooter() {
453
+ return this._showFooter;
454
+ }
455
+ /**
456
+ * Indicates whether paging information will be visible or not
457
+ */
458
+ set showPaging(value) {
459
+ this._showPaging = value;
460
+ // show or hide footer
461
+ if (this._element && this._element.nativeElement) {
462
+ const element = this._element.nativeElement
463
+ .querySelector('.dataTables_paginate');
464
+ if (element == null) {
465
+ return;
466
+ }
467
+ if (value) {
468
+ jQuery(element).show();
469
+ }
470
+ else {
471
+ jQuery(element).hide();
472
+ }
473
+ }
474
+ }
475
+ get showPaging() {
476
+ return this._showPaging;
477
+ }
478
+ /**
479
+ * Gets or sets an active query for this table
480
+ */
481
+ set query(value) {
482
+ this._query = value;
483
+ }
484
+ get query() {
485
+ return this._query;
486
+ }
487
+ // simple search
488
+ search(searchText) {
489
+ this.query = this.query || this._context.model(this.config.model).asQueryable();
490
+ if (searchText && this.config.searchExpression) {
491
+ // validate search text in double quotes
492
+ if (/^"(.*?)"$/.test(searchText)) {
493
+ this.query.setParam('$filter', this._template.transform(this.config.searchExpression, {
494
+ text: searchText.replace(/^"|"$/g, '')
495
+ }));
496
+ }
497
+ else {
498
+ // try to split words
499
+ const words = searchText.split(' ');
500
+ // join words to a single filter
501
+ const filter = words.map(word => {
502
+ return this._template.transform(this.config.searchExpression, {
503
+ text: word
504
+ });
505
+ }).join(' and ');
506
+ // set filter
507
+ this.query.setParam('$filter', filter);
508
+ }
509
+ }
510
+ else {
511
+ // clear filter
512
+ this.query.setParam('$filter', null);
513
+ }
514
+ this._firstLoad = false;
515
+ this.dataTable.draw();
516
+ }
517
+ fetch(clearSelected) {
518
+ // if data table is null
519
+ if (this.dataTable == null) {
520
+ // initialize data table
521
+ return this.ngOnInit();
522
+ }
523
+ // clear selection if parameter is not declared or it's true
524
+ if (clearSelected == null || clearSelected === true) {
525
+ // set smart select to false
526
+ this.smartSelect = false;
527
+ // clear selected items
528
+ if (this.selected.length) {
529
+ this.selected.splice(0, this.selected.length);
530
+ }
531
+ // clear unselected items
532
+ if (this.unselected.length) {
533
+ this.unselected.splice(0, this.unselected.length);
534
+ }
535
+ }
536
+ // otherwise load data only
537
+ this.dataTable.draw();
538
+ }
539
+ destroy() {
540
+ if (this.dataTable) {
541
+ this.dataTable.destroy();
542
+ this._firstLoad = true;
543
+ jQuery(this.table.nativeElement).empty();
544
+ this.dataTable = null;
545
+ // clear selected items
546
+ if (this.selected.length) {
547
+ this.selected.splice(0, this.selected.length);
548
+ }
549
+ // clear unselected items
550
+ if (this.unselected.length) {
551
+ this.unselected.splice(0, this.unselected.length);
552
+ }
553
+ // set smart select to false
554
+ this.smartSelect = false;
555
+ }
556
+ }
557
+ /**
558
+ * Selects the specified data row
559
+ */
560
+ selectRow(row, silent) {
561
+ if (row) {
562
+ // get row
563
+ const rowNode = row.node();
564
+ // if multiple select is false
565
+ if (this.multipleSelect === false) {
566
+ if (this.customSelect === false) {
567
+ // enumerate row
568
+ this.dataTable.rows().nodes().each((node) => {
569
+ if (node !== rowNode) {
570
+ jQuery(node).find(':checkbox').prop('checked', false);
571
+ }
572
+ });
573
+ }
574
+ else {
575
+ this.dataTable.rows('.selected').nodes().each((node) => {
576
+ if (node !== rowNode) {
577
+ jQuery(node).removeClass('selected');
578
+ }
579
+ });
580
+ }
581
+ // clear selected
582
+ this.selected.splice(0, this.selected.length);
583
+ }
584
+ // if custom select mode is off
585
+ if (this.customSelect === false) {
586
+ jQuery(rowNode).find(':checkbox').prop('checked', true);
587
+ }
588
+ else {
589
+ // otherwise add class
590
+ jQuery(row.node()).addClass('selected');
591
+ }
592
+ // get row data
593
+ const key = this.config.columns[0].property || this.config.columns[0].name;
594
+ const data = row.data();
595
+ // search selected items
596
+ let findIndex = this.selected.findIndex((x) => {
597
+ return x[key] === data[key];
598
+ });
599
+ if (findIndex >= 0) {
600
+ // remove previously selected row
601
+ this.selected.splice(findIndex, 1);
602
+ }
603
+ this.selected.push(data);
604
+ // validate unselected data
605
+ findIndex = this.unselected.findIndex((x) => {
606
+ return x[key] === data[key];
607
+ });
608
+ if (findIndex >= 0) {
609
+ // remove previously selected row
610
+ this.unselected.splice(findIndex, 1);
611
+ }
612
+ if (silent) {
613
+ return;
614
+ }
615
+ this.selectedItems.emit(this.selected);
616
+ }
617
+ }
618
+ /**
619
+ * Unselects the specified data row if it's selected
620
+ */
621
+ unSelectRow(row, silent) {
622
+ if (row) {
623
+ // if custom select mode is off
624
+ if (this.customSelect === false) {
625
+ jQuery(row.node()).find(':checkbox').prop('checked', false);
626
+ }
627
+ else {
628
+ // otherwise add class
629
+ jQuery(row.node()).removeClass('selected');
630
+ }
631
+ // get first column key
632
+ const key = this.config.columns[0].property || this.config.columns[0].name;
633
+ const data = row.data();
634
+ const id = data[key];
635
+ let findIndex = this.selected.findIndex(x => {
636
+ return x[key] === id;
637
+ });
638
+ if (findIndex >= 0) {
639
+ this.selected.splice(findIndex, 1);
640
+ if (this.smartSelect) {
641
+ // add this row to unselected rows
642
+ findIndex = this.unselected.findIndex(x => {
643
+ return x[key] === id;
644
+ });
645
+ if (findIndex < 0) {
646
+ this.unselected.push(data);
647
+ }
648
+ }
649
+ if (silent) {
650
+ return;
651
+ }
652
+ this.selectedItems.emit(this.selected);
653
+ }
654
+ }
655
+ }
656
+ selectAny() {
657
+ if (this.dataTable && this.selectable) {
658
+ // set smart select flag
659
+ this.smartSelect = true;
660
+ // select any row
661
+ this.dataTable.rows().every((index) => {
662
+ const row = this.dataTable.row(index);
663
+ this.selectRow(row, true);
664
+ });
665
+ // emit selection event
666
+ this.selectedItems.emit(this.selected);
667
+ }
668
+ }
669
+ selectNone() {
670
+ if (this.dataTable && this.selectable) {
671
+ // set smart select flag
672
+ this.smartSelect = false;
673
+ this.unselected.splice(0, this.unselected.length);
674
+ // select any row
675
+ this.dataTable.rows().every((index) => {
676
+ const row = this.dataTable.row(index);
677
+ this.unSelectRow(row, true);
678
+ });
679
+ this.selected.splice(0, this.selected.length);
680
+ // emit selection event
681
+ this.selectedItems.emit(this.selected);
682
+ }
683
+ }
684
+ toggleSelectRow(row) {
685
+ if (row) {
686
+ const data = row.data();
687
+ // get first column key
688
+ const key = this.config.columns[0].property || this.config.columns[0].name;
689
+ // get id
690
+ const id = data[key];
691
+ // find if row is selected
692
+ const findIndex = this.selected.find(x => {
693
+ return x[key] === id;
694
+ });
695
+ // if row is selected
696
+ if (findIndex >= 0) {
697
+ // unselect
698
+ this.unSelectRow(row);
699
+ }
700
+ else {
701
+ this.selectRow(row);
702
+ }
703
+ }
704
+ }
705
+ // noinspection JSMethodCanBeStatic
706
+ /**
707
+ * Datatables column renderer for select checkbox
708
+ * @param data
709
+ * @param type
710
+ * @param row
711
+ * @param meta
712
+ * @private
713
+ */
714
+ _renderSelect(data, type, row, meta) {
715
+ // get column
716
+ const column = meta.settings.aoColumns[meta.col];
717
+ // render template
718
+ return this._template.transform(`<input class="checkbox checkbox-theme s-row" type="checkbox"
719
+ id="s-${this._id}-${meta.row}" data-id="${data}" /><label for="s-${this._id}-${meta.row}">&nbsp;</label>`, row);
720
+ }
721
+ ngOnInit() {
722
+ const self = this;
723
+ if (this.dataTable) {
724
+ this.destroy();
725
+ }
726
+ if (this.config) {
727
+ // parse properties
728
+ if (Object.prototype.hasOwnProperty.call(this.config, 'selectable')) {
729
+ this.selectable = this.config.selectable;
730
+ }
731
+ if (Object.prototype.hasOwnProperty.call(this.config, 'multipleSelect')) {
732
+ this.multipleSelect = this.config.multipleSelect;
733
+ }
734
+ // prepare configuration for columns
735
+ const tableColumns = this.config.columns
736
+ // filter action link column
737
+ .filter(column => {
738
+ if (column.formatter && column.formatter === 'ActionLinkFormatter' && this.showActions === false) {
739
+ return false;
740
+ }
741
+ return true;
742
+ }).map(column => {
743
+ // convert column to advance table column
744
+ return Object.assign(new ColumnFormatter(this._injector), column);
745
+ }).map(column => {
746
+ const columnDefinition = {
747
+ data: column.property || column.name,
748
+ defaultContent: column.hasOwnProperty('defaultContent') ? column.defaultContent : '',
749
+ title: this.translator.transform(column.title),
750
+ sortable: column.hasOwnProperty('sortable') ? column.sortable : true,
751
+ visible: column.hasOwnProperty('hidden') ? !column.hidden : true,
752
+ name: column.name
753
+ };
754
+ if (column.className) {
755
+ Object.assign(columnDefinition, {
756
+ className: column.className
757
+ });
758
+ }
759
+ else {
760
+ Object.assign(columnDefinition, {
761
+ className: ''
762
+ });
763
+ }
764
+ if (column.formatters) {
765
+ Object.assign(columnDefinition, {
766
+ render: function (data, type, row, meta) {
767
+ let result = data;
768
+ column.formatters.forEach(columnFormatter => {
769
+ const formatter = self._columnFormatters[columnFormatter.formatter];
770
+ if (formatter && typeof formatter.render === 'function') {
771
+ column.formatString = columnFormatter.formatString;
772
+ column.formatOptions = columnFormatter.formatOptions;
773
+ }
774
+ result = formatter.render.bind(column)(result, type, row, meta);
775
+ });
776
+ delete column.formatString;
777
+ delete column.formatOptions;
778
+ return result;
779
+ }
780
+ });
781
+ }
782
+ if (column.formatter) {
783
+ const formatter = self._columnFormatters[column.formatter];
784
+ if (formatter && typeof formatter.render === 'function') {
785
+ Object.assign(columnDefinition, {
786
+ render: formatter.render.bind(column)
787
+ });
788
+ }
789
+ }
790
+ return columnDefinition;
791
+ });
792
+ // get table element
793
+ const tableElement = jQuery(this.table.nativeElement);
794
+ // allow selection
795
+ if (this.selectable) {
796
+ // get first column (as row primary key)
797
+ // todo::allow row primary key configuration
798
+ const firstColumn = this.config.columns[0];
799
+ // add select column
800
+ const selectColumnDefinition = {
801
+ className: 'text-center pl-4 pr-0',
802
+ data: firstColumn.property || firstColumn.name,
803
+ defaultContent: '',
804
+ title: '',
805
+ sortable: false,
806
+ visible: !this.customSelect,
807
+ name: firstColumn.name,
808
+ };
809
+ // assign renderer
810
+ Object.assign(selectColumnDefinition, {
811
+ render: this._renderSelect.bind(this)
812
+ });
813
+ // and finally add column
814
+ tableColumns.unshift(selectColumnDefinition);
815
+ // user interface additions
816
+ // handle page change event (a trick for firing draw.dt events)
817
+ tableElement
818
+ // for each page
819
+ .on('page.dt', ($event) => {
820
+ const info = this.dataTable.page.info();
821
+ })
822
+ // handle draw events
823
+ .on('draw.dt', () => {
824
+ if (this.customSelect) {
825
+ // enable row selection event
826
+ tableElement.find('tbody').on('click', 'tr', function () {
827
+ // select row
828
+ const row = self.dataTable.row(this);
829
+ // add row
830
+ self.select.emit(row);
831
+ });
832
+ }
833
+ tableElement.find('input.s-row').on('click', this.onSelectionChange.bind(this));
834
+ // make selection on current page
835
+ const key = this.config.columns[0].property;
836
+ if (this.selected.length > 0) {
837
+ // enumerate selected items
838
+ this.selected.forEach(item => {
839
+ // find row identifier
840
+ const id = item[key];
841
+ // if identifier is defined
842
+ if (id != null) {
843
+ // try to find check element with this identifier in attribute data-id
844
+ tableElement.find(`input[data-id='${id}']`).prop('checked', true);
845
+ }
846
+ });
847
+ }
848
+ if (this.smartSelect) {
849
+ // select rows that are not selected
850
+ // and do not included in unselected collection
851
+ tableElement.find('input[data-id]').each((index, element) => {
852
+ // get data id
853
+ if (this.unselected.findIndex((x) => {
854
+ // tslint:disable-next-line:triple-equals
855
+ return x[key] == jQuery(element).attr('data-id');
856
+ }) < 0) {
857
+ this.selectRow(this.dataTable.row(index));
858
+ }
859
+ });
860
+ }
861
+ else if (this.selected.length === 0) {
862
+ tableElement.find('input[data-id]').prop('checked', false);
863
+ }
864
+ });
865
+ }
866
+ // initialize data table
867
+ const settings = {
868
+ // set length menu
869
+ lengthMenu: [5, 10, 50, 100, 200, 500],
870
+ // hide length menu
871
+ lengthChange: false,
872
+ // enable search but hide search box (css)
873
+ searching: true,
874
+ // enable data processing
875
+ processing: true,
876
+ // enable getting server side data
877
+ serverSide: this.serverSide,
878
+ // enumerate table columns
879
+ columns: tableColumns,
880
+ order: [],
881
+ // set scroll x
882
+ scrollX: this.scrollX,
883
+ dom: 'Bfrtip',
884
+ autoWidth: false,
885
+ buttons: [
886
+ {
887
+ extend: 'excel',
888
+ filename: this.config.title,
889
+ className: 'd-none',
890
+ exportOptions: {
891
+ columns: 'th:not(:first-child)'
892
+ }
893
+ }
894
+ ],
895
+ // define server data callback
896
+ fnServerData: function (sSource, aoData, fnCallback, oSettings) {
897
+ // if method is invoked for the first time
898
+ if (self._firstLoad) {
899
+ // disabled first load flag
900
+ self._firstLoad = false;
901
+ // if auto load is disabled
902
+ if (self.autoLoad === false) {
903
+ const emptyDataSet = {
904
+ recordsTotal: 0,
905
+ recordsFiltered: 0,
906
+ data: []
907
+ };
908
+ // emit empty data
909
+ self.load.emit(emptyDataSet);
910
+ // return empty data
911
+ return fnCallback(emptyDataSet);
912
+ }
913
+ }
914
+ // get activated route params
915
+ const queryParams = self._activatedRoute.snapshot.queryParams;
916
+ // get columns
917
+ const columns = aoData[1].value;
918
+ // get order expression
919
+ const order = aoData[2].value;
920
+ // get skip records param
921
+ const skip = aoData[3].value;
922
+ // get page size param
923
+ const top = aoData[4].value;
924
+ // get search value
925
+ const searchText = aoData[5].value;
926
+ // get data queryable
927
+ const q = self._query instanceof ClientDataQueryable ? self._query : self._context.model(self.config.model).asQueryable();
928
+ // apply paging params
929
+ if (top) {
930
+ q.take(top).skip(skip);
931
+ }
932
+ // apply order params
933
+ if (order && order.length) {
934
+ // get order query expression
935
+ const orderByStr = order.map(expr => {
936
+ return tableColumns[expr.column].name + ' ' + expr.dir || 'asc';
937
+ }).join(',');
938
+ // set order
939
+ q.setParam('$orderby', orderByStr);
940
+ }
941
+ // configure $select parameter
942
+ const select = self.config.columns.filter(column => {
943
+ if (column.virtual === true) {
944
+ return false;
945
+ }
946
+ return true;
947
+ }).map(column => {
948
+ if (column.property) {
949
+ return column.name + ' as ' + column.property;
950
+ }
951
+ else {
952
+ return column.name;
953
+ }
954
+ });
955
+ q.select.apply(q, select);
956
+ // configure $filter
957
+ if (queryParams.$filter && queryParams.$filter.length) {
958
+ // set route filter
959
+ const qParams = q.getParams();
960
+ q.setParam('$filter', null);
961
+ const expressions = [queryParams.$filter];
962
+ if (qParams.$filter) {
963
+ expressions.push('(' + qParams.$filter + ')');
964
+ }
965
+ q.filter(expressions.join(' and '));
966
+ }
967
+ // check if query parameters contain filter
968
+ if (queryParams.$expand && queryParams.$expand.length) {
969
+ // set route $expand
970
+ q.setParam('$expand', queryParams.$expand);
971
+ }
972
+ if (!q.getParams()['$orderby'] && self.config && self.config.defaults && self.config.defaults.orderBy) {
973
+ q.setParam('$orderby', self.config.defaults.orderBy);
974
+ }
975
+ // append default filter
976
+ if (self.config && self.config.defaults && self.config.defaults.filter) {
977
+ return self._advancedFilterValueProvider.asyncBuildFilter(self.config.defaults.filter).then(defaultFilterString => {
978
+ if (defaultFilterString) {
979
+ const qParams = q.getParams();
980
+ q.setParam('$filter', null);
981
+ const expressions = [defaultFilterString];
982
+ if (qParams.$filter) {
983
+ expressions.push('(' + qParams.$filter + ')');
984
+ }
985
+ q.filter(expressions.join(' and '));
986
+ }
987
+ self.lastQueryParams = q.getParams();
988
+ q.getList().then(items => {
989
+ const dataSet = {
990
+ recordsTotal: items.total,
991
+ recordsFiltered: items.total,
992
+ data: items.value
993
+ };
994
+ if (items.total === 0) {
995
+ self.resetEmptyTable();
996
+ }
997
+ // emit load event
998
+ self.load.emit(dataSet);
999
+ // return data
1000
+ return fnCallback(dataSet);
1001
+ });
1002
+ }).catch((err) => {
1003
+ console.error('TABLES', err);
1004
+ });
1005
+ }
1006
+ self.lastQueryParams = q.getParams();
1007
+ q.getList().then(items => {
1008
+ const dataSet = {
1009
+ recordsTotal: items.total,
1010
+ recordsFiltered: items.total,
1011
+ data: items.value
1012
+ };
1013
+ if (items.total === 0) {
1014
+ self.resetEmptyTable();
1015
+ }
1016
+ // emit load event
1017
+ self.load.emit(dataSet);
1018
+ // return data
1019
+ return fnCallback(dataSet);
1020
+ }).catch((err) => {
1021
+ console.error('TABLES', err);
1022
+ });
1023
+ },
1024
+ language: Object.assign({}, self.translator.transform('Tables.DataTable'), { emptyTable: ' ', processing: ' ' })
1025
+ };
1026
+ if (this.scrollable) {
1027
+ // set continuous scrolling
1028
+ Object.assign(settings, {
1029
+ scrollY: this.scrollY,
1030
+ scrollCollapse: false,
1031
+ scroller: {
1032
+ loadingIndicator: false,
1033
+ displayBuffer: 10
1034
+ },
1035
+ });
1036
+ }
1037
+ else {
1038
+ Object.assign(settings, {
1039
+ paging: this.paging,
1040
+ pageLength: this.pageLength
1041
+ });
1042
+ }
1043
+ if (this.selectable) ;
1044
+ // handle init.dt event and emit local event
1045
+ this.dataTable = tableElement.DataTable(settings);
1046
+ tableElement.on('init.dt', () => {
1047
+ if (this.dataTable == null) {
1048
+ return;
1049
+ }
1050
+ // restore default empty table html
1051
+ // (due to ng async binding)
1052
+ this.init.emit(this.dataTable);
1053
+ });
1054
+ // show or hide footer
1055
+ if (!this.showFooter) {
1056
+ this._element.nativeElement
1057
+ .querySelector('.dataTables_info').classList.add('d-none');
1058
+ }
1059
+ // show or hide pager
1060
+ if (this.showPaging) {
1061
+ this.showPaging = true;
1062
+ }
1063
+ }
1064
+ }
1065
+ get lastQuery() {
1066
+ if (this.config == null) {
1067
+ return null;
1068
+ }
1069
+ if (this.lastQueryParams == null) {
1070
+ return null;
1071
+ }
1072
+ const q = this._context.model(this.config.model).asQueryable();
1073
+ Object.keys(this.lastQueryParams).forEach((key) => {
1074
+ q.setParam(key, this.lastQueryParams[key]);
1075
+ });
1076
+ // return query
1077
+ return q;
1078
+ }
1079
+ /**
1080
+ * Fetches and updates a single row
1081
+ * @param find - Any object which is being converted to a query
1082
+ */
1083
+ fetchOne(find) {
1084
+ if (find == null) {
1085
+ return;
1086
+ }
1087
+ if (this.dataTable == null) {
1088
+ return;
1089
+ }
1090
+ if (this.lastQueryParams == null) {
1091
+ return;
1092
+ }
1093
+ // get rows
1094
+ const rows = Array.from(this.dataTable.rows().data());
1095
+ if (rows && rows.length) {
1096
+ // find result
1097
+ const findKeys = Object.keys(find);
1098
+ if (findKeys.length === 0) {
1099
+ // do nothing
1100
+ return;
1101
+ }
1102
+ // find row index by searching each property
1103
+ const rowIndex = rows.findIndex((row) => {
1104
+ return findKeys.map((key) => {
1105
+ return row[key] === find[key];
1106
+ }).filter((res) => {
1107
+ return res === false;
1108
+ }).length === 0;
1109
+ });
1110
+ // if row has been found
1111
+ if (rowIndex >= 0) {
1112
+ const query = this._context.model(this.config.model).asQueryable();
1113
+ // select attributes
1114
+ query.setParam('$select', this.lastQueryParams.$select);
1115
+ // apply filter for row
1116
+ findKeys.forEach((key) => {
1117
+ const column = this.config.columns.find((col) => {
1118
+ return col.property === key || col.name === key;
1119
+ });
1120
+ if (column) {
1121
+ // get property or name
1122
+ const property = column.property || column.name;
1123
+ query.and(property).equal(find[key]);
1124
+ }
1125
+ else {
1126
+ query.and(key).equal(find[key]);
1127
+ }
1128
+ });
1129
+ // and finally get item
1130
+ return query.getItem().then((item) => {
1131
+ // if item exists
1132
+ const row = this.dataTable.row(rowIndex);
1133
+ const checked = jQuery(row.node()).find(':checkbox').prop('checked');
1134
+ if (item) {
1135
+ // redraw row
1136
+ row.data(item);
1137
+ jQuery(row.node()).find(':checkbox').on('click', this.onSelectionChange.bind(this));
1138
+ // if row is already selected
1139
+ if (checked) {
1140
+ this.selectRow(row, true);
1141
+ }
1142
+ }
1143
+ return Promise.resolve();
1144
+ });
1145
+ }
1146
+ }
1147
+ return Promise.resolve();
1148
+ }
1149
+ ngOnDestroy() {
1150
+ if (this.reloadSubscription) {
1151
+ this.reloadSubscription.unsubscribe();
1152
+ }
1153
+ }
1154
+ /**
1155
+ * Handle selection change
1156
+ * @param $event
1157
+ */
1158
+ onSelectionChange($event) {
1159
+ const input = $event.target;
1160
+ // get row from id
1161
+ const rowIndex = parseInt(/-(\d+)$/g.exec(input.id)[1], 10);
1162
+ // get row
1163
+ const row = this.dataTable.row(rowIndex).data();
1164
+ // get row identifier
1165
+ if (row) {
1166
+ const key = this.config.columns[0].property;
1167
+ const id = row[key];
1168
+ let findItemIndex = this.selected.findIndex(item => {
1169
+ return item[key] === id;
1170
+ });
1171
+ if (input.checked) {
1172
+ if (findItemIndex < 0) {
1173
+ // add row to selected rows
1174
+ this.selected.push(row);
1175
+ }
1176
+ }
1177
+ else {
1178
+ if (findItemIndex >= 0) {
1179
+ // remove selected item
1180
+ this.selected.splice(findItemIndex, 1);
1181
+ if (this.smartSelect) {
1182
+ // add row to unselected rows
1183
+ findItemIndex = this.unselected.findIndex(item => {
1184
+ return item[key] === id;
1185
+ });
1186
+ if (findItemIndex < 0) {
1187
+ this.unselected.push(row);
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ }
1194
+ resetEmptyTable() {
1195
+ // this operation is important
1196
+ // due to ng data binding
1197
+ // empty table template has no translated text during ngOnInit()
1198
+ if (this.dataTable && this.dataTable.context[0]) {
1199
+ this.dataTable.context[0].oLanguage.sEmptyTable = this.emptyTable.nativeElement.innerHTML;
1200
+ }
1201
+ }
1202
+ clearEmptyTable() {
1203
+ if (this.dataTable && this.dataTable.context[0]) {
1204
+ this.dataTable.context[0].oLanguage.sEmptyTable = '';
1205
+ }
1206
+ }
1207
+ export() {
1208
+ const element = document.querySelector('.dt-buttons .buttons-excel');
1209
+ element.click();
1210
+ }
1211
+ handleColumns(columnsToShow) {
1212
+ const table = this.dataTable;
1213
+ this.config.columns.slice(1).forEach((column, i) => {
1214
+ if (!column.hidden) {
1215
+ if (columnsToShow.includes(column.name)) {
1216
+ // noinspection TypeScriptValidateJSTypes
1217
+ table.column(column.name + ':name').visible(true);
1218
+ }
1219
+ else {
1220
+ // noinspection TypeScriptValidateJSTypes
1221
+ table.column(column.name + ':name').visible(false);
1222
+ }
1223
+ }
1224
+ });
1225
+ }
1226
+ getConfig() {
1227
+ return this.config;
1228
+ }
1229
+ ngOnChanges(changes) {
1230
+ if (Object.prototype.hasOwnProperty.call(changes, 'configSrc')) {
1231
+ if (changes.configSrc != null) {
1232
+ // load table configuration on change
1233
+ if (changes.configSrc.currentValue && (changes.configSrc.currentValue != changes.configSrc.previousValue)) {
1234
+ this._http.get(changes.configSrc.currentValue).toPromise().then((config) => {
1235
+ // set configuration
1236
+ this.config = config;
1237
+ // fetch and redraw
1238
+ this.fetch();
1239
+ }).catch((err) => {
1240
+ console.error('TABLES', 'An error occurred while loading table configuration.');
1241
+ console.error(err);
1242
+ });
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+ AdvancedTableComponent.decorators = [
1249
+ { type: Component, args: [{
1250
+ selector: 'app-advanced-table',
1251
+ template: `
1252
+ <table #table class="dataTable">
1253
+ </table>
1254
+ <div #emptyTable class="d-none">
1255
+ <div>
1256
+ <div class="mt-4">
1257
+ <div class="icon-circle bg-gray-300 border-gray-100"></div>
1258
+ </div>
1259
+ <div class="mt-4">
1260
+ <h4 class="font-3xl text-dark" [translate]="'Tables.EmptyTableTitle'"></h4>
1261
+ <p class="font-lg text-dark" [translate]="'Tables.EmptyTableMessage'"></p>
1262
+ </div>
1263
+ </div>
1264
+ </div>
1265
+ `,
1266
+ encapsulation: ViewEncapsulation.None,
1267
+ styles: ["table.dataTable thead .sorting{background-image:none}table.dataTable thead .sorting_asc{background-position:95% 60%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsSAAALEgHS3X78AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAEtJREFUKJFjYBgywAOKMQALFjE/BgaGTCibjYGBYRM+kz0ZGBh2MTAw8DAwMHAxMDBsY2BgCCBGMQzg1IRNMV5NuVAJXIALqmYwAgBBkAlPRCLcLwAAAABJRU5ErkJggg==)}table.dataTable thead .sorting_desc{background-position:95% 60%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAArzAAAK8wHvFRX1AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAFNJREFUKJHVzTEKgDAQRNGHZfBg2sSTeDMLKwXPYuVJbBKQQEyrH6bZ+cPyWWaElz4kR5cOJ1b0FXnBVRYjjmIUsGGqvX6OmnImYk+JLTkzpPyBGwrtCvfBe56nAAAAAElFTkSuQmCC)}table.dataTable thead th{color:#73818e;font-size:.75rem;font-weight:600;padding-top:16px;padding-bottom:4px;border-bottom:none;padding-left:8px}table.dataTable tbody tr:first-child td:first-child{border-radius:6px 0}table.dataTable tbody tr:first-child td:last-child{border-radius:0 6px}table.dataTable tbody tr{border-radius:6px}table.dataTable tbody tr td:first-child{border-left:1px solid #c2cfd6}table.dataTable tbody tr td:last-child{border-right:1px solid #c2cfd6}table.dataTable tbody tr td{padding-top:16px;padding-bottom:16px;border-top:1px solid #c2cfd6}table.dataTable tbody tr:last-child td:first-child{border-radius:0 0 0 6px}table.dataTable tbody tr:last-child td:last-child{border-radius:0 0 6px}table.dataTable tbody tr:last-child td{border-bottom:1px solid #c2cfd6}table.dataTable td.dataTables_empty{border:none!important}table.dataTable.no-footer{border-bottom:none}.dataTables_paginate .btn.previous{margin-right:8px}.dataTables_paginate .btn.next{margin-left:8px}.dataTables_info,.dataTables_paginate{margin-top:1rem}.dataTables_paginate span .btn{border-radius:1.5rem}.dataTables_filter{display:none}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:0;overflow:auto}.dataTables_paginate{display:none}.dataTables_wrapper .dataTables_processing{background:0 0;top:-40%;color:#73818e}.text-decoration-line-through{text-decoration:line-through!important}tr.removed .checkbox+label{visibility:hidden}"]
1268
+ }] }
1269
+ ];
1270
+ /** @nocollapse */
1271
+ AdvancedTableComponent.ctorParameters = () => [
1272
+ { type: AngularDataContext },
1273
+ { type: undefined, decorators: [{ type: Inject, args: [COLUMN_FORMATTERS,] }] },
1274
+ { type: ActivatedRoute },
1275
+ { type: TranslateService },
1276
+ { type: Injector },
1277
+ { type: AdvancedFilterValueProvider },
1278
+ { type: ElementRef },
1279
+ { type: TemplatePipe },
1280
+ { type: LoadingService },
1281
+ { type: HttpClient }
1282
+ ];
1283
+ AdvancedTableComponent.propDecorators = {
1284
+ table: [{ type: ViewChild, args: ['table',] }],
1285
+ emptyTable: [{ type: ViewChild, args: ['emptyTable',] }],
1286
+ loadingTable: [{ type: ViewChild, args: ['loadingTable',] }],
1287
+ height: [{ type: Input }],
1288
+ showActions: [{ type: Input }],
1289
+ autoLoad: [{ type: Input }],
1290
+ load: [{ type: Output, args: ['load',] }],
1291
+ init: [{ type: Output, args: ['init',] }],
1292
+ reload: [{ type: Input, args: ['reload',] }],
1293
+ select: [{ type: Output, args: ['select',] }],
1294
+ selectedItems: [{ type: Output, args: ['selectedItems',] }],
1295
+ scrollable: [{ type: Input }],
1296
+ serverSide: [{ type: Input }],
1297
+ lengthMenu: [{ type: Input }],
1298
+ paging: [{ type: Input }],
1299
+ pageLength: [{ type: Input }],
1300
+ scrollY: [{ type: Input }],
1301
+ scrollX: [{ type: Input }],
1302
+ selectable: [{ type: Input }],
1303
+ multipleSelect: [{ type: Input }],
1304
+ customSelect: [{ type: Input }],
1305
+ selected: [{ type: Input }],
1306
+ configSrc: [{ type: Input }],
1307
+ config: [{ type: Input, args: ['config',] }],
1308
+ showFooter: [{ type: Input, args: ['showFooter',] }],
1309
+ showPaging: [{ type: Input, args: ['showPaging',] }],
1310
+ query: [{ type: Input, args: ['query',] }]
1311
+ };
1312
+
1313
+ class AdvancedListComponent {
1314
+ constructor(_context, _translateService, _activatedRoute, _errorService, _toastService, _modalService, _userActivityService) {
1315
+ this._context = _context;
1316
+ this._translateService = _translateService;
1317
+ this._activatedRoute = _activatedRoute;
1318
+ this._errorService = _errorService;
1319
+ this._toastService = _toastService;
1320
+ this._modalService = _modalService;
1321
+ this._userActivityService = _userActivityService;
1322
+ this.allowSaveOption = true;
1323
+ }
1324
+ ngOnDestroy() {
1325
+ if (this.dataSubscription) {
1326
+ this.dataSubscription.unsubscribe();
1327
+ }
1328
+ if (this.reloadSubscription) {
1329
+ this.reloadSubscription.unsubscribe();
1330
+ }
1331
+ if (this.subscription) {
1332
+ this.subscription.unsubscribe();
1333
+ }
1334
+ }
1335
+ ngOnInit() {
1336
+ return __awaiter(this, void 0, void 0, function* () {
1337
+ try {
1338
+ // get metadata
1339
+ const metadata = yield this._context.getMetadata();
1340
+ // get route data
1341
+ this.dataSubscription = this._activatedRoute.data.subscribe(routeData => {
1342
+ // get entityType
1343
+ const re = new RegExp(`^${routeData.model}$`, 'ig');
1344
+ const entitySet = metadata.EntityContainer.EntitySet.find(x => {
1345
+ return re.test(x.Name);
1346
+ });
1347
+ // if entity type is null
1348
+ if (entitySet == null) {
1349
+ // navigate to error
1350
+ return this._errorService.navigateToError(new ResponseError('EntitySet Not Found', 404));
1351
+ }
1352
+ // get entity type
1353
+ const entityType = metadata.EntityType.find(x => {
1354
+ return x.Name === entitySet.EntityType;
1355
+ });
1356
+ if (entityType == null) {
1357
+ return this._errorService.navigateToError(new ResponseError('EntityType Not Found', 404));
1358
+ }
1359
+ if (routeData.searchConfiguration) {
1360
+ if (Object.prototype.hasOwnProperty.call(routeData.searchConfiguration, 'allowSave')) {
1361
+ this.allowSaveOption = routeData.searchConfiguration.allowSave;
1362
+ }
1363
+ this.searchConfiguration = routeData.searchConfiguration;
1364
+ }
1365
+ if (routeData.tableConfiguration) {
1366
+ this.tableConfiguration = routeData.tableConfiguration;
1367
+ }
1368
+ if (routeData.description) {
1369
+ this.description = this._translateService.instant(routeData.description);
1370
+ }
1371
+ if (routeData.longDescription) {
1372
+ this.longDescription = this._translateService.instant(routeData.longDescription);
1373
+ }
1374
+ if (routeData.category) {
1375
+ this.category = this._translateService.instant(routeData.category);
1376
+ }
1377
+ // clone default configuration
1378
+ const defaultConfiguration = AdvancedTableConfiguration.cast(this.tableConfiguration, true);
1379
+ // correct edit button link
1380
+ const column = defaultConfiguration.columns.find(col => {
1381
+ return col.formatter === 'ButtonFormatter' && col.formatString === '(modal:edit)';
1382
+ });
1383
+ if (column) {
1384
+ const url = [];
1385
+ this._activatedRoute.snapshot.pathFromRoot.forEach(route => {
1386
+ url.push.apply(url, route.url);
1387
+ });
1388
+ column.formatString = `#/${url.join('/')}/(modal:\$\{id\}/edit)`;
1389
+ }
1390
+ // create list configuration by assigning list entity set
1391
+ this.table.config = Object.assign({
1392
+ model: entitySet.Name
1393
+ }, defaultConfiguration);
1394
+ // fetch data
1395
+ this.table.fetch(true);
1396
+ // do reload by using hidden fragment e.g. /classes#reload
1397
+ this.reloadSubscription = this._activatedRoute.fragment.subscribe(fragment => {
1398
+ if (fragment && fragment === 'reload') {
1399
+ this._toastService.show(this.description, this._translateService.instant('Settings.OperationCompleted'));
1400
+ this.table.fetch(true);
1401
+ }
1402
+ });
1403
+ // finally add user activity
1404
+ this._userActivityService.setItem({
1405
+ category: this.category,
1406
+ description: this.description,
1407
+ url: window.location.hash.substring(1),
1408
+ dateCreated: new Date
1409
+ });
1410
+ });
1411
+ }
1412
+ catch (err) {
1413
+ console.error(err);
1414
+ return this._errorService.showError(err);
1415
+ }
1416
+ });
1417
+ }
1418
+ /**
1419
+ * Data load event handler of advanced table component
1420
+ * @param {AdvancedTableDataResult} data
1421
+ */
1422
+ onDataLoad(data) {
1423
+ this.recordsTotal = data.recordsTotal;
1424
+ }
1425
+ /**
1426
+ * Remove selected items
1427
+ */
1428
+ remove() {
1429
+ if (this.table && this.table.selected && this.table.selected.length) {
1430
+ // get items to remove
1431
+ const items = this.table.selected;
1432
+ return this._modalService.showWarningDialog(this._translateService.instant('Tables.RemoveItemsTitle'), this._translateService.instant('Tables.RemoveItemsMessage'), DIALOG_BUTTONS.OkCancel).then(result => {
1433
+ if (result === 'ok') {
1434
+ this._context.model(this.table.config.model).remove(items).then(() => {
1435
+ this._toastService.show(this.description + ':' + this._translateService.instant('Tables.RemoveItemMessage.title'), this._translateService.instant((items.length === 1 ?
1436
+ 'Tables.RemoveItemMessage.one' : 'Tables.RemoveItemMessage.many'), { value: items.length }));
1437
+ this.table.fetch(true);
1438
+ }).catch(err => {
1439
+ this._errorService.showError(err, {
1440
+ continueLink: '.'
1441
+ });
1442
+ });
1443
+ }
1444
+ });
1445
+ }
1446
+ }
1447
+ }
1448
+ AdvancedListComponent.decorators = [
1449
+ { type: Component, args: [{
1450
+ selector: 'app-advanced-list',
1451
+ template: "<div>\n <div class=\"row\" *ngIf=\"description\">\n <div class=\"col-auto\">\n <h2>{{description}}</h2>\n <p *ngIf=\"longDescription\">{{longDescription}}</p>\n </div>\n </div>\n <div class=\"row\" *ngIf=\"searchConfiguration\">\n <div class=\"col-12\" [ngVar]=\"{}\" #filter=\"ngVar\">\n <app-advanced-table-search #advancedSearch [(filter)]=\"filter.value\" [collapsed]=\"true\" [table]=\"table\" [allowSave]=\"allowSaveOption\">\n <advanced-search>\n <app-advanced-search-form [form]=\"searchConfiguration\" (filterChange)=\"advancedSearch.asyncAdvancedSearch()\" #search [(filter)]=\"filter.value\"></app-advanced-search-form>\n </advanced-search>\n </app-advanced-table-search>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-auto mt-4\">\n <div class=\"font-xl text-secondary\" *ngIf=\"recordsTotal > 1\">\n <span>{{recordsTotal}}</span><span>&nbsp;</span><span [translate]=\"'Tables.Items'\"></span>\n </div>\n <div class=\"font-xl text-secondary\" *ngIf=\"recordsTotal === 1\">\n <span>{{recordsTotal}}</span><span>&nbsp;</span><span [translate]=\"'Tables.Item'\"></span>\n </div>\n </div>\n <div class=\"pull-right ml-auto mr-3 mt-3\">\n <div class=\"font-lg btn-toolbar mt-3\">\n <a *ngIf=\"table && table.selected && table.selected.length\"\n class=\"text-danger text-decoration-none mr-2\" href=\"javascript:void(0)\" (click)=\"remove()\">\n <span>-</span><span>&nbsp;</span><span [translate]=\"'Tables.RemoveItem'\"></span>\n </a>\n <a class=\"text-indigo text-decoration-none\" [routerLink]=\"['.', { outlets:{ modal:'add'} }]\">\n <span>+</span><span>&nbsp;</span><span [translate]=\"'Tables.AddItem'\"></span>\n </a>\n </div>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-12 mb-4\">\n <app-advanced-table [selectable]=\"true\" #table\n [autoLoad]=false\n [showFooter]=\"false\"\n (load)=\"onDataLoad($event)\" [height]=\"600\"></app-advanced-table>\n </div>\n </div>\n <router-modal></router-modal>\n</div>\n"
1452
+ }] }
1453
+ ];
1454
+ /** @nocollapse */
1455
+ AdvancedListComponent.ctorParameters = () => [
1456
+ { type: AngularDataContext },
1457
+ { type: TranslateService },
1458
+ { type: ActivatedRoute },
1459
+ { type: ErrorService },
1460
+ { type: ToastService },
1461
+ { type: ModalService },
1462
+ { type: UserActivityService }
1463
+ ];
1464
+ AdvancedListComponent.propDecorators = {
1465
+ entitySet: [{ type: Input, args: ['entitySet',] }],
1466
+ category: [{ type: Input, args: ['category',] }],
1467
+ description: [{ type: Input, args: ['description',] }],
1468
+ longDescription: [{ type: Input, args: ['longDescription',] }],
1469
+ tableConfiguration: [{ type: Input, args: ['tableConfiguration',] }],
1470
+ searchConfiguration: [{ type: Input, args: ['searchConfiguration',] }],
1471
+ table: [{ type: ViewChild, args: ['table',] }],
1472
+ allowSaveOption: [{ type: Input, args: ['allowSaveOption',] }]
1473
+ };
1474
+
1475
+ class AdvancedRowActionComponent extends RouterModalOkCancel {
1476
+ constructor(router, activatedRoute, _translateService, _context, _errorService, _modalService, _appEvent) {
1477
+ super(router, activatedRoute);
1478
+ this._translateService = _translateService;
1479
+ this._context = _context;
1480
+ this._errorService = _errorService;
1481
+ this._modalService = _modalService;
1482
+ this._appEvent = _appEvent;
1483
+ this.modalIcon = 'fas fa-project-diagram';
1484
+ this.progressType = 'indigo';
1485
+ this.errorMessage = 'Tables.Actions.CompletedWithErrors';
1486
+ this.loading = false;
1487
+ this.refresh = new EventEmitter();
1488
+ // set modal size
1489
+ this.modalClass = 'modal-lg';
1490
+ // set modal title
1491
+ this.modalTitle = 'Tables.Actions.Label';
1492
+ // disable ok button
1493
+ this.okButtonDisabled = false;
1494
+ // set button text
1495
+ this.okButtonText = 'Tables.Actions.Start';
1496
+ }
1497
+ get form() {
1498
+ return this.formComponent;
1499
+ }
1500
+ ngOnInit() {
1501
+ this.refreshSubscription = this.refresh.subscribe((value) => {
1502
+ if (value && value.progress) {
1503
+ let progress = 0;
1504
+ if (value.progress < 0) {
1505
+ progress = 0;
1506
+ }
1507
+ else if (value.progress > 100) {
1508
+ progress = 100;
1509
+ }
1510
+ else {
1511
+ progress = value.progress;
1512
+ }
1513
+ this.progressComponent.value = progress;
1514
+ }
1515
+ });
1516
+ }
1517
+ ngAfterViewInit() {
1518
+ if (this.formComponent && this.formComponent.form) {
1519
+ this.okButtonDisabled = true;
1520
+ this.invalidSubscription = this.formComponent.form.change.subscribe((event) => {
1521
+ if (Object.prototype.hasOwnProperty.call(event, "isValid") && (event.changed != null)) {
1522
+ this.okButtonDisabled = !event.isValid;
1523
+ }
1524
+ });
1525
+ }
1526
+ }
1527
+ ngOnDestroy() {
1528
+ if (this.refreshSubscription) {
1529
+ this.refreshSubscription.unsubscribe();
1530
+ }
1531
+ if (this.executeSubscription) {
1532
+ this.executeSubscription.unsubscribe();
1533
+ }
1534
+ if (this.invalidSubscription) {
1535
+ this.invalidSubscription.unsubscribe();
1536
+ }
1537
+ }
1538
+ cancel() {
1539
+ if (this.loading) {
1540
+ return;
1541
+ }
1542
+ // close
1543
+ if (this._modalService.modalRef) {
1544
+ return this._modalService.modalRef.hide();
1545
+ }
1546
+ }
1547
+ ok() {
1548
+ return __awaiter(this, void 0, void 0, function* () {
1549
+ try {
1550
+ this.loading = true;
1551
+ this.lastError = null;
1552
+ if (this.formComponent && this.formComponent.form &&
1553
+ this.formComponent.form.formio && this.formComponent.form.formio.data) {
1554
+ // set data
1555
+ this.data = this.formComponent.form.formio.data;
1556
+ }
1557
+ // do action
1558
+ this.execute.subscribe((result) => {
1559
+ this.loading = false;
1560
+ this.refresh.emit({
1561
+ progress: 100
1562
+ });
1563
+ if (result && result.errors && result.errors > 0) {
1564
+ // show message for partial success
1565
+ if (result.errors === 1) {
1566
+ this.lastError = new Error(this._translateService.instant(`${this.errorMessage}.Description.One`));
1567
+ }
1568
+ else {
1569
+ this.lastError = new Error(this._translateService.instant(`${this.errorMessage}.Description.Many`, result));
1570
+ }
1571
+ this.refresh.emit({
1572
+ progress: 1
1573
+ });
1574
+ return;
1575
+ }
1576
+ if (this._modalService.modalRef) {
1577
+ this._modalService.modalRef.hide();
1578
+ }
1579
+ }, (err) => {
1580
+ this.loading = false;
1581
+ this.lastError = err;
1582
+ this.refresh.emit({
1583
+ progress: 0
1584
+ });
1585
+ });
1586
+ }
1587
+ catch (err) {
1588
+ this.loading = false;
1589
+ this.lastError = err;
1590
+ }
1591
+ });
1592
+ }
1593
+ }
1594
+ AdvancedRowActionComponent.decorators = [
1595
+ { type: Component, args: [{
1596
+ selector: 'app-advanced-row-action',
1597
+ template: "<div>\n <div class=\"modal-header\" *ngIf=\"modalTitle\">\n <h5 class=\"modal-title\">{{modalTitle | translate}}</h5>\n <button (click)=\"cancel()\" [disabled]=\"loading\"\n type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Cancel\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n <div class=\"modal-body pt-0 mt-2 pb-0\">\n <div class=\"row\" *ngIf=\"lastError\">\n <div class=\"col-12\">\n <div class=\"alert alert-warning\" role=\"alert\">\n <span>{{ 'Tables.Actions.CompletedWithErrors.Title' | translate }}</span><br>\n <small *ngIf=\"lastError.status >= 0\" [translate]=\"'E' + lastError.status + '.message'\"></small>\n <small *ngIf=\"lastError.status == null\">{{lastError.message}}</small>\n </div>\n </div>\n </div>\n <div class=\"row\" *ngIf=\"description\">\n <div class=\"col-12 mb-3\">\n <div class=\"text-justify\">\n {{description | translate}}\n </div>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-12\">\n <div *ngIf=\"items && items.length > 1\" [translate]=\"'Tables.Actions.ActionMessage.Many'\" [translateParams]=\"items\"></div>\n <div *ngIf=\"items && items.length === 1\" [translate]=\"'Tables.Actions.ActionMessage.One'\"></div>\n <div class=\"font-weight-bold text-danger\" *ngIf=\"items && items.length <= 0\" [translate]=\"'Tables.Actions.ActionMessage.None'\"></div>\n <div class=\"pt-2 font-weight-bold text-danger\" *ngIf=\"additionalMessage && items && items.length <= 0\">{{additionalMessage}}</div>\n </div>\n </div>\n <div class=\"row pt-3\" *ngIf=\"formTemplate\">\n <div class=\"col-12 mb-3\">\n <div>\n <advanced-form-edit #formComponent [formName]=\"formTemplate\" [(data)]=\"data\"></advanced-form-edit>\n </div>\n </div>\n </div>\n\n <div class=\"row mt-3\">\n <div class=\"col-12\">\n <progressbar #progress [type]=\"progressType\" [max]=\"100\">\n </progressbar>\n </div>\n </div>\n </div>\n\n <div class=\"modal-footer justify-content-end\">\n <button [disabled]=\"okButtonDisabled || loading || (items && items.length === 0)\" (click)=\"ok()\"\n type=\"button\" [ngClass]=\"okButtonClass\">\n {{okButtonText | translate}}\n </button>\n <button [disabled]=\"cancelButtonDisabled || loading\"\n (click)=\"cancel()\" type=\"button\" [ngClass]=\"cancelButtonClass\" data-dismiss=\"modal\">\n {{cancelButtonText | translate}}\n </button>\n </div>\n</div>\n",
1598
+ encapsulation: ViewEncapsulation.None,
1599
+ styles: [`
1600
+ .fa-1_5x {
1601
+ font-size: 1.5rem;
1602
+ }
1603
+ .form-control:disabled, .form-control[readonly] {
1604
+ background-color: inherit;
1605
+ }
1606
+ .form-check.checkbox {
1607
+ position: relative
1608
+ }
1609
+ `]
1610
+ }] }
1611
+ ];
1612
+ /** @nocollapse */
1613
+ AdvancedRowActionComponent.ctorParameters = () => [
1614
+ { type: Router },
1615
+ { type: ActivatedRoute },
1616
+ { type: TranslateService },
1617
+ { type: AngularDataContext },
1618
+ { type: ErrorService },
1619
+ { type: ModalService },
1620
+ { type: AppEventService }
1621
+ ];
1622
+ AdvancedRowActionComponent.propDecorators = {
1623
+ items: [{ type: Input }],
1624
+ modalIcon: [{ type: Input }],
1625
+ description: [{ type: Input }],
1626
+ formTemplate: [{ type: Input }],
1627
+ progressType: [{ type: Input }],
1628
+ data: [{ type: Output }],
1629
+ additionalMessage: [{ type: Input }],
1630
+ errorMessage: [{ type: Input }],
1631
+ progressComponent: [{ type: ViewChild, args: ['progress',] }],
1632
+ formComponent: [{ type: ViewChild, args: ['formComponent',] }],
1633
+ refresh: [{ type: Input }],
1634
+ execute: [{ type: Input }]
1635
+ };
1636
+
1637
+ class AdvancedTableSearchBaseComponent {
1638
+ constructor() {
1639
+ this.filterChange = new EventEmitter();
1640
+ }
1641
+ get filter() {
1642
+ return this._filter;
1643
+ }
1644
+ set filter(value) {
1645
+ this._filter = value;
1646
+ }
1647
+ onKeyEnter(event) {
1648
+ if (event.code === 'Enter') {
1649
+ this.filterChange.emit(this._filter);
1650
+ }
1651
+ }
1652
+ }
1653
+ AdvancedTableSearchBaseComponent.propDecorators = {
1654
+ filterChange: [{ type: Output }],
1655
+ filter: [{ type: Input }]
1656
+ };
1657
+
1658
+ class AdvancedSearchFormComponent extends AdvancedTableSearchBaseComponent {
1659
+ constructor(_context, _translateService, _activatedRoute, _http) {
1660
+ super();
1661
+ this._context = _context;
1662
+ this._translateService = _translateService;
1663
+ this._activatedRoute = _activatedRoute;
1664
+ this._http = _http;
1665
+ this.form = {};
1666
+ this.refreshForm = new EventEmitter();
1667
+ this.submission = { data: {} };
1668
+ this.loading = true;
1669
+ }
1670
+ ngOnChanges(changes) {
1671
+ if (Object.prototype.hasOwnProperty.call(changes, 'form')) {
1672
+ if (changes.form != null) {
1673
+ if (changes.form.currentValue &&
1674
+ changes.form.currentValue != changes.form.previousValue) {
1675
+ this.ngOnInit();
1676
+ }
1677
+ }
1678
+ }
1679
+ if (Object.prototype.hasOwnProperty.call(changes, 'formSrc')) {
1680
+ if (changes.formSrc != null) {
1681
+ if (changes.formSrc.currentValue &&
1682
+ changes.formSrc.currentValue != changes.formSrc.previousValue) {
1683
+ this._http.get(changes.formSrc.currentValue).toPromise().then((form) => {
1684
+ // set form
1685
+ this.form = form;
1686
+ // and init
1687
+ this.ngOnInit();
1688
+ }).catch((err) => {
1689
+ console.error('An error occurred while loading search configuration');
1690
+ console.error(err);
1691
+ });
1692
+ }
1693
+ }
1694
+ }
1695
+ if (Object.prototype.hasOwnProperty.call(changes, 'filter')) {
1696
+ if (changes.filter != null) {
1697
+ this.submission.data = changes.filter.currentValue || {};
1698
+ this.refreshForm.emit({
1699
+ submission: this.submission
1700
+ });
1701
+ }
1702
+ }
1703
+ }
1704
+ ngOnInit() {
1705
+ if (this.form) {
1706
+ new ServiceUrlPreProcessor(this._context).parse(this.form);
1707
+ new TranslatePreProcessor(this._translateService).parse(this.form);
1708
+ this.submission = {
1709
+ data: this.filter
1710
+ };
1711
+ this.refreshForm.emit({
1712
+ form: this.form
1713
+ });
1714
+ this.loading = false;
1715
+ this.filterSubscription = this.filterChange.subscribe(filter => {
1716
+ this.submission.data = filter;
1717
+ this.refreshForm.emit({
1718
+ submission: this.submission
1719
+ });
1720
+ });
1721
+ }
1722
+ }
1723
+ onChange(event) {
1724
+ Object.keys(this.formComponent.submission.data).forEach(key => {
1725
+ if (Object.prototype.hasOwnProperty.call(this.formComponent.submission.data, key)) {
1726
+ const value = this.formComponent.submission.data[key];
1727
+ // the filter with type is a string and is not empty
1728
+ const isEmptyString = typeof value === 'string' && value.length === 0;
1729
+ if (!isEmptyString) {
1730
+ Object.defineProperty(this.filter, key, {
1731
+ configurable: true,
1732
+ enumerable: true,
1733
+ value: value
1734
+ });
1735
+ }
1736
+ else {
1737
+ delete this.filter[key];
1738
+ }
1739
+ }
1740
+ });
1741
+ }
1742
+ onFormLoad(form) {
1743
+ const currentLang = this._translateService.currentLang;
1744
+ this.formComponent.formio.i18next.options.resources[currentLang] = { translation: this._translateService.instant(`Forms`) };
1745
+ this.formComponent.formio.language = currentLang;
1746
+ }
1747
+ ngAfterViewInit() {
1748
+ //
1749
+ }
1750
+ ngOnDestroy() {
1751
+ if (this.filterSubscription) {
1752
+ this.filterSubscription.unsubscribe();
1753
+ }
1754
+ }
1755
+ }
1756
+ AdvancedSearchFormComponent.decorators = [
1757
+ { type: Component, args: [{
1758
+ selector: 'app-advanced-search-form',
1759
+ template: `
1760
+ <div class="p-4">
1761
+ <formio #formioComponent class="formio-search" (keydown)="onKeyEnter($event)" (change)="onChange($event)" (formLoad)="onFormLoad($event)"
1762
+ [refresh]="refreshForm" [submission]="submission" [form]="form"></formio>
1763
+ </div>
1764
+ `,
1765
+ encapsulation: ViewEncapsulation.None,
1766
+ styles: ["formio.formio-search .form-group{margin-bottom:0}formio.formio-search .form-group .col-form-label{font-size:90%;padding-top:4px;padding-bottom:4px}.choices__list.choices__list--single .choices__item:first-child.choices__item--selectable{width:85%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}"]
1767
+ }] }
1768
+ ];
1769
+ /** @nocollapse */
1770
+ AdvancedSearchFormComponent.ctorParameters = () => [
1771
+ { type: AngularDataContext },
1772
+ { type: TranslateService },
1773
+ { type: ActivatedRoute },
1774
+ { type: HttpClient }
1775
+ ];
1776
+ AdvancedSearchFormComponent.propDecorators = {
1777
+ formComponent: [{ type: ViewChild, args: ['formioComponent',] }],
1778
+ form: [{ type: Input }],
1779
+ formSrc: [{ type: Input }],
1780
+ refreshForm: [{ type: Output }]
1781
+ };
1782
+
1783
+ class AdvancedTableConfigurationResolver {
1784
+ constructor(_http) {
1785
+ this._http = _http;
1786
+ }
1787
+ resolve(route, state) {
1788
+ // get list
1789
+ const list = route.data.list || route.params.list;
1790
+ // get model
1791
+ const model = route.data.model || route.params.model;
1792
+ // get configuration
1793
+ return this._http.get(`assets/lists/${model}/${list}.json`).toPromise();
1794
+ }
1795
+ }
1796
+ AdvancedTableConfigurationResolver.decorators = [
1797
+ { type: Injectable }
1798
+ ];
1799
+ /** @nocollapse */
1800
+ AdvancedTableConfigurationResolver.ctorParameters = () => [
1801
+ { type: HttpClient }
1802
+ ];
1803
+ class AdvancedSearchConfigurationResolver {
1804
+ constructor(_http) {
1805
+ this._http = _http;
1806
+ }
1807
+ resolve(route, state) {
1808
+ // get list
1809
+ const list = route.data.list || route.params.list;
1810
+ // get model
1811
+ const model = route.data.model || route.params.model;
1812
+ // get configuration
1813
+ return this._http.get(`assets/lists/${model}/search.${list}.json`).toPromise();
1814
+ }
1815
+ }
1816
+ AdvancedSearchConfigurationResolver.decorators = [
1817
+ { type: Injectable }
1818
+ ];
1819
+ /** @nocollapse */
1820
+ AdvancedSearchConfigurationResolver.ctorParameters = () => [
1821
+ { type: HttpClient }
1822
+ ];
1823
+
1824
+ class AdvancedTableSearchComponent extends AdvancedTableSearchBaseComponent {
1825
+ constructor(_context, _activatedRoute, datePipe, _modalService, _userStorage, _translate, _advancedSearchValueProvider, _template, _router, _modal) {
1826
+ super();
1827
+ this._context = _context;
1828
+ this._activatedRoute = _activatedRoute;
1829
+ this.datePipe = datePipe;
1830
+ this._modalService = _modalService;
1831
+ this._userStorage = _userStorage;
1832
+ this._translate = _translate;
1833
+ this._advancedSearchValueProvider = _advancedSearchValueProvider;
1834
+ this._template = _template;
1835
+ this._router = _router;
1836
+ this._modal = _modal;
1837
+ this.collapsed = true;
1838
+ this.allowSave = true;
1839
+ this.showMore = true;
1840
+ this.mergeQueryParams = true;
1841
+ this.viewChange = new EventEmitter();
1842
+ this.userSearchList = [];
1843
+ this.selectedList = null;
1844
+ }
1845
+ set text(value) {
1846
+ if (this.searchText) {
1847
+ this.searchText.nativeElement.value = value;
1848
+ }
1849
+ }
1850
+ get text() {
1851
+ if (this.searchText) {
1852
+ return this.searchText.nativeElement.value;
1853
+ }
1854
+ }
1855
+ ngOnInit() {
1856
+ this.configChangesSubscription = this.table.configChanges.subscribe((config) => {
1857
+ if (config == null) {
1858
+ return;
1859
+ }
1860
+ // load user searches
1861
+ this.loadUserSearchList().then(() => {
1862
+ // if search component merges query params
1863
+ // (uses queryParams to apply filter)
1864
+ if (this.mergeQueryParams) {
1865
+ this.queryParamSubscription = this._activatedRoute.queryParams.subscribe((queryParams) => {
1866
+ this.applyQueryParams(queryParams);
1867
+ });
1868
+ }
1869
+ });
1870
+ });
1871
+ }
1872
+ /**
1873
+ * Applies query given by the queryParams of the activated route
1874
+ * @param queryParams
1875
+ */
1876
+ applyQueryParams(queryParams) {
1877
+ this.filter = {};
1878
+ if (Object.prototype.hasOwnProperty.call(queryParams, 'q')) {
1879
+ // set collapsed
1880
+ this.collapsed = true;
1881
+ // search text
1882
+ this.text = queryParams.q;
1883
+ return this.table.search(this.text);
1884
+ }
1885
+ else {
1886
+ this.text = '';
1887
+ }
1888
+ let filterParams = Object.assign({}, queryParams);
1889
+ if (Object.prototype.hasOwnProperty.call(queryParams, 's')) {
1890
+ // set collapsed
1891
+ this.collapsed = false;
1892
+ // find user search by name
1893
+ if (this.userSearchList) {
1894
+ this.selectedList = this.userSearchList.find((item) => {
1895
+ return item.name === queryParams.s;
1896
+ });
1897
+ if (this.selectedList) {
1898
+ filterParams = this.selectedList.filter;
1899
+ }
1900
+ }
1901
+ }
1902
+ else {
1903
+ this.selectedList = null;
1904
+ }
1905
+ Object.keys(filterParams).filter((key) => {
1906
+ // search criteria
1907
+ if (this.table && this.table.config && this.table.config.criteria) {
1908
+ return this.table.config.criteria.find((item) => {
1909
+ return item.name === key;
1910
+ });
1911
+ }
1912
+ }).forEach((key) => {
1913
+ let value = filterParams[key];
1914
+ if (TextUtils.isNumber(value)) {
1915
+ value = parseFloat(filterParams[key]);
1916
+ }
1917
+ if (this.filter[key] !== value) {
1918
+ // set value
1919
+ Object.defineProperty(this.filter, key, {
1920
+ configurable: true,
1921
+ enumerable: true,
1922
+ writable: true,
1923
+ value: value
1924
+ });
1925
+ }
1926
+ if (this.filter[key] != null) {
1927
+ this.collapsed = false;
1928
+ }
1929
+ });
1930
+ // call filterChange event to apply change to advanced search children
1931
+ this.filterChange.emit(this.filter);
1932
+ // apply query
1933
+ this.getQuery().then((query) => {
1934
+ // if table query is null
1935
+ if (this.table.query == null) {
1936
+ // set query
1937
+ this.table.query = query;
1938
+ }
1939
+ else {
1940
+ if (query != null) {
1941
+ const params = query.getParams();
1942
+ // otherwise set filter params
1943
+ this.table.query.setParam('$filter', params.$filter);
1944
+ }
1945
+ else {
1946
+ this.table.query.setParam('$filter', null);
1947
+ }
1948
+ }
1949
+ // and finally fetch data
1950
+ this.table.fetch(true);
1951
+ });
1952
+ }
1953
+ ngOnDestroy() {
1954
+ if (this.queryParamSubscription) {
1955
+ this.queryParamSubscription.unsubscribe();
1956
+ }
1957
+ if (this.configChangesSubscription) {
1958
+ this.configChangesSubscription.unsubscribe();
1959
+ }
1960
+ }
1961
+ ngAfterViewInit() {
1962
+ }
1963
+ onSearchKeyDown(event) {
1964
+ if (this.table && event.keyCode === 13) {
1965
+ // get search text
1966
+ const searchText = event.target.value;
1967
+ // merge query params ?
1968
+ if (this.mergeQueryParams) {
1969
+ const queryParams = {
1970
+ q: searchText
1971
+ };
1972
+ if (this.table.config && this.table.config.criteria) {
1973
+ this.table.config.criteria.forEach((item) => {
1974
+ Object.defineProperty(queryParams, item.name, {
1975
+ configurable: true,
1976
+ enumerable: true,
1977
+ writable: true,
1978
+ value: null
1979
+ });
1980
+ });
1981
+ }
1982
+ // try to apply filter by appending query params
1983
+ return this._router.navigate([], {
1984
+ relativeTo: this._activatedRoute,
1985
+ queryParams: queryParams,
1986
+ queryParamsHandling: 'merge'
1987
+ });
1988
+ }
1989
+ // otherwise do search
1990
+ this.table.search(searchText);
1991
+ return false;
1992
+ }
1993
+ }
1994
+ transformDate(date) {
1995
+ date = this.datePipe.transform(date, 'MM/dd/yyyy'); // whatever format you need.
1996
+ return date;
1997
+ }
1998
+ convertToString(nameFilter) {
1999
+ let name = '';
2000
+ if (nameFilter != null) {
2001
+ name = nameFilter.toString();
2002
+ }
2003
+ return name;
2004
+ }
2005
+ // Load saved criteria searches user has stored before
2006
+ loadUserSearchList() {
2007
+ this.storageKey = 'tables/' + this.table.config.model + '/searches';
2008
+ return this._userStorage.getItem(this.storageKey).then((data) => {
2009
+ const list = data.value || [];
2010
+ // if table configuration has pre-defined searches
2011
+ if (this.table && this.table.config.searches && this.table.config.searches.length) {
2012
+ // add pre-defined lists
2013
+ list.push.apply(list, this.table.config.searches.map(x => {
2014
+ x.readOnly = true; // as readonly
2015
+ x.name = this._translate.instant(x.name);
2016
+ return x;
2017
+ }));
2018
+ }
2019
+ // set user search list
2020
+ this.userSearchList = list;
2021
+ return Promise.resolve();
2022
+ }).catch((err) => {
2023
+ // DO NOT THROW ERROR
2024
+ // todo: notify component for this error in order to disable some actions
2025
+ console.error('An error occurred while getting user search list');
2026
+ console.error(err);
2027
+ return Promise.resolve();
2028
+ });
2029
+ }
2030
+ onSelectUserSearch(event) {
2031
+ if (this.selectedList) {
2032
+ const listFound = this.searchInListByName(this.selectedList.name);
2033
+ if (listFound) {
2034
+ if (this.mergeQueryParams) {
2035
+ const queryParams = { q: null, s: this.selectedList.name };
2036
+ // clear criteria if any
2037
+ this.table.config.criteria.forEach((item) => {
2038
+ if (Object.prototype.hasOwnProperty.call(queryParams, item.name) === false) {
2039
+ // set null value
2040
+ Object.defineProperty(queryParams, item.name, {
2041
+ configurable: true,
2042
+ enumerable: true,
2043
+ writable: true,
2044
+ value: null
2045
+ });
2046
+ }
2047
+ });
2048
+ return this._router.navigate([], {
2049
+ relativeTo: this._activatedRoute,
2050
+ queryParams: queryParams,
2051
+ queryParamsHandling: 'merge'
2052
+ });
2053
+ }
2054
+ this.selectedList = listFound;
2055
+ this.filter = this.selectedList.filter;
2056
+ this.filterChange.emit(this.filter);
2057
+ return true;
2058
+ }
2059
+ }
2060
+ else {
2061
+ return this._router.navigate([], {
2062
+ relativeTo: this._activatedRoute,
2063
+ queryParams: { s: null },
2064
+ queryParamsHandling: 'merge'
2065
+ });
2066
+ }
2067
+ return false;
2068
+ }
2069
+ searchInListByName(name) {
2070
+ if (Array.isArray(this.userSearchList)) {
2071
+ const listFound = this.userSearchList.find(x => x.name === name);
2072
+ if (listFound) {
2073
+ return listFound;
2074
+ }
2075
+ else {
2076
+ return false;
2077
+ }
2078
+ }
2079
+ }
2080
+ // Update list item contents with given content (Search by object name property)
2081
+ updateSavedSearchListItemByName(name, newItem) {
2082
+ const itemInList = this.userSearchList.find(x => x.name === name);
2083
+ const index = this.userSearchList.indexOf(itemInList);
2084
+ this.userSearchList[index] = newItem;
2085
+ this.selectedList = this.userSearchList[index];
2086
+ }
2087
+ // Update list item contents with given content (Search by object)
2088
+ updateSavedSearchListItemByObject(itemInList, newItem) {
2089
+ const index = this.userSearchList.indexOf(itemInList);
2090
+ this.userSearchList[index] = newItem;
2091
+ this.selectedList = this.userSearchList[index];
2092
+ }
2093
+ // Delete list item from list
2094
+ deleteItemFromSavedSearchListByObject(itemInList) {
2095
+ this.userSearchList = this.userSearchList.filter(item => item !== itemInList);
2096
+ this.selectedList = null;
2097
+ this.filter = {};
2098
+ this.advancedSearch(true);
2099
+ this.uploadListOnUserStorage();
2100
+ }
2101
+ // User chose to save its advanced form search criteria
2102
+ saveSearch(templateToLoad, newList = true) {
2103
+ if (this.table.config.model) {
2104
+ // Set the corresponding key to its list and show a confirmation modal window
2105
+ this.storageKey = `tables/${this.table.config.model}/searches`;
2106
+ if (this.filter && Object.keys(this.filter).length) {
2107
+ // User wants to save an existing list from the dropdown
2108
+ if (!newList && this.selectedList) {
2109
+ const newItem = { 'name': this.selectedList.name, 'filter': Object.assign({}, this.filter) };
2110
+ this.updateSavedSearchListItemByObject(this.selectedList, newItem);
2111
+ this.uploadListOnUserStorage();
2112
+ // User wants to save a new list
2113
+ }
2114
+ else {
2115
+ this.modalRef = this._modalService.show(templateToLoad);
2116
+ }
2117
+ }
2118
+ else {
2119
+ // User did not define any criteria
2120
+ this._modal.showWarningDialog(this._translate.instant('AdvancedSearch.Modal.Title'), this._translate.instant('AdvancedSearch.Modal.EmptyCriteriaList'), DIALOG_BUTTONS.Ok);
2121
+ }
2122
+ }
2123
+ else {
2124
+ this._modal.showWarningDialog(this._translate.instant('AdvancedSearch.Modal.Title'), this._translate.instant('AdvancedSearch.Modal.NoListDefined'), DIALOG_BUTTONS.Ok);
2125
+ }
2126
+ }
2127
+ // User Accepted => Save list and Close modal
2128
+ confirmModal(saveFormName) {
2129
+ // name given and list name is unique
2130
+ if (saveFormName && !(this.searchInListByName(saveFormName))) {
2131
+ const newInsertedValue = { 'name': saveFormName, 'filter': Object.assign({}, this.filter) };
2132
+ if (this.userSearchList && this.userSearchList.length !== 0) {
2133
+ this.userSearchList.push(newInsertedValue);
2134
+ this.storageValue = this.userSearchList;
2135
+ this.selectedList = newInsertedValue;
2136
+ }
2137
+ else {
2138
+ this.storageValue = [newInsertedValue];
2139
+ this.userSearchList = this.storageValue;
2140
+ this.selectedList = newInsertedValue;
2141
+ }
2142
+ this.uploadListOnUserStorage();
2143
+ this.modalRef.hide();
2144
+ }
2145
+ else if (saveFormName && (this.searchInListByName(saveFormName))) {
2146
+ // User should insert a different name cause this one is already in use
2147
+ alert(this._translate.instant('AdvancedSearch.Modal.TitleOnListAlreadyCreated'));
2148
+ }
2149
+ else {
2150
+ // User did not insert any text
2151
+ alert(this._translate.instant('AdvancedSearch.Modal.TitleOnUserError'));
2152
+ }
2153
+ }
2154
+ // User declined => Close modal
2155
+ closeModal() {
2156
+ this.modalRef.hide();
2157
+ }
2158
+ // Upload user's criteria on User Storage
2159
+ uploadListOnUserStorage() {
2160
+ this._userStorage.setItem(this.storageKey, this.userSearchList.filter(x => {
2161
+ return !x.readOnly;
2162
+ }));
2163
+ }
2164
+ /**
2165
+ * @deprecated This method is going to be deprecated. Use search() method instead.
2166
+ * @param {*} event
2167
+ */
2168
+ asyncAdvancedSearch(event) {
2169
+ return __awaiter(this, void 0, void 0, function* () {
2170
+ if (this.table && this.table.config && Array.isArray(this.table.config.criteria)) {
2171
+ const query = yield this.getQuery();
2172
+ if (this.mergeQueryParams) {
2173
+ this.selectedList = null;
2174
+ const queryParams = Object.assign({ q: null, s: null }, this.filter);
2175
+ // enumerate criteria
2176
+ this.table.config.criteria.forEach((item) => {
2177
+ if (Object.prototype.hasOwnProperty.call(queryParams, item.name) === false) {
2178
+ // set null value
2179
+ Object.defineProperty(queryParams, item.name, {
2180
+ configurable: true,
2181
+ enumerable: true,
2182
+ writable: true,
2183
+ value: null
2184
+ });
2185
+ }
2186
+ });
2187
+ return this._router.navigate([], {
2188
+ relativeTo: this._activatedRoute,
2189
+ queryParams: queryParams,
2190
+ queryParamsHandling: 'merge'
2191
+ });
2192
+ }
2193
+ if (this.table.query) {
2194
+ this.table.query.setParam('$filter', query.getParams()['$filter']);
2195
+ }
2196
+ else {
2197
+ this.table.query = query;
2198
+ }
2199
+ this.table.fetch();
2200
+ }
2201
+ });
2202
+ }
2203
+ search() {
2204
+ return this.asyncAdvancedSearch().then(() => {
2205
+ // do nothing
2206
+ });
2207
+ }
2208
+ getQuery() {
2209
+ return __awaiter(this, void 0, void 0, function* () {
2210
+ // on simple search return null
2211
+ if (this.collapsed) {
2212
+ return null;
2213
+ }
2214
+ if (this.table && this.table.config && Array.isArray(this.table.config.criteria)) {
2215
+ const expressions = [];
2216
+ const values = yield this._advancedSearchValueProvider.getValues();
2217
+ this.table.config.criteria.forEach((x) => {
2218
+ if (Object.prototype.hasOwnProperty.call(this.filter, x.name)) {
2219
+ const nameFilter = this.convertToString(this.filter[x.name]);
2220
+ if (nameFilter.includes('GMT')) {
2221
+ const newDate = this.transformDate(this.filter[x.name]);
2222
+ expressions.push(this._template.transform(x.filter, {
2223
+ value: newDate
2224
+ }));
2225
+ }
2226
+ else if (this.filter[x.name] !== 'undefined') {
2227
+ expressions.push(this._template.transform(x.filter, Object.assign({
2228
+ value: this.filter[x.name]
2229
+ }, values)));
2230
+ }
2231
+ }
2232
+ });
2233
+ let query;
2234
+ if (expressions || expressions.length) {
2235
+ // create client query
2236
+ query = this._context.model(this.table.config.model).asQueryable();
2237
+ query.setParam('filter', expressions.join(' and '));
2238
+ }
2239
+ else {
2240
+ query = this._context.model(this.table.config.model).asQueryable();
2241
+ }
2242
+ return query;
2243
+ }
2244
+ });
2245
+ }
2246
+ advancedSearch(event) {
2247
+ return this.asyncAdvancedSearch(event);
2248
+ }
2249
+ clear() {
2250
+ if (this.mergeQueryParams) {
2251
+ this.selectedList = null;
2252
+ const queryParams = { q: null, s: null };
2253
+ // enumerate criteria
2254
+ this.table.config.criteria.forEach((item) => {
2255
+ if (Object.prototype.hasOwnProperty.call(queryParams, item.name) === false) {
2256
+ // set null value
2257
+ Object.defineProperty(queryParams, item.name, {
2258
+ configurable: true,
2259
+ enumerable: true,
2260
+ writable: true,
2261
+ value: null
2262
+ });
2263
+ }
2264
+ });
2265
+ return this._router.navigate([], {
2266
+ relativeTo: this._activatedRoute,
2267
+ queryParams: queryParams,
2268
+ queryParamsHandling: 'merge'
2269
+ });
2270
+ }
2271
+ else {
2272
+ // clear filter
2273
+ this.filter = {};
2274
+ // clear list
2275
+ this.selectedList = null;
2276
+ // raise filter change event
2277
+ this.filterChange.emit(this.filter);
2278
+ }
2279
+ }
2280
+ /**
2281
+ * Gets the view state of search control
2282
+ */
2283
+ get viewState() {
2284
+ return {
2285
+ advancedView: !this.collapsed
2286
+ };
2287
+ }
2288
+ /**
2289
+ * Switches view between simple and advanced search
2290
+ */
2291
+ toggleView() {
2292
+ return __awaiter(this, void 0, void 0, function* () {
2293
+ this.collapsed = !this.collapsed;
2294
+ // emit event for view status change
2295
+ this.viewChange.emit(this.viewState);
2296
+ });
2297
+ }
2298
+ }
2299
+ AdvancedTableSearchComponent.decorators = [
2300
+ { type: Component, args: [{
2301
+ selector: 'app-advanced-table-search',
2302
+ template: "<div class=\"s--list-navbar d-none d-sm-block\">\n\n <div id=\"collapse-header\" [ngClass]=\"{'show':collapsed}\" class=\"collapse multi-collapse\">\n <form class=\"form-inline p-3 col\">\n <div class=\"form-group col-7\" [ngClass]= \"{ 'col-12': !showMore }\">\n <label class=\"pr-3\" for=\"searchText\" [translate]=\"'Tables.Search'\"></label>\n <input (keydown)=\"onSearchKeyDown($event)\" spellcheck=\"false\" type=\"text\" id=\"searchText\" name=\"searchText\"\n class=\"form-control col text-left\" placeholder=\"{{ 'Tables.SearchPlaceholder' | translate }}\" #searchText>\n </div>\n <div *ngIf=\"showMore\" class=\"col-5 text-right text-secondary\" id=\"heading3\">\n <button class=\"d-inline-block btn btn-link collapsed float-right text-secondary text-decoration-none\"\n type=\"button\" (click)=\"toggleView();\">\n <span class=\"d-none d-sm-inline\" [translate]=\"'AdvancedSearch.More'\"></span>\n <i class=\"ml-2 pt-1 fa fa-chevron-down\"></i>\n </button>\n </div>\n </form>\n </div>\n\n <div id=\"collapse3\" [ngClass]=\"{'show':!collapsed}\" class=\"collapse multi-collapse\" aria-labelledby=\"heading3\">\n <div class=\"row\">\n <div class=\"col-sm-12\">\n <div id=\"advanced-form\">\n <ng-content select=\"advanced-search\"></ng-content>\n <hr class=\"mt-0 mb-0\" width=\"97%\">\n <div class=\"row\">\n <div class=\"col-sm-12\">\n <div class=\"form-inline pb-3 pt-3 pr-1 pl-1 col\">\n\n <div class=\"form-group col-4\">\n <div class=\"d-flex flex-row w-100\" *ngIf=\"allowSave\">\n <label class=\"pr-3 d-none\" [translate]=\"'AdvancedSearch.SavedSearches'\"></label>\n <select *ngIf=\"userSearchList\" class=\"custom-select text-secondary flex col-12\" (change)=\"onSelectUserSearch($event)\" [(ngModel)]=\"selectedList\">\n <option [ngValue]=\"null\" [translate]=\"'AdvancedSearch.Choose'\"></option>\n <option *ngFor=\"let item of userSearchList\" [ngValue]=\"item\">{{ item.name }}</option>\n </select>\n <select *ngIf=\"!userSearchList\" class=\"custom-select flex col-6\">\n <option [value]=\"null\" [translate]=\"'AdvancedSearch.Choose'\"></option>\n </select>\n </div>\n </div>\n\n <div class=\"col-3 d-flex flex-row w-100\">\n <div class=\"btn-group\" *ngIf=\"allowSave\" dropdown>\n <button type=\"button\" class=\"btn btn-sm btn-primary\" (click)=\"saveSearch(templateToUse, ( (selectedList && selectedList.readOnly) ? true : false ) )\" [translate]=\"'AdvancedSearch.SaveCurrentSearch'\"></button>\n <button type=\"button\" dropdownToggle class=\"btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n <span class=\"sr-only\" [translate]=\"'AdvancedSearch.ToggleDropdown'\"></span>\n </button>\n\n <div class=\"dropdown-menu\" *dropdownMenu>\n <a [ngClass]=\"{'disabled': selectedList && selectedList.readOnly}\" class=\"dropdown-item\" (click)=\"saveSearch(templateToUse, false)\" [translate]=\"'AdvancedSearch.Save'\" href=\"javascript:void(0)\"></a>\n <a class=\"dropdown-item\" (click)=\"saveSearch(templateToUse)\" [translate]=\"'AdvancedSearch.SaveAs'\" href=\"javascript:void(0)\"></a>\n <div class=\"dropdown-divider\"></div>\n <a [ngClass]=\"{'disabled': selectedList && selectedList.readOnly}\" class=\"dropdown-item\" (click)=\"deleteItemFromSavedSearchListByObject(selectedList)\" [translate]=\"'AdvancedSearch.Delete'\" href=\"javascript:void(0)\"></a>\n </div>\n </div>\n </div>\n\n <div class=\"col-5 text-right text-secondary\">\n <button type=\"button\" class=\"btn btn-light text-secondary pl-2 pr-2 mr-2\" (click)=\"clear()\">\n <i class=\"fa fa-eraser\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-success pl-4 pr-4 mr-2\" (click)=\"advancedSearch($event);\"\n [translate]=\"'Tables.Search'\"></button>\n <button class=\"d-inline-block btn btn-link collapsed float-right text-secondary text-decoration-none\"\n type=\"button\" (click)=\"toggleView();\">\n <span class=\"d-none d-sm-inline\" [translate]=\"'AdvancedSearch.Less'\"></span>\n <i class=\"ml-2 pt-1 fa fa-chevron-up\"></i>\n </button>\n </div>\n\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <ng-template #templateToUse>\n <div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\" [translate]=\"'AdvancedSearch.Modal.Title'\"></h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"modalRef.hide()\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n <div class=\"modal-body mt-0 mb-0 text-center\">\n <div>\n <p class=\"text-secondary text-left\" [translate]=\"'AdvancedSearch.Modal.Help'\"></p>\n </div>\n <form class=\"p-3 text-center\" #newListForm=\"ngForm\" >\n <div [ngVar]=\"{ name: ( selectedList?.name ? ( selectedList?.name + ' (1)' ) : null ) }\" #model=\"ngVar\">\n <input [(ngModel)]=\"model.value.name\" [required]=\"true\" id=\"newListName\" name=\"newListName\" type=\"text\" class=\"form-control\" placeholder=\"{{ 'AdvancedSearch.Modal.SaveNamePlaceholder' | translate }}\"/>\n <div class=\"btn-group mt-4\">\n <button [disabled]=\"newListForm.invalid\" type=\"button\" class=\"btn btn-primary\" (click)=\"confirmModal(model.value.name)\" [translate]=\"'AdvancedSearch.Modal.Accept'\"></button>\n <button type=\"button\" class=\"btn btn-gray-300 text-white\" (click)=\"closeModal()\" [translate]=\"'AdvancedSearch.Modal.Close'\"></button>\n </div>\n </div>\n </form>\n </div>\n </ng-template>\n\n</div>\n",
2303
+ encapsulation: ViewEncapsulation.None
2304
+ }] }
2305
+ ];
2306
+ /** @nocollapse */
2307
+ AdvancedTableSearchComponent.ctorParameters = () => [
2308
+ { type: AngularDataContext },
2309
+ { type: ActivatedRoute },
2310
+ { type: DatePipe },
2311
+ { type: BsModalService },
2312
+ { type: UserStorageService },
2313
+ { type: TranslateService },
2314
+ { type: AdvancedFilterValueProvider },
2315
+ { type: TemplatePipe },
2316
+ { type: Router },
2317
+ { type: ModalService }
2318
+ ];
2319
+ AdvancedTableSearchComponent.propDecorators = {
2320
+ table: [{ type: Input }],
2321
+ collapsed: [{ type: Input }],
2322
+ allowSave: [{ type: Input }],
2323
+ showMore: [{ type: Input }],
2324
+ mergeQueryParams: [{ type: Input }],
2325
+ searchText: [{ type: ViewChild, args: ['searchText',] }],
2326
+ viewChange: [{ type: Output, args: ['viewChange',] }]
2327
+ };
2328
+
2329
+ const FORMATTERS = {
2330
+ ActionLinkFormatter: new ActionLinkFormatter(),
2331
+ ButtonFormatter: new ButtonFormatter(),
2332
+ DateTimeFormatter: new DateTimeFormatter(),
2333
+ LanguageFormatter: new LanguageFormatter(),
2334
+ LinkFormatter: new LinkFormatter(),
2335
+ NestedPropertyFormatter: new NestedPropertyFormatter(),
2336
+ NgClassFormatter: new NgClassFormatter(),
2337
+ SelectRowFormatter: new SelectRowFormatter(),
2338
+ TemplateFormatter: new TemplateFormatter(),
2339
+ TranslationFormatter: new TranslationFormatter(),
2340
+ TrueFalseFormatter: new TrueFalseFormatter()
2341
+ };
2342
+
2343
+ // important: export this const due to AOT compilation errors
2344
+ // see more https://angular.io/guide/aot-metadata-errors#only-initialized-variables
2345
+ const AdvancedTableModalBaseTemplate = `
2346
+ <div>
2347
+ <!-- search -->
2348
+ <div class="row mt-3">
2349
+ <div class="col-12 col-lg-3">
2350
+ <div class="input-group mb-3">
2351
+ <input spellcheck="false" type="text"
2352
+ (keyup)="onKeyUp($event)" [(ngModel)]="searchText"
2353
+ class="form-control bg-gray-100 border-0"
2354
+ placeholder="{{'Tables.SearchPlaceholder' | translate}}" aria-label="Search" aria-describedby="basic-addon2">
2355
+ <div class="input-group-append">
2356
+ <button class="btn btn-default bg-gray-100 border-0" type="button">
2357
+ <i class="fa fa-search text-secondary"></i>
2358
+ </button>
2359
+ </div>
2360
+ </div>
2361
+ </div>
2362
+ <div #emptyTable class="d-none">
2363
+ <div>
2364
+ <div class="mt-4">
2365
+ <div class="icon-circle bg-gray-300 border-gray-100"></div>
2366
+ </div>
2367
+ <div class="mt-4">
2368
+ <h4 class="font-3xl text-dark" [translate]="'Tables.EmptyTableTitle'"></h4>
2369
+ <p class="font-lg text-dark" [translate]="'Tables.EmptyTableMessage'"></p>
2370
+ </div>
2371
+
2372
+ </div>
2373
+ </div>
2374
+ <div class="col-12 col-lg-5">
2375
+ <div class="btn-toolbar">
2376
+ <button class="btn btn-gray-100">
2377
+ <i class="fa fa-sliders-h pr-2 text-secondary" aria-hidden="true"></i>{{'Tables.Filters' | translate}}
2378
+ </button>
2379
+ <div #dropdownFilter class="dropdown ml-3" dropdown>
2380
+ <button class="btn btn-gray-100 dropdown-toggle"
2381
+ type="button" id="dropdownDefaultFilters"
2382
+ data-toggle="dropdown" dropdownToggle>
2383
+ {{ this.defaultFilter.name | translate }}
2384
+ </button>
2385
+ <div class="dropdown-menu" aria-labelledby="dropdownDefaultFilters" *dropdownMenu>
2386
+ <a class="dropdown-item" *ngFor="let filter of defaultFilters" (click)="advancedSearch(filter.filter ? filter : '')">
2387
+ {{ filter.name | translate }}
2388
+ </a>
2389
+ </div>
2390
+ </div>
2391
+ </div>
2392
+ </div>
2393
+ <div class="col text-lg-right mt-3 mt-lg-0">
2394
+ <button [disabled]="true" class="btn rounded-pill btn-outline-theme">
2395
+ <span class="pr-2 font-lg">+</span><span [translate]="'Tables.AddItem'"></span>
2396
+ </button>
2397
+ </div>
2398
+ </div>
2399
+ <div class="row">
2400
+ <div class="col-12" [ngClass]="{ 'component-loading': loading}">
2401
+ <app-advanced-table #advancedTable (init)="onTableInit($event)"
2402
+ (load)="onDataLoad($event)"
2403
+ [selectable]="true"
2404
+ [multipleSelect]="true"
2405
+ [scrollX]="false" [scrollY]="320"
2406
+ [pageLength]="pageLength"
2407
+ [scrollable]="false" [showFooter]="false"
2408
+ [showActions]="false" [lengthMenu]="lengthMenu"
2409
+ [config]="tableConfig" [configSrc]="tableConfigSrc"></app-advanced-table>
2410
+ </div>
2411
+ </div>
2412
+ <div class="row mt-3 table-custom-pager">
2413
+ <div class="col-12 d-flex" *ngIf="recordsTotal">
2414
+ <div class="mt-2 ml-auto mr-3 text-secondary" [translate]="'Tables.RecordsPerPage'"></div>
2415
+ <div #dropdownLength class="dropdown mr-3" dropdown>
2416
+ <button class="btn btn-gray-100 dropdown-toggle"
2417
+ type="button" id="dropdownPageSize" data-toggle="dropdown"
2418
+ dropdownToggle>
2419
+ {{pageLength}}
2420
+ </button>
2421
+ <div class="dropdown-menu" aria-labelledby="dropdownPageSize" *dropdownMenu>
2422
+ <button (click)="setLength(item)"
2423
+ class="dropdown-item dropdown-item-theme"
2424
+ type="button" *ngFor="let item of lengthMenu">{{item}}</button>
2425
+ </div>
2426
+ </div>
2427
+ <div class="mt-2 mr-2" [translate]="'Tables.PageInfo'" [translateParams]="pageInfo"></div>
2428
+ <button [disabled]="pageInfo && pageInfo.page === 0" class="btn text-nowrap" (click)="firstPage()">
2429
+ <i class="fa fa-chevron-left text-color-theme"></i>
2430
+ <i class="fa fa-chevron-left text-color-theme"></i>
2431
+ </button>
2432
+ <button [disabled]="pageInfo && pageInfo.page===0" class="btn" (click)="previousPage()">
2433
+ <i class="fa fa-chevron-left text-color-theme"></i>
2434
+ </button>
2435
+ <button [disabled]="pageInfo && pageInfo.page===pageInfo.pages-1" class="btn" (click)="nextPage()">
2436
+ <i class="fa fa-chevron-right text-color-theme"></i>
2437
+ </button>
2438
+ <button [disabled]="pageInfo && pageInfo.page===pageInfo.pages-1" class="btn text-nowrap" (click)="lastPage()">
2439
+ <i class="fa fa-chevron-right text-color-theme"></i>
2440
+ <i class="fa fa-chevron-right text-color-theme"></i>
2441
+ </button>
2442
+ </div>
2443
+ </div>
2444
+ </div>
2445
+ `;
2446
+ class AdvancedTableModalBaseComponent extends RouterModalOkCancel {
2447
+ constructor(_router, _activatedRoute, _context, advancedFilterValueProvider, datePipe) {
2448
+ super(_router, _activatedRoute);
2449
+ this._context = _context;
2450
+ this.advancedFilterValueProvider = advancedFilterValueProvider;
2451
+ this.datePipe = datePipe;
2452
+ this.pageLength = 25;
2453
+ this.lengthMenu = [5, 10, 25, 50, 100];
2454
+ this.recordsTotal = 0;
2455
+ this.loading = false;
2456
+ this.recordsFiltered = 0;
2457
+ this.defaultFilters = [AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER];
2458
+ this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
2459
+ this.pageInfo = {
2460
+ serverSide: true
2461
+ };
2462
+ this.modalClass = 'modal-xl modal-table';
2463
+ this._template = new TemplatePipe();
2464
+ }
2465
+ ngAfterViewInit() {
2466
+ //
2467
+ }
2468
+ _getInputs(componentType) {
2469
+ const props = componentType.__prop__metadata__;
2470
+ const inputs = [];
2471
+ if (props == null) {
2472
+ return inputs;
2473
+ }
2474
+ Object.keys(props).forEach(prop => {
2475
+ const member = props[prop][0];
2476
+ if (member.ngMetadataName === 'Input') {
2477
+ inputs.push(prop);
2478
+ }
2479
+ });
2480
+ return inputs.sort();
2481
+ }
2482
+ hasInputs() {
2483
+ return [];
2484
+ }
2485
+ ngOnInit() {
2486
+ this.okButtonText = 'Tables.Apply';
2487
+ this.cancelButtonText = 'Tables.Cancel';
2488
+ // get route data
2489
+ this.subscription = this.activatedRoute.data.subscribe(data => {
2490
+ // set modal title if any
2491
+ if (data.title) {
2492
+ this.modalTitle = data.title;
2493
+ }
2494
+ if (data.config) {
2495
+ this.tableConfig = data.config;
2496
+ }
2497
+ if (this.tableConfig) {
2498
+ this.defaultFilters.push.apply(this.defaultFilters, this.tableConfig.searches);
2499
+ }
2500
+ // important: use hasInputs due to AOT compilation. Metadata properties cannot be used (e.g. @Input())
2501
+ // because there are empty during runtime
2502
+ // so we need to define manually input properties that are going to be bind through this process
2503
+ // bind all inputs
2504
+ const inputs = this.hasInputs();
2505
+ inputs.forEach(input => {
2506
+ if (Object.prototype.hasOwnProperty.call(data, input)) {
2507
+ this[input] = data[input];
2508
+ }
2509
+ });
2510
+ this.configChangesSubscription = this.advancedTable.configChanges.subscribe((config) => {
2511
+ if (this.tableConfig) {
2512
+ // reset default filter
2513
+ this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
2514
+ // init filter
2515
+ this.defaultFilters = [AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER];
2516
+ // add extra filters
2517
+ this.defaultFilters.push.apply(this.defaultFilters, this.tableConfig.searches);
2518
+ }
2519
+ });
2520
+ });
2521
+ }
2522
+ cancel() {
2523
+ return this.close();
2524
+ }
2525
+ ok() {
2526
+ return this.close();
2527
+ }
2528
+ ngOnDestroy() {
2529
+ if (this.subscription) {
2530
+ this.subscription.unsubscribe();
2531
+ }
2532
+ }
2533
+ onDataLoad(data) {
2534
+ this.recordsTotal = data.recordsTotal;
2535
+ this.recordsFiltered = data.recordsFiltered;
2536
+ // get first page info (because page is not going to be fired for first page)
2537
+ if (data.recordsTotal > 0 && this.pageInfo.recordsTotal === 0) {
2538
+ this.pageInfo.recordsTotal = data.recordsTotal;
2539
+ }
2540
+ }
2541
+ previousPage() {
2542
+ // go to next page
2543
+ // noinspection TypeScriptValidateJSTypes
2544
+ this.advancedTable.dataTable.page('previous').draw('page');
2545
+ }
2546
+ nextPage() {
2547
+ // go to next page
2548
+ // noinspection TypeScriptValidateJSTypes
2549
+ this.advancedTable.dataTable.page('next').draw('page');
2550
+ }
2551
+ onTableInit(dataTable) {
2552
+ const self = this;
2553
+ // get initial page info
2554
+ const initialPageInfo = dataTable.page.info();
2555
+ initialPageInfo.start += 1;
2556
+ self.pageInfo = initialPageInfo;
2557
+ // change emptyTableMessage
2558
+ dataTable.context[0].oLanguage.sEmptyTable = this.emptyTable.nativeElement.innerHTML;
2559
+ // bind page change event
2560
+ dataTable.on('page.dt', function () {
2561
+ const pageInfo = dataTable.page.info();
2562
+ pageInfo.start += 1;
2563
+ self.pageInfo = pageInfo;
2564
+ });
2565
+ // bind draw change event
2566
+ dataTable.on('draw.dt', function () {
2567
+ // for future use
2568
+ self.loading = false;
2569
+ const pageInfo = dataTable.page.info();
2570
+ pageInfo.start += 1;
2571
+ self.pageInfo = pageInfo;
2572
+ });
2573
+ }
2574
+ firstPage() {
2575
+ // go to first page
2576
+ // noinspection TypeScriptValidateJSTypes
2577
+ this.advancedTable.dataTable.page('first').draw('page');
2578
+ }
2579
+ lastPage() {
2580
+ // go to last page
2581
+ // noinspection TypeScriptValidateJSTypes
2582
+ this.advancedTable.dataTable.page('last').draw('page');
2583
+ }
2584
+ setLength(value) {
2585
+ this.pageLength = value;
2586
+ this.advancedTable.dataTable.page.len(value).draw();
2587
+ }
2588
+ onKeyUp(event) {
2589
+ if (event.code === 'Enter') {
2590
+ // loading
2591
+ this.loading = true;
2592
+ this.advancedTable.search(event.target.value);
2593
+ }
2594
+ }
2595
+ /**
2596
+ *
2597
+ * Reads the modal table and returns a list of filters to use in the table
2598
+ * query.
2599
+ *
2600
+ * @param tableConfig {object} The table configuration from json file
2601
+ * @param defaultFilterValues {object} A list of default filters
2602
+ * @param {object} searchItem The searchItem to use
2603
+ *
2604
+ */
2605
+ getFilters(tableConfig, defaultFilterValues = {}, searchItem) {
2606
+ // Do nothing in absence of configuration.
2607
+ if (!tableConfig || !Array.isArray(tableConfig.criteria)) {
2608
+ console.warn('No table configuration available', this.tableConfig);
2609
+ return [];
2610
+ }
2611
+ const matchedCriterion = tableConfig.criteria.find(criterion => Object.prototype.hasOwnProperty.call(searchItem.filter, criterion.name));
2612
+ if (!matchedCriterion) {
2613
+ return [];
2614
+ }
2615
+ const criterionSafeName = matchedCriterion.name !== null
2616
+ ? matchedCriterion.name.toString()
2617
+ : '';
2618
+ let applicableFilter;
2619
+ // Raw dates should be transformed to a specific format
2620
+ if (criterionSafeName.includes('GMT')) {
2621
+ const newDate = this.datePipe.transform(criterionSafeName, 'MM/dd/yyyy');
2622
+ applicableFilter = this._template.transform(matchedCriterion.filter, {
2623
+ value: newDate
2624
+ });
2625
+ }
2626
+ else if (criterionSafeName !== 'undefined') {
2627
+ applicableFilter = (this._template.transform(matchedCriterion.filter, Object.assign({
2628
+ value: matchedCriterion.filter
2629
+ }, defaultFilterValues)));
2630
+ }
2631
+ else {
2632
+ applicableFilter = '';
2633
+ }
2634
+ return [applicableFilter];
2635
+ }
2636
+ /**
2637
+ *
2638
+ * Runs a filter against the modal table
2639
+ *
2640
+ * @param {object} filter The filter to use in the search
2641
+ *
2642
+ */
2643
+ advancedSearch(filter) {
2644
+ return __awaiter(this, void 0, void 0, function* () {
2645
+ // Do nothing in absence of configuration.
2646
+ if (!this.tableConfig && !Array.isArray(this.tableConfig.criteria)) {
2647
+ console.warn('No table configuration available', this.tableConfig);
2648
+ return;
2649
+ }
2650
+ // An advanced table has some filters by default
2651
+ let defaultFilterValues;
2652
+ try {
2653
+ defaultFilterValues = yield this.advancedFilterValueProvider.getValues();
2654
+ }
2655
+ catch (err) {
2656
+ defaultFilterValues = {};
2657
+ }
2658
+ const expressions = [];
2659
+ let predefinedCriteria;
2660
+ if (filter) {
2661
+ this.defaultFilter = filter;
2662
+ predefinedCriteria = this.getFilters(this.tableConfig, defaultFilterValues, filter);
2663
+ }
2664
+ else {
2665
+ // Empty a falsy filter resets the results
2666
+ this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
2667
+ }
2668
+ // search filter is applied separately, there is no need to add here.
2669
+ expressions.push(predefinedCriteria);
2670
+ const query = this._context.model(this.advancedTable.config.model).asQueryable();
2671
+ query.setParam('filter', expressions.join(' and ')).prepare();
2672
+ this.advancedTable.query = query;
2673
+ this.advancedTable.fetch();
2674
+ });
2675
+ }
2676
+ }
2677
+ AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER = {
2678
+ name: 'Tables.All',
2679
+ filter: null
2680
+ };
2681
+ AdvancedTableModalBaseComponent.decorators = [
2682
+ { type: Component, args: [{
2683
+ selector: 'app-advanced-table-modal',
2684
+ template: AdvancedTableModalBaseTemplate,
2685
+ styles: [`
2686
+
2687
+ `]
2688
+ }] }
2689
+ ];
2690
+ /** @nocollapse */
2691
+ AdvancedTableModalBaseComponent.ctorParameters = () => [
2692
+ { type: Router },
2693
+ { type: ActivatedRoute },
2694
+ { type: AngularDataContext },
2695
+ { type: AdvancedFilterValueProvider },
2696
+ { type: DatePipe }
2697
+ ];
2698
+ AdvancedTableModalBaseComponent.propDecorators = {
2699
+ advancedTable: [{ type: ViewChild, args: ['advancedTable',] }],
2700
+ emptyTable: [{ type: ViewChild, args: ['emptyTable',] }],
2701
+ dropdownFilter: [{ type: ViewChild, args: ['dropdownFilter',] }],
2702
+ dropdownLength: [{ type: ViewChild, args: ['dropdownLength',] }],
2703
+ tableConfig: [{ type: Input, args: ['tableConfig',] }],
2704
+ tableConfigSrc: [{ type: Input, args: ['tableConfigSrc',] }],
2705
+ pageLength: [{ type: Input, args: ['pageLength',] }],
2706
+ searchText: [{ type: Input, args: ['searchText',] }],
2707
+ lengthMenu: [{ type: Input, args: ['lengthMenu',] }],
2708
+ defaultFilter: [{ type: Input, args: ['defaultFilter',] }]
2709
+ };
2710
+
2711
+ class AdvancedSelectComponent extends AdvancedTableModalBaseComponent {
2712
+ constructor(router, activatedRoute, context, advancedFilterValueProvider, datePipe, _modalRef) {
2713
+ super(router, activatedRoute, context, advancedFilterValueProvider, datePipe);
2714
+ this._modalRef = _modalRef;
2715
+ this.dismiss = new EventEmitter();
2716
+ }
2717
+ ok() {
2718
+ return __awaiter(this, void 0, void 0, function* () {
2719
+ this._modalRef.hide();
2720
+ this.dismiss.emit('ok');
2721
+ });
2722
+ }
2723
+ cancel() {
2724
+ return __awaiter(this, void 0, void 0, function* () {
2725
+ this._modalRef.hide();
2726
+ this.dismiss.emit('cancel');
2727
+ });
2728
+ }
2729
+ }
2730
+ AdvancedSelectComponent.decorators = [
2731
+ { type: Component, args: [{
2732
+ selector: 'app-advanced-select',
2733
+ template: "<div class=\"modal-header\" *ngIf=\"modalTitle\">\n <h5 class=\"modal-title\">{{modalTitle | translate}}</h5>\n <button [disabled]=\"cancelButtonDisabled\" (click)=\"cancel()\" type=\"button\" class=\"close\" data-dismiss=\"modal\"\n aria-label=\"Cancel\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <div>\n <!-- search -->\n <div class=\"row mt-3\">\n <div class=\"col-12 col-lg-3\">\n <div class=\"input-group mb-3\">\n <input spellcheck=\"false\" type=\"text\" (keyup)=\"onKeyUp($event)\" [(ngModel)]=\"searchText\"\n class=\"form-control bg-gray-100 border-0\" placeholder=\"{{'Tables.SearchPlaceholder' | translate}}\"\n aria-label=\"Search\" aria-describedby=\"basic-addon2\">\n <div class=\"input-group-append\">\n <button class=\"btn btn-default bg-gray-100 border-0\" type=\"button\">\n <i class=\"fa fa-search text-secondary\"></i>\n </button>\n </div>\n </div>\n </div>\n <div #emptyTable class=\"d-none\">\n <div>\n <div class=\"mt-4\">\n <div class=\"icon-circle bg-gray-300 border-gray-100\"></div>\n </div>\n <div class=\"mt-4\">\n <h4 class=\"font-3xl text-dark\" [translate]=\"'Tables.EmptyTableTitle'\"></h4>\n <p class=\"font-lg text-dark\" [translate]=\"'Tables.EmptyTableMessage'\"></p>\n </div>\n\n </div>\n </div>\n <div class=\"col-12 col-lg-5\">\n <div class=\"btn-toolbar\">\n <button class=\"btn btn-gray-100\">\n <i class=\"fa fa-sliders-h pr-2 text-secondary\" aria-hidden=\"true\"></i>{{'Tables.Filters' | translate}}\n </button>\n <div class=\"dropdown ml-3\" dropdown>\n <button dropdownToggle class=\"btn btn-gray-100 dropdown-toggle\" type=\"button\" id=\"dropdownDefaultFilters\"\n data-toggle=\"dropdown\">\n {{ this.defaultFilter.name | translate }}\n </button>\n <div class=\"dropdown-menu\" aria-labelledby=\"dropdownDefaultFilters\" *dropdownMenu>\n <a class=\"dropdown-item\" *ngFor=\"let filter of defaultFilters\"\n (click)=\"advancedSearch(filter.filter ? filter : '')\">\n {{ filter.name | translate }}\n </a>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col text-lg-right mt-3 mt-lg-0\">\n <button [disabled]=\"true\" class=\"btn rounded-pill btn-outline-theme\">\n <span class=\"pr-2 font-lg\">+</span><span [translate]=\"'Tables.AddItem'\"></span>\n </button>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-12\" [ngClass]=\"{ 'component-loading': loading}\">\n <app-advanced-table #advancedTable (init)=\"onTableInit($event)\" (load)=\"onDataLoad($event)\"\n [selectable]=\"true\" [multipleSelect]=\"true\" [scrollX]=\"false\" [scrollY]=\"320\" [pageLength]=\"pageLength\"\n [scrollable]=\"false\" [showFooter]=\"false\" [showActions]=\"false\" [lengthMenu]=\"lengthMenu\"\n [config]=\"tableConfig\" [configSrc]=\"tableConfigSrc\"></app-advanced-table>\n </div>\n </div>\n <div class=\"row mt-3 table-custom-pager\">\n <div class=\"col-12 d-flex\" *ngIf=\"recordsTotal\">\n <div class=\"mt-2 ml-auto mr-3 text-secondary\" [translate]=\"'Tables.RecordsPerPage'\"></div>\n <div class=\"dropdown mr-3\" dropdown>\n <button dropdownToggle class=\"btn btn-gray-100 dropdown-toggle\" type=\"button\" id=\"dropdownPageSize\"\n data-toggle=\"dropdown\">\n {{pageLength}}\n </button>\n <div class=\"dropdown-menu\" aria-labelledby=\"dropdownPageSize\" *dropdownMenu>\n <button (click)=\"setLength(item)\" class=\"dropdown-item dropdown-item-theme\" type=\"button\"\n *ngFor=\"let item of lengthMenu\">{{item}}</button>\n </div>\n </div>\n <div class=\"mt-2 mr-2\" [translate]=\"'Tables.PageInfo'\" [translateParams]=\"pageInfo\"></div>\n <button [disabled]=\"pageInfo && pageInfo.page === 0\" class=\"btn text-nowrap\" (click)=\"firstPage()\">\n <i class=\"fa fa-chevron-left text-color-theme\"></i>\n <i class=\"fa fa-chevron-left text-color-theme\"></i>\n </button>\n <button [disabled]=\"pageInfo && pageInfo.page===0\" class=\"btn\" (click)=\"previousPage()\">\n <i class=\"fa fa-chevron-left text-color-theme\"></i>\n </button>\n <button [disabled]=\"pageInfo && pageInfo.page===pageInfo.pages-1\" class=\"btn\" (click)=\"nextPage()\">\n <i class=\"fa fa-chevron-right text-color-theme\"></i>\n </button>\n <button [disabled]=\"pageInfo && pageInfo.page===pageInfo.pages-1\" class=\"btn text-nowrap\"\n (click)=\"lastPage()\">\n <i class=\"fa fa-chevron-right text-color-theme\"></i>\n <i class=\"fa fa-chevron-right text-color-theme\"></i>\n </button>\n </div>\n </div>\n </div>\n</div>\n<div class=\"modal-footer justify-content-end\">\n <button [disabled]=\"okButtonDisabled\" (click)=\"ok()\" type=\"button\" [ngClass]=\"okButtonClass\">\n {{okButtonText | translate}}\n </button>\n <button [disabled]=\"cancelButtonDisabled\" (click)=\"cancel()\" type=\"button\" [ngClass]=\"cancelButtonClass\"\n data-dismiss=\"modal\">\n {{ cancelButtonText | translate}}\n </button>\n</div>\n",
2734
+ encapsulation: ViewEncapsulation.None,
2735
+ styles: [`
2736
+ .modal-table .dataTables_wrapper {
2737
+ height: 50vh;
2738
+ overflow-y: auto;
2739
+ }
2740
+ `]
2741
+ }] }
2742
+ ];
2743
+ /** @nocollapse */
2744
+ AdvancedSelectComponent.ctorParameters = () => [
2745
+ { type: Router },
2746
+ { type: ActivatedRoute },
2747
+ { type: AngularDataContext },
2748
+ { type: AdvancedFilterValueProvider },
2749
+ { type: DatePipe },
2750
+ { type: BsModalRef }
2751
+ ];
2752
+
2753
+ class AdvancedSelectService {
2754
+ constructor(_modal) {
2755
+ this._modal = _modal;
2756
+ //
2757
+ }
2758
+ select(initialState) {
2759
+ return new Promise((resolve, reject) => {
2760
+ try {
2761
+ const modalRef = this._modal.openModalComponent(AdvancedSelectComponent, {
2762
+ class: 'modal-xl modal-table',
2763
+ ignoreBackdropClick: true,
2764
+ animated: true,
2765
+ initialState: initialState
2766
+ });
2767
+ const componentRef = modalRef.content;
2768
+ componentRef.dismiss.subscribe((result) => {
2769
+ return resolve({
2770
+ result: result,
2771
+ items: componentRef.advancedTable.selected
2772
+ });
2773
+ });
2774
+ }
2775
+ catch (err) {
2776
+ return reject(err);
2777
+ }
2778
+ });
2779
+ }
2780
+ }
2781
+ AdvancedSelectService.decorators = [
2782
+ { type: Injectable }
2783
+ ];
2784
+ /** @nocollapse */
2785
+ AdvancedSelectService.ctorParameters = () => [
2786
+ { type: ModalService }
2787
+ ];
2788
+
2789
+ class ActivatedTableService {
2790
+ constructor(_router, _activatedRoute) {
2791
+ this._router = _router;
2792
+ this._activatedRoute = _activatedRoute;
2793
+ _router.events.forEach((event) => {
2794
+ if (event instanceof NavigationStart) {
2795
+ if (this._activeTable) {
2796
+ this._activeTable = null;
2797
+ }
2798
+ }
2799
+ });
2800
+ }
2801
+ get activeTable() {
2802
+ return this._activeTable;
2803
+ }
2804
+ set activeTable(tableComponent) {
2805
+ this._activeTable = tableComponent;
2806
+ }
2807
+ }
2808
+ ActivatedTableService.decorators = [
2809
+ { type: Injectable, args: [{
2810
+ providedIn: 'root'
2811
+ },] }
2812
+ ];
2813
+ /** @nocollapse */
2814
+ ActivatedTableService.ctorParameters = () => [
2815
+ { type: Router },
2816
+ { type: ActivatedRoute }
2817
+ ];
2818
+ ActivatedTableService.ngInjectableDef = defineInjectable({ factory: function ActivatedTableService_Factory() { return new ActivatedTableService(inject(Router), inject(ActivatedRoute)); }, token: ActivatedTableService, providedIn: "root" });
2819
+
2820
+ class BtnExpDirective {
2821
+ constructor(_activatedTable, _context, _config, _translate, _loadingService, _errorService) {
2822
+ this._activatedTable = _activatedTable;
2823
+ this._context = _context;
2824
+ this._config = _config;
2825
+ this._translate = _translate;
2826
+ this._loadingService = _loadingService;
2827
+ this._errorService = _errorService;
2828
+ }
2829
+ clickEvent(event) {
2830
+ return __awaiter(this, void 0, void 0, function* () {
2831
+ event.preventDefault();
2832
+ event.stopPropagation();
2833
+ if (this._activatedTable.activeTable) {
2834
+ const lastQuery = this._activatedTable.activeTable.lastQuery;
2835
+ const tableConfig = this._activatedTable.activeTable.config;
2836
+ const serviceHeaders = this._context.getService().getHeaders();
2837
+ const queryModel = lastQuery.getModel();
2838
+ let query = this._context.model(queryModel).asQueryable();
2839
+ const queryParams = lastQuery.getParams();
2840
+ query = this.constructQuery(query, queryParams, tableConfig, serviceHeaders);
2841
+ const maxExportItems = 1000;
2842
+ {
2843
+ query.skip(0).take(maxExportItems);
2844
+ }
2845
+ // This constructs the URI based on the way @themost does
2846
+ const url = this.constructURL(query);
2847
+ this.downloadFile(url, query.getService().getHeaders(), query.getModel());
2848
+ }
2849
+ });
2850
+ }
2851
+ /**
2852
+ * Sets the parameters to the query based on
2853
+ * the active filters of the activated table
2854
+ * @param {ClientDataQueryable} query
2855
+ * @param params: The filters of the last query of the activated table
2856
+ * @return ClientDataQueryable
2857
+ */
2858
+ setParams(query, params) {
2859
+ if (params && typeof params === 'object') {
2860
+ Object.keys(params).forEach((key) => {
2861
+ query.setParam(key, params[key]);
2862
+ });
2863
+ }
2864
+ return query;
2865
+ }
2866
+ /**
2867
+ * Sets the headers to the query based on
2868
+ * the default headers of the DataContext
2869
+ * and appending the accept header to get
2870
+ * an xlsx file from the API
2871
+ * @param {ClientDataQueryable} query
2872
+ * @param {Headers} headers: The headers to append to the query
2873
+ * @return ClientDataQueryable
2874
+ */
2875
+ setHeaders(query, headers) {
2876
+ Object.keys(headers).forEach((key) => {
2877
+ if (headers.hasOwnProperty(key)) {
2878
+ query.getService().setHeader(key, headers[key]);
2879
+ }
2880
+ });
2881
+ // sets a header to accept xlsx files
2882
+ query.getService().setHeader('Accept', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
2883
+ return query;
2884
+ }
2885
+ /**
2886
+ * Constructs the select parameter of the
2887
+ * ClientDataQuery by replacing the target name
2888
+ * of the field based on the localized translation
2889
+ * of that property on the advanced tabled
2890
+ * @param {ClientDataQueryable} query
2891
+ * @param tableConfig: The configuration of the activated table
2892
+ * @return ClientDataQueryable
2893
+ */
2894
+ constructQueryParam(query, tableConfig) {
2895
+ let selectQuery = query.getParams().$select;
2896
+ let selectQueryArray = selectQuery.split(',');
2897
+ selectQueryArray = selectQueryArray.map((select) => {
2898
+ const words = select.split(' ');
2899
+ if (words.length === 3) {
2900
+ if (tableConfig && tableConfig.columns) {
2901
+ for (const column of tableConfig.columns) {
2902
+ if (column.property === words[2] && column.title) {
2903
+ words[2] = `"${this._translate.instant(column.title)}"`;
2904
+ }
2905
+ else if (column.property === words[2]) {
2906
+ words[2] = `"${this._translate.instant(words[2])}"`;
2907
+ }
2908
+ }
2909
+ }
2910
+ }
2911
+ if (words.length === 1) {
2912
+ if (tableConfig && tableConfig.columns) {
2913
+ for (const column of tableConfig.columns) {
2914
+ if (column.name === words[0] && column.title) {
2915
+ words[0] = `${words[0]} as "${this._translate.instant(column.title)}"`;
2916
+ break;
2917
+ }
2918
+ else if (column.name === words[0] && column.property) {
2919
+ words[0] = `${words[0]} as "${this._translate.instant(column.property)}"`;
2920
+ break;
2921
+ }
2922
+ else if (words[0] === column.name && words[0] === "id" && column.hidden) {
2923
+ break;
2924
+ }
2925
+ else if (words[0] == column.name || words[0] === column.property) {
2926
+ words[0] = `${words[0]} as "${this._translate.instant(words[0])}"`;
2927
+ }
2928
+ }
2929
+ }
2930
+ return words[0];
2931
+ }
2932
+ return words.join(' ');
2933
+ });
2934
+ selectQuery = selectQueryArray.join(',');
2935
+ query.setParam('$select', selectQuery);
2936
+ return query;
2937
+ }
2938
+ /**
2939
+ * Constructs the fetch URL based the query parameters
2940
+ * @param {ClientDataQueryable} query
2941
+ * @return String
2942
+ */
2943
+ constructURL(query) {
2944
+ const baseURL = query.getService().getBase();
2945
+ let url = baseURL.endsWith('/') ? (baseURL + query.getModel() + '?') : (baseURL + '/' + query.getModel() + '?');
2946
+ Object.keys(query.getParams()).forEach((key, index) => {
2947
+ if (index === 0) {
2948
+ url += key + '=' + query.getParams()[key];
2949
+ }
2950
+ else {
2951
+ url += '&' + key + '=' + query.getParams()[key];
2952
+ }
2953
+ });
2954
+ return url;
2955
+ }
2956
+ /**
2957
+ * Constructs the query by setting the parameters of the
2958
+ * advanced table's active filters, sets the necessary headers
2959
+ * and build the select parameter of the query
2960
+ * @param {ClientDataQueryable} query
2961
+ * @param params: The filters of the last query of the activated table
2962
+ * @param tableConfig: The configuration of the activated table
2963
+ * @param {Headers} headers: The headers to append to the query
2964
+ * @return ClientDataQueryable
2965
+ */
2966
+ constructQuery(query, params, tableConfig, headers) {
2967
+ let _query = this.setParams(query, params);
2968
+ _query = this.setHeaders(_query, headers);
2969
+ _query = this.constructQueryParam(_query, tableConfig);
2970
+ return _query;
2971
+ }
2972
+ /**
2973
+ * Fetches and saves the xlsx file
2974
+ * @param {String} url
2975
+ * @param {Headers} serviceHeaders: The headers to append to the query
2976
+ * @param modelName: The model name of the advanced table
2977
+ */
2978
+ downloadFile(url, serviceHeaders, modelName) {
2979
+ this._loadingService.showLoading();
2980
+ fetch(encodeURI(url), {
2981
+ headers: serviceHeaders,
2982
+ credentials: 'include'
2983
+ }).then(response => {
2984
+ if (response && !response.ok) {
2985
+ throw new ResponseError(response.statusText, response.status);
2986
+ }
2987
+ return response.blob();
2988
+ }).then(blob => {
2989
+ const objectUrl = window.URL.createObjectURL(blob);
2990
+ const a = document.createElement('a');
2991
+ document.body.appendChild(a);
2992
+ a.setAttribute('style', 'display: none');
2993
+ a.href = objectUrl;
2994
+ const downloadName = `${modelName}.xlsx`;
2995
+ a.download = downloadName;
2996
+ // this adds support for IE and MS Edge (up to version 44.19041)
2997
+ if (window.navigator && window.navigator.msSaveOrOpenBlob) {
2998
+ window.navigator.msSaveOrOpenBlob(blob, downloadName);
2999
+ }
3000
+ else {
3001
+ // for all other browsers
3002
+ a.click();
3003
+ }
3004
+ window.URL.revokeObjectURL(objectUrl);
3005
+ a.remove();
3006
+ this._loadingService.hideLoading();
3007
+ }).catch(err => {
3008
+ this._loadingService.hideLoading();
3009
+ this._errorService.showError(err, {
3010
+ continueLink: '.'
3011
+ });
3012
+ });
3013
+ }
3014
+ }
3015
+ BtnExpDirective.decorators = [
3016
+ { type: Directive, args: [{
3017
+ selector: '.universis-btn-export'
3018
+ },] }
3019
+ ];
3020
+ /** @nocollapse */
3021
+ BtnExpDirective.ctorParameters = () => [
3022
+ { type: ActivatedTableService },
3023
+ { type: AngularDataContext },
3024
+ { type: ConfigurationService },
3025
+ { type: TranslateService },
3026
+ { type: LoadingService },
3027
+ { type: ErrorService }
3028
+ ];
3029
+ BtnExpDirective.propDecorators = {
3030
+ clickEvent: [{ type: HostListener, args: ['click', ['$event'],] }]
3031
+ };
3032
+
3033
+ class AdvancedTableSettingsComponent {
3034
+ constructor(_context, _activatedRoute, elRef, modalService, userStorage, formBuilder, _activatedTable) {
3035
+ this._context = _context;
3036
+ this._activatedRoute = _activatedRoute;
3037
+ this.elRef = elRef;
3038
+ this.modalService = modalService;
3039
+ this.userStorage = userStorage;
3040
+ this.formBuilder = formBuilder;
3041
+ this._activatedTable = _activatedTable;
3042
+ this.params = this._activatedRoute.params;
3043
+ this.columns = [];
3044
+ }
3045
+ ngOnInit() {
3046
+ }
3047
+ ngAfterViewInit() {
3048
+ this.activeTableConfig = this._activatedTable.activeTable.getConfig();
3049
+ this.storageKey = 'registrar/settings/' + this.activeTableConfig.title + '/table/columns';
3050
+ this.form = this.formBuilder.group({ columns: new FormArray([], this.minSelectedColumns(1)) });
3051
+ this.userStorage.getItem(this.storageKey).then((data) => {
3052
+ if (data) {
3053
+ this.savedTableSettings = data.value;
3054
+ if (this.savedTableSettings) {
3055
+ if (this._activatedTable.activeTable) {
3056
+ this._activatedTable.activeTable.handleColumns(this.savedTableSettings);
3057
+ }
3058
+ }
3059
+ this.addCheckboxes();
3060
+ }
3061
+ });
3062
+ }
3063
+ addCheckboxes() {
3064
+ this.tableColumns = this.activeTableConfig.columns.slice(1);
3065
+ this.tableColumns.forEach((column, i) => {
3066
+ if (!column.hidden) {
3067
+ const control = {
3068
+ id: column.name,
3069
+ name: column.title
3070
+ };
3071
+ this.columns.push(control);
3072
+ }
3073
+ });
3074
+ if (this.savedTableSettings) {
3075
+ this.columns.forEach((column, i) => {
3076
+ column.check = false;
3077
+ column.check = this.savedTableSettings.includes(column.id);
3078
+ });
3079
+ this.columns.map((column, i) => {
3080
+ const control = new FormControl(column.check);
3081
+ this.form.controls.columns.push(control);
3082
+ });
3083
+ }
3084
+ }
3085
+ openTableSettings(table_settings_modal) {
3086
+ this.modalRef = this.modalService.show(table_settings_modal);
3087
+ }
3088
+ saveTableSettings() {
3089
+ const selectedColumnsIds = this.form.value.columns
3090
+ .map((v, i) => v ? this.columns[i].id : null)
3091
+ .filter(v => v !== null);
3092
+ this.userStorage.setItem(this.storageKey, selectedColumnsIds);
3093
+ if (this._activatedTable.activeTable) {
3094
+ this._activatedTable.activeTable.handleColumns(selectedColumnsIds);
3095
+ }
3096
+ this.modalRef.hide();
3097
+ }
3098
+ dismiss() {
3099
+ this.modalRef.hide();
3100
+ }
3101
+ minSelectedColumns(min = 1) {
3102
+ const validator = (formArray) => {
3103
+ const totalSelected = formArray.controls
3104
+ .map(control => control.value)
3105
+ .reduce((prev, next) => next ? prev + next : prev, 0);
3106
+ return totalSelected >= min ? null : { required: true };
3107
+ };
3108
+ return validator;
3109
+ }
3110
+ }
3111
+ AdvancedTableSettingsComponent.decorators = [
3112
+ { type: Component, args: [{
3113
+ selector: 'app-advanced-table-settings',
3114
+ template: "<i class=\"fa fa-edit fa-lg advanced-table-settings\" (click)=\"openTableSettings(modal_settings_template)\"></i>\r\n<ng-template #modal_settings_template>\r\n <div class=\"modal-header\">\r\n <h3 class=\"modal-title\" [translate]=\"'Tables.ColumnDisplay'\"></h3>\r\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"dismiss()\">\r\n <span aria-hidden=\"true\">&times;</span>\r\n </button>\r\n </div>\r\n\r\n <form class=\"form-group p-3 text-left\" [formGroup]=\"form\" (ngSubmit)=\"saveTableSettings()\">\r\n <div class=\"modal-body\">\r\n <p [translate]=\"'Tables.ColumnDisplayInfo'\"></p>\r\n <!-- <div *ngFor=\"let column of form.controls.columns.controls; let i = index\">\r\n <div formArrayName=\"columns\">\r\n <input class=\"checkbox checkbox-primary\" type=\"checkbox\" [formControlName]=\"i\" id=\"checkbox{{i}}\">\r\n <label for=\"checkbox{{i}}\">\r\n {{columns[i].name | translate}}\r\n </label>\r\n </div>\r\n </div> -->\r\n </div>\r\n <div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-light\" (click)=\"dismiss()\" [translate]=\"'Tables.Cancel'\"> </button>\r\n <button type=\"submit\" class=\"btn btn-success\" [translate]=\"'Tables.Save'\"></button>\r\n </div>\r\n </form>\r\n</ng-template>\r\n",
3115
+ encapsulation: ViewEncapsulation.None
3116
+ }] }
3117
+ ];
3118
+ /** @nocollapse */
3119
+ AdvancedTableSettingsComponent.ctorParameters = () => [
3120
+ { type: AngularDataContext },
3121
+ { type: ActivatedRoute },
3122
+ { type: ElementRef },
3123
+ { type: BsModalService },
3124
+ { type: UserStorageService },
3125
+ { type: FormBuilder },
3126
+ { type: ActivatedTableService }
3127
+ ];
3128
+ AdvancedTableSettingsComponent.propDecorators = {
3129
+ table: [{ type: Input }],
3130
+ modal_settings_template: [{ type: ViewChild, args: ['modal_settings_template',] }]
3131
+ };
3132
+
3133
+ const TABLES_EL = {
3134
+ "Tables": {
3135
+ "Export": "Εξαγωγή",
3136
+ "More": "Περισσότερα",
3137
+ "Search": "Αναζήτηση",
3138
+ "SearchPlaceholder": "Αναζήτηση...",
3139
+ "GroupBy": "Ομαδοποίηση ανά",
3140
+ "DataTable": {
3141
+ "paginate": {
3142
+ "first": "Πρώτη σελίδα",
3143
+ "last": "Τελεταία σελίδα",
3144
+ "next": "Επόμενη",
3145
+ "previous": "Προηγούμενη"
3146
+ },
3147
+ "info": "Εμφάνιση _START_ έως _END_ από _TOTAL_ εγγραφές",
3148
+ "infoEmpty": "Δεν βρέθηκαν εγγραφές",
3149
+ "loading": "Εμφάνιση δεδομένων...",
3150
+ "processing": "Παραλαβή δεδομένων...",
3151
+ "emptyTable": "Δεν βρέθηκαν αποτελέσματα."
3152
+ },
3153
+ "ColumnDisplay": "Επιλογή Εμφάνισης στηλών",
3154
+ "ColumnDisplayInfo": "Επιλέξτε τις στήλες που θέλετε να εμφανίζονται.",
3155
+ "Save": "Εφαρμογή",
3156
+ "Apply": "Εφαρμογή",
3157
+ "Cancel": "Ακύρωση",
3158
+ "RecordsPerPage": "Εγγραφές ανά σελίδα",
3159
+ "PageInfo": "{{start}}-{{end}} από {{recordsTotal}}",
3160
+ "SearchListPlaceholder": "Αναζητήστε στη λίστα...",
3161
+ "Filters": "Φίλτρα",
3162
+ "All": "Όλα",
3163
+ "AddItem": "Προσθήκη στοιχείου",
3164
+ "EmptyTableTitle": "Δεν βρέθηκαν αποτελέσματα.",
3165
+ "EmptyTableMessage": "Προσπαθήστε να χρησιμοποιήσετε άλλες λέξεις κλειδιά.",
3166
+ "RemoveItem": "Διαγραφή στοιχείου",
3167
+ "Items": "στοιχεία",
3168
+ "Item": "στοιχείο",
3169
+ "AddItemMessage": {
3170
+ "title": "Προσθήκη στοιχείων",
3171
+ "one": "Ένα νέο στοιχείο προστέθηκε με επιτυχία.",
3172
+ "many": "{{value}} νέα στοιχεία προστέθηκαν με επιτυχία."
3173
+ },
3174
+ "RemoveItemMessage": {
3175
+ "title": "Αφαίρεση στοιχείων",
3176
+ "one": "Ένα στοιχείο αφαιρέθηκε με επιτυχία.",
3177
+ "many": "{{value}} στοιχεία αφαιρέθηκαν με επιτυχία."
3178
+ },
3179
+ "RemoveItemsTitle": "Διαγραφή στοιχείου",
3180
+ "RemoveItemsMessage": "Πρόκειται να διαγράψετε ένα ή περισσότερα στοιχεία. Θέλετε να προχωρήσετε?",
3181
+ "SelectAll": "Επιλογή όλων",
3182
+ "ClearSelection": "Αναίρεση επιλογής",
3183
+ "Actions": {
3184
+ "Label": "Επεξεργασία",
3185
+ "Start": "Έναρξη",
3186
+ "ActionMessage": {
3187
+ "None": "Δεν έχετε επιλέξει κανένα στοιχείο που να ακολουθεί τις προϋποθέσεις της ενέργειας.",
3188
+ "One": "Έχετε επιλέξει ένα στοιχείο που ακολουθεί τις προϋποθέσεις της ενέργειας. Πατήστε το πλήκτρο Έναρξη για να ξεκινήσει η διαδικασία.",
3189
+ "Many": "Έχετε επιλέξει {{length}} στοιχεία που ακολουθούν τις προϋποθέσεις της ενέργειας. Πατήστε το πλήκτρο Έναρξη για να ξεκινήσει η διαδικασία."
3190
+ },
3191
+ "CompletedWithErrors": {
3192
+ "Title": "Η διαδικασία ολοκληρώθηκε με σφάλματα.",
3193
+ "Description": {
3194
+ "One": "Ένα στοιχείο δεν ανανεώθηκε λόγω ενός σφάλματος που συνέβη κατά την εκτέλεση της διαδικασίας.",
3195
+ "Many": "{{errors}} στοιχεία δεν ανανεώθηκαν λόγω ενός σφάλματος που συνέβη κατά την εκτέλεση της διαδικασίας."
3196
+ }
3197
+ },
3198
+ "CompletedWithSuccess": {
3199
+ "Title": "Η διαδικασία ολοκληρώθηκε με επιτυχία."
3200
+ }
3201
+ }
3202
+ },
3203
+ "TrueFalse": {
3204
+ "long": {
3205
+ "Yes": "Ναι",
3206
+ "No": "Όχι"
3207
+ }
3208
+ },
3209
+ "AdvancedSearch": {
3210
+ "Choose": "<Επιλέξτε μια αναζήτηση>",
3211
+ "ChooseDate": "Επιλογή ημερομηνίας",
3212
+ "SavedSearches": "Αποθηκευμένες αναζητήσεις",
3213
+ "ToggleDropdown": "Εμφάνιση λίστας",
3214
+ "SaveCurrentSearch": "Αποθήκευση αναζήτησης",
3215
+ "SaveAs": "Αποθήκευση ως",
3216
+ "Save": "Αποθήκευση",
3217
+ "Delete": "Διαγραφή",
3218
+ "More": "Περισσότερα",
3219
+ "Less": "Λιγότερα",
3220
+ "Modal": {
3221
+ "Title": "Αποθήκευση κριτηρίων αναζήτησης",
3222
+ "Help": "Ορίστε έναν τίτλο για τα επιλεγμένα κριτήρια αναζήτησης. Αυτός ο τίτλος θα σας βοηθήσει να τα χρησιμοποιήσετε ξανά στο μέλλον.",
3223
+ "TitleOnUserError": "Παρακαλώ εισάγετε έναν τίτλο",
3224
+ "TitleOnListAlreadyCreated": "Το όνομα αυτό χρησιμοποιείται ήδη. Διαλέξτε ένα διαφορετικό.",
3225
+ "NoListDefined": "Δεν έχει οριστεί το σημείο αποθήκευσης της λίστας",
3226
+ "EmptyCriteriaList": "Δεν έχετε καθορίσει κανένα κριτήριο για αποθήκευση",
3227
+ "SaveNameLabel": "Ονομασία λίστας",
3228
+ "SaveNamePlaceholder": "Εισάγετε έναν τίτλο",
3229
+ "Accept": "Αποθήκευση",
3230
+ "Decline": "Απόρριψη",
3231
+ "Close": "Κλείσιμο"
3232
+ }
3233
+ }
3234
+ };
3235
+
3236
+ const TABLES_EN = {
3237
+ "Tables": {
3238
+ "Export": "Export",
3239
+ "More": "More",
3240
+ "Search": "Search",
3241
+ "SearchPlaceholder": "Search...",
3242
+ "GroupBy": "Group by",
3243
+ "DataTable": {
3244
+ "paginate": {
3245
+ "first": "First",
3246
+ "last": "Last",
3247
+ "next": "Next",
3248
+ "previous": "Previous"
3249
+ },
3250
+ "info": "Showing _START_ to _END_ of _TOTAL_ entries",
3251
+ "infoEmpty": "Showing 0 to 0 of 0 entries",
3252
+ "processing": "Processing data...",
3253
+ "emptyTable": "There are no matching records."
3254
+ },
3255
+ "ColumnDisplay": "Column Display",
3256
+ "ColumnDisplayInfo": "Επιλέξτε τις στήλες που θέλετε να εμφανίζονται.",
3257
+ "Save": "Save",
3258
+ "Apply": "Apply",
3259
+ "Cancel": "Cancel",
3260
+ "RecordsPerPage": "Items per page",
3261
+ "PageInfo": "{{start}}-{{end}} of {{recordsTotal}}",
3262
+ "SearchListPlaceHolder": "Search in list...",
3263
+ "Filters": "Filters",
3264
+ "All": "All",
3265
+ "AddItem": "Add item",
3266
+ "EmptyTableTitle": "There are no matching records.",
3267
+ "EmptyTableMessage": "Please try to use other keywords or search parameters.",
3268
+ "RemoveItem": "Remove item",
3269
+ "Items": "items",
3270
+ "Item": "item",
3271
+ "AddItemMessage": {
3272
+ "title": "Add item",
3273
+ "one": "A new item has been added successfully.",
3274
+ "many": "{{value}} new items have been added successfully."
3275
+ },
3276
+ "RemoveItemMessage": {
3277
+ "title": "Remove items",
3278
+ "one": "An item has been successfully removed.",
3279
+ "many": "{{value}} items have been successfully removed."
3280
+ },
3281
+ "RemoveItemsTitle": "Remove items",
3282
+ "RemoveItemsMessage": "You are going to remove one or more instructor courses. Do you want to proceed?",
3283
+ "SelectAll": "Select all",
3284
+ "ClearSelection": "Clear selection",
3285
+ "Actions": {
3286
+ "Label": "Edit",
3287
+ "Start": "Start",
3288
+ "ActionMessage": {
3289
+ "None": "There is no selected item that meets action requirements.",
3290
+ "One": "You have selected one item that meets action requirements.",
3291
+ "Many": "You have selected {{length}} items that meet action requirements."
3292
+ },
3293
+ "CompletedWithErrors": {
3294
+ "Title": "The operation was completed with errors.",
3295
+ "Description": {
3296
+ "One": "One item has not been updated due to an error occurred while executing process.",
3297
+ "Many": "{{errors}} items have not been updated due to an error occurred while executing process."
3298
+ }
3299
+ },
3300
+ "CompletedWithSuccess": {
3301
+ "Title": "The operation was completed successfully."
3302
+ }
3303
+ }
3304
+ },
3305
+ "TrueFalse": {
3306
+ "long": {
3307
+ "Yes": "Yes",
3308
+ "No": "No"
3309
+ }
3310
+ },
3311
+ "AdvancedSearch": {
3312
+ "Choose": "<Select a saved search>",
3313
+ "ChooseDate": "Choose date",
3314
+ "SavedSearches": "Saved searches",
3315
+ "ToggleDropdown": "Toggle Dropdown",
3316
+ "SaveCurrentSearch": "Save search",
3317
+ "SaveAs": "Save as...",
3318
+ "Save": "Save",
3319
+ "Delete": "Remove",
3320
+ "More": "More",
3321
+ "Less": "Less",
3322
+ "Modal": {
3323
+ "Title": "Save current search criteria",
3324
+ "Help": "Please add a title for the given search criteria. This title will help you to use those criteria again in the future.",
3325
+ "TitleOnUserError": "Please use a valid name for your list",
3326
+ "TitleOnListAlreadyCreated": "Name already in use. Please choose a different one",
3327
+ "NoListDefined": "Cannot define user storage key.",
3328
+ "EmptyCriteriaList": "Please define search criteria before save.",
3329
+ "SaveNameLabel": "List name",
3330
+ "SaveNamePlaceholder": "Type a title here...",
3331
+ "Accept": "Save",
3332
+ "Decline": "Decline",
3333
+ "Close": "Close"
3334
+ }
3335
+ }
3336
+ };
3337
+
3338
+ class SimpleHeaderToolsComponent {
3339
+ }
3340
+ SimpleHeaderToolsComponent.decorators = [
3341
+ { type: Component, args: [{
3342
+ selector: 'table-simple-header-tools',
3343
+ template: `
3344
+ <ng-content></ng-content>
3345
+ `
3346
+ }] }
3347
+ ];
3348
+ class TableSimpleHeaderComponent {
3349
+ constructor() { }
3350
+ ngOnChanges(changes) {
3351
+ if (changes.table) {
3352
+ if (changes.table.currentValue == null) {
3353
+ if (this.dataLoadSubscription) {
3354
+ this.dataLoadSubscription.unsubscribe();
3355
+ return;
3356
+ }
3357
+ }
3358
+ if (changes.table.currentValue != changes.table.previousValue) {
3359
+ this.dataLoadSubscription = changes.table.currentValue.load.subscribe((data) => {
3360
+ this.recordsTotal = data.recordsTotal;
3361
+ });
3362
+ }
3363
+ }
3364
+ }
3365
+ ngOnDestroy() {
3366
+ if (this.dataLoadSubscription) {
3367
+ this.dataLoadSubscription.unsubscribe();
3368
+ }
3369
+ }
3370
+ ngOnInit() {
3371
+ //
3372
+ }
3373
+ }
3374
+ TableSimpleHeaderComponent.decorators = [
3375
+ { type: Component, args: [{
3376
+ selector: 'app-table-simple-header',
3377
+ template: "<div class=\"row\">\n <div class=\"col-auto mt-4\">\n <div class=\"font-xl text-secondary\" *ngIf=\"recordsTotal === 0\">\n <span>&nbsp;</span>\n </div>\n <div class=\"font-xl text-secondary\" *ngIf=\"recordsTotal > 1\">\n <span>{{recordsTotal}}</span><span>&nbsp;</span><span [translate]=\"'Tables.Items'\"></span>\n </div>\n <div class=\"font-xl text-secondary\" *ngIf=\"recordsTotal === 1\">\n <span>{{recordsTotal}}</span><span>&nbsp;</span><span [translate]=\"'Tables.Item'\"></span>\n </div>\n </div>\n <div class=\"pull-right ml-auto mr-3 mt-3\">\n <div class=\"font-lg btn-toolbar mt-2\">\n <ng-content select=\"table-simple-header-tools\"></ng-content>\n </div>\n </div>\n</div>\n\n"
3378
+ }] }
3379
+ ];
3380
+ /** @nocollapse */
3381
+ TableSimpleHeaderComponent.ctorParameters = () => [];
3382
+ TableSimpleHeaderComponent.propDecorators = {
3383
+ table: [{ type: Input }]
3384
+ };
3385
+
3386
+ class AdvancedTableNotNullError extends Error {
3387
+ constructor() {
3388
+ super('Advanced table component cannot be empty at this context.');
3389
+ }
3390
+ }
3391
+ class AdvancedTableEditorDirective {
3392
+ constructor(table) {
3393
+ this.table = table;
3394
+ this.dirty = false;
3395
+ this.dirtyChange = new EventEmitter(false);
3396
+ //
3397
+ }
3398
+ /**
3399
+ * Add rows to table and mark them as added rows
3400
+ */
3401
+ add(...addItems) {
3402
+ if (this.table == null) {
3403
+ throw new AdvancedTableNotNullError();
3404
+ }
3405
+ // decorate items
3406
+ const key = this.table.config.columns[0].property || this.table.config.columns[0].name;
3407
+ for (let index = 0; index < addItems.length; index++) {
3408
+ const element = addItems[index];
3409
+ let tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3410
+ if (tr.hasClass('removed')) {
3411
+ tr.toggleClass('removed text-gray-400 text-decoration-line-through');
3412
+ }
3413
+ else {
3414
+ this.table.dataTable.rows.add([element]);
3415
+ tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3416
+ tr.toggleClass('added font-weight-bold text-success');
3417
+ }
3418
+ }
3419
+ this.table.dataTable.draw();
3420
+ // find removed
3421
+ const removedLength = this.table.dataTable.rows('.removed')[0].length;
3422
+ // find added
3423
+ const addedLength = this.table.dataTable.rows('.added')[0].length;
3424
+ // and emit event
3425
+ const items = this.table.dataTable.rows().data();
3426
+ this.table.load.emit({
3427
+ data: items,
3428
+ recordsFiltered: items.length - removedLength,
3429
+ recordsTotal: items.length - removedLength
3430
+ });
3431
+ this.dirty = (removedLength > 0 || addedLength > 0);
3432
+ this.dirtyChange.emit(this.dirty);
3433
+ }
3434
+ /**
3435
+ * Marks rows as removed rows
3436
+ */
3437
+ remove(...removeItems) {
3438
+ if (this.table == null) {
3439
+ throw new AdvancedTableNotNullError();
3440
+ }
3441
+ // get key
3442
+ const key = this.table.config.columns[0].property || this.table.config.columns[0].name;
3443
+ // loop items
3444
+ for (let index = 0; index < removeItems.length; index++) {
3445
+ const element = removeItems[index];
3446
+ const tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3447
+ // if row is marked as added
3448
+ if (tr.hasClass('added')) {
3449
+ // remove this dirty row
3450
+ this.table.dataTable.rows(tr).remove();
3451
+ }
3452
+ else {
3453
+ // otherwise unmarked row
3454
+ tr.toggleClass('removed text-gray-400 text-decoration-line-through');
3455
+ }
3456
+ // check if item is selected
3457
+ const selectedIndex = this.table.selected.findIndex((item) => {
3458
+ return item[key] === element[key];
3459
+ });
3460
+ // and remove it from selected collection
3461
+ if (selectedIndex >= 0) {
3462
+ this.table.selected.splice(selectedIndex, 1);
3463
+ }
3464
+ }
3465
+ // re-draw table
3466
+ this.table.dataTable.draw();
3467
+ // get number of table items
3468
+ const items = this.table.dataTable.rows().data();
3469
+ // get number of removed items
3470
+ const removedLength = this.table.dataTable.rows('.removed')[0].length;
3471
+ // get number of added items
3472
+ const addedLength = this.table.dataTable.rows('.added')[0].length;
3473
+ // refresh
3474
+ this.table.load.emit({
3475
+ data: items,
3476
+ recordsFiltered: items.length - removedLength,
3477
+ recordsTotal: items.length - removedLength
3478
+ });
3479
+ this.dirty = (removedLength > 0 || addedLength > 0);
3480
+ this.dirtyChange.emit(this.dirty);
3481
+ }
3482
+ set(items) {
3483
+ if (this.table == null) {
3484
+ throw new AdvancedTableNotNullError();
3485
+ }
3486
+ // clear rows
3487
+ this.table.selected = [];
3488
+ this.table.dataTable.clear();
3489
+ // set items
3490
+ this.table.dataTable.rows.add(items).draw();
3491
+ this.table.load.emit({
3492
+ data: items,
3493
+ recordsFiltered: items.length,
3494
+ recordsTotal: items.length
3495
+ });
3496
+ }
3497
+ /**
3498
+ * Undo changes
3499
+ */
3500
+ undo() {
3501
+ if (this.table == null) {
3502
+ throw new AdvancedTableNotNullError();
3503
+ }
3504
+ // reset removed rows
3505
+ this.table.dataTable.rows('.removed').nodes().to$().toggleClass('removed text-gray-400 text-decoration-line-through');
3506
+ // remove added rows
3507
+ this.table.dataTable.rows('.added').remove();
3508
+ // clear selected items
3509
+ this.table.selected = [];
3510
+ const items = this.table.dataTable.rows().data();
3511
+ this.table.dataTable.draw();
3512
+ this.table.load.emit({
3513
+ data: items,
3514
+ recordsFiltered: items.length,
3515
+ recordsTotal: items.length
3516
+ });
3517
+ this.dirty = false;
3518
+ this.dirtyChange.emit(this.dirty);
3519
+ }
3520
+ rows() {
3521
+ if (this.table == null) {
3522
+ throw new AdvancedTableNotNullError();
3523
+ }
3524
+ return Array.from(this.table.dataTable.rows(':not(.removed)').data());
3525
+ }
3526
+ }
3527
+ AdvancedTableEditorDirective.decorators = [
3528
+ { type: Directive, args: [{
3529
+ selector: '[tableEditor]'
3530
+ },] }
3531
+ ];
3532
+ /** @nocollapse */
3533
+ AdvancedTableEditorDirective.ctorParameters = () => [
3534
+ { type: AdvancedTableComponent }
3535
+ ];
3536
+ AdvancedTableEditorDirective.propDecorators = {
3537
+ dirtyChange: [{ type: Output }]
3538
+ };
3539
+
3540
+ class AdvancedAggregatorService {
3541
+ constructor(_context, _template) {
3542
+ this._context = _context;
3543
+ this._template = _template;
3544
+ }
3545
+ /**
3546
+ *
3547
+ * Formats and executes the data query
3548
+ *
3549
+ * @param {string} model The target model
3550
+ * @param {object} fixedParams Pairs of parameters to always be applied
3551
+ * @param {string} queryStatement The default query to use for fetching the data (e.g. expands etc)
3552
+ * @param {Array<string>} filters A list of filter expressions to append at the query
3553
+ * @param {string} searchTemplate The template to use for searching expressions
3554
+ * @param {string} searchValue The value of the user search
3555
+ *
3556
+ * @returns {Promise<any>} The response of the query execution
3557
+ */
3558
+ fetchData(model, fixedParams, queryStatement = '', filters, searchTemplate, searchValue) {
3559
+ return __awaiter(this, void 0, void 0, function* () {
3560
+ const query = this._context.model(model).asQueryable();
3561
+ if (!fixedParams || typeof fixedParams !== 'object') {
3562
+ fixedParams = {};
3563
+ }
3564
+ let filterExpressions = [];
3565
+ if (fixedParams) {
3566
+ for (let param of Object.keys(fixedParams)) {
3567
+ filterExpressions.push(`${param} eq ${fixedParams[param]}`);
3568
+ }
3569
+ }
3570
+ if (filters && Array.isArray(filters)) {
3571
+ filterExpressions = [
3572
+ ...filterExpressions,
3573
+ ...filters
3574
+ ];
3575
+ }
3576
+ if (searchTemplate && searchValue) {
3577
+ let evaluatedSearch;
3578
+ if (/^"(.*?)"$/.test(searchValue)) { // validate search text in double quotes
3579
+ evaluatedSearch = this._template.transform(searchTemplate, Object.assign({}, fixedParams, { text: searchValue.replace(/^"|"$/g, '') }));
3580
+ }
3581
+ else {
3582
+ // try to split words
3583
+ const words = searchValue.split(' ');
3584
+ // join words to a single filter
3585
+ evaluatedSearch = words.map(word => {
3586
+ return this._template.transform(searchTemplate, Object.assign({}, fixedParams, { text: word }));
3587
+ }).join(' and ');
3588
+ }
3589
+ filterExpressions.push(evaluatedSearch);
3590
+ }
3591
+ query.setParam('$filter', filterExpressions.join(' and '));
3592
+ return query.expand(queryStatement).take(-1).getItems();
3593
+ });
3594
+ }
3595
+ /**
3596
+ *
3597
+ * Formats the received data to use flat keys
3598
+ *
3599
+ * @param config The applied configuration
3600
+ * @param data The raw data
3601
+ *
3602
+ * @returns {Array<any>} The flat-key object list
3603
+ *
3604
+ */
3605
+ formatData(config, data) {
3606
+ try {
3607
+ return data.map((entry) => {
3608
+ let entryObject = {};
3609
+ for (let i = 0; i < config.columns.length; i++) {
3610
+ const keys = config.columns[i].name.split('/');
3611
+ let helperObject = entry[keys[0]];
3612
+ if (helperObject && keys.length > 1) {
3613
+ for (let i = 1; i < keys.length; i++) {
3614
+ if (helperObject === undefined || helperObject === null) {
3615
+ break;
3616
+ }
3617
+ else {
3618
+ helperObject = helperObject[keys[i]] !== undefined && helperObject[keys[i]] !== null
3619
+ ? helperObject[keys[i]]
3620
+ : undefined;
3621
+ }
3622
+ }
3623
+ }
3624
+ entryObject[config.columns[i].name] = helperObject;
3625
+ }
3626
+ if (entry.subitems) {
3627
+ entryObject.subitems = entry.subitems;
3628
+ }
3629
+ return entryObject;
3630
+ });
3631
+ }
3632
+ catch (err) {
3633
+ console.error(err);
3634
+ }
3635
+ }
3636
+ }
3637
+ AdvancedAggregatorService.decorators = [
3638
+ { type: Injectable, args: [{
3639
+ providedIn: 'root'
3640
+ },] }
3641
+ ];
3642
+ /** @nocollapse */
3643
+ AdvancedAggregatorService.ctorParameters = () => [
3644
+ { type: AngularDataContext },
3645
+ { type: TemplatePipe }
3646
+ ];
3647
+ AdvancedAggregatorService.ngInjectableDef = defineInjectable({ factory: function AdvancedAggregatorService_Factory() { return new AdvancedAggregatorService(inject(AngularDataContext), inject(TemplatePipe)); }, token: AdvancedAggregatorService, providedIn: "root" });
3648
+
3649
+ /**
3650
+ *
3651
+ * @class AdvancedAggregatorComponent
3652
+ * @classdesc An advanced list with the capability of aggregate data based on configurable filters
3653
+ *
3654
+ */
3655
+ class AdvancedAggregatorComponent {
3656
+ constructor(_changeDetector, _advancedAggregatorService, _loadingService) {
3657
+ this._changeDetector = _changeDetector;
3658
+ this._advancedAggregatorService = _advancedAggregatorService;
3659
+ this._loadingService = _loadingService;
3660
+ this.groups = [];
3661
+ this.eventHandler = new EventEmitter();
3662
+ }
3663
+ set config(value) {
3664
+ this._config = value;
3665
+ }
3666
+ set aggregators(value) {
3667
+ this._aggregators = value;
3668
+ }
3669
+ set postProcessors(value) {
3670
+ this._postProcessors = value;
3671
+ }
3672
+ get config() {
3673
+ return this._config;
3674
+ }
3675
+ get aggregators() {
3676
+ return this._aggregators;
3677
+ }
3678
+ get postProcessors() {
3679
+ return this._postProcessors;
3680
+ }
3681
+ ngOnInit() {
3682
+ return __awaiter(this, void 0, void 0, function* () {
3683
+ this.selectedGroups$.subscribe((data) => {
3684
+ this.groups = data;
3685
+ this._changeDetector.markForCheck();
3686
+ });
3687
+ this.filters$.subscribe((filters) => __awaiter(this, void 0, void 0, function* () {
3688
+ try {
3689
+ yield this.fetch(filters);
3690
+ }
3691
+ catch (err) {
3692
+ console.error(err);
3693
+ }
3694
+ }));
3695
+ });
3696
+ }
3697
+ ngOnDestroy() {
3698
+ return __awaiter(this, void 0, void 0, function* () {
3699
+ this.selectedGroups$.unsubscribe();
3700
+ this.filters$.unsubscribe();
3701
+ });
3702
+ }
3703
+ /**
3704
+ *
3705
+ * Retrieves the data
3706
+ *
3707
+ */
3708
+ fetch(filters) {
3709
+ return __awaiter(this, void 0, void 0, function* () {
3710
+ try {
3711
+ this._loadingService.showLoading();
3712
+ let data = yield this._advancedAggregatorService.fetchData(this.config.model, this.params, this.config.query, filters.filters, this.searchExpression, filters.text);
3713
+ if (this.postProcessors && Object.keys(this.postProcessors).length > 0 && this.config.postProcessors && this.config.postProcessors.length > 0) {
3714
+ for (let i = 0; i < this.config.postProcessors.length; i++) {
3715
+ data = yield this.postProcessors[this.config.postProcessors[i].name](data);
3716
+ }
3717
+ }
3718
+ this.body = this._advancedAggregatorService.formatData(this.config, data);
3719
+ this._changeDetector.markForCheck();
3720
+ }
3721
+ catch (err) {
3722
+ console.error(err);
3723
+ }
3724
+ finally {
3725
+ this._loadingService.hideLoading();
3726
+ }
3727
+ });
3728
+ }
3729
+ /**
3730
+ *
3731
+ * Triggers the event handler event
3732
+ *
3733
+ * @param {*} event The event data
3734
+ *
3735
+ */
3736
+ onActionEvent(event) {
3737
+ this.eventHandler.emit(event);
3738
+ }
3739
+ }
3740
+ AdvancedAggregatorComponent.decorators = [
3741
+ { type: Component, args: [{
3742
+ selector: 'app-advanced-aggregator',
3743
+ template: "<ng-container *ngIf=\"config && body\">\n <div class=\"w-100 text-center\" *ngIf=\"body && body.length === 0\">\n <div>\n <div class=\"mt-4\">\n <div class=\"icon-circle bg-gray-300 border-gray-100\"></div>\n </div>\n <div class=\"mt-4\">\n <h4 class=\"font-3xl text-dark\" [translate]=\"'Tables.EmptyTableTitle'\"></h4>\n <p class=\"font-lg text-dark\" [translate]=\"'Tables.EmptyTableMessage'\"></p>\n </div>\n </div>\n </div>\n <ng-container *ngIf=\"groups.length === 0\">\n <div class=\"row mt-4\">\n <app-advanced-aggregator-list [body]=\"body\" [config]=\"config\" (clickEvent)=\"onActionEvent($event)\" class=\"col-12\">\n </app-advanced-aggregator-list>\n <div *ngIf=\"config.aggregators && config.aggregators.length > 0\"\n class=\"footer-text text-dark font-weight-normal text-right font-sm mt-3 mr-2 w-100\">\n <div *ngFor=\"let aggregator of config.aggregators\">\n <span *ngIf=\"aggregator.label\"> {{ aggregator.label | translate }} </span>:\n <span class=\"font-weight-bold\"> {{ body | callAggregator: aggregators: aggregator.name }}\n </span>\n </div>\n </div>\n </div>\n </ng-container>\n <ng-container *ngIf=\"groups.length === 1\">\n <ng-container *ngFor=\"let group of body | groupBy: groups[0].name | keyvalue\">\n <div class=\"row mt-4\">\n <h4 class=\"col text-secondary\"> {{ groups[0].title | aggregatorListTitle: group.key }} </h4>\n <app-advanced-aggregator-list [body]=\"group.value\" [config]=\"config\" (clickEvent)=\"onActionEvent($event)\"\n class=\"col-12\">\n </app-advanced-aggregator-list>\n </div>\n\n <!-- Footer for the group -->\n <div *ngIf=\"config.aggregators && config.aggregators.length > 0\"\n class=\"footer-text text-dark font-weight-normal text-right font-sm mt-3 mr-2 w-100\">\n <div *ngFor=\"let aggregator of config.aggregators\">\n <span *ngIf=\"aggregator.label\"> {{ aggregator.label | translate }} </span>:\n <span class=\"font-weight-bold\"> {{ group.value | callAggregator: aggregators: aggregator.name }} </span>\n </div>\n </div>\n </ng-container>\n </ng-container>\n <ng-container *ngIf=\"groups.length > 1\">\n <ng-container *ngFor=\"let group of body | groupBy: groups[0].name | keyvalue\">\n <div class=\"row mt-4\">\n <h4 class=\"col text-secondary\"> {{ groups[0].title | aggregatorListTitle: group.key }} </h4>\n <div *ngFor=\"let subgroup of group.value | groupBy: groups[1].name | keyvalue\" class=\"col-12 ml-4 pl-4\">\n <div class=\"row mt-4\">\n <h6 class=\"col-12 text-secondary\"> {{ groups[1].title | aggregatorListTitle: subgroup.key }} </h6>\n <app-advanced-aggregator-list [body]=\"subgroup.value\" [config]=\"config\" (clickEvent)=\"onActionEvent($event)\"\n class=\"col-12\">\n </app-advanced-aggregator-list>\n </div>\n\n <!-- Footer for each subgroup -->\n <div *ngIf=\"config.aggregators && config.aggregators.length > 0\"\n class=\"footer-text text-dark font-weight-normal text-right font-sm mt-3 mr-2 w-100\">\n <div *ngFor=\"let aggregator of config.aggregators\">\n <span *ngIf=\"aggregator.label\"> {{ aggregator.label | translate }} </span>:\n <span class=\"font-weight-bold\"> {{ subgroup.value | callAggregator: aggregators: aggregator.name }}\n </span>\n </div>\n </div>\n </div>\n </div>\n </ng-container>\n </ng-container>\n</ng-container>",
3744
+ changeDetection: ChangeDetectionStrategy.OnPush
3745
+ }] }
3746
+ ];
3747
+ /** @nocollapse */
3748
+ AdvancedAggregatorComponent.ctorParameters = () => [
3749
+ { type: ChangeDetectorRef },
3750
+ { type: AdvancedAggregatorService },
3751
+ { type: LoadingService }
3752
+ ];
3753
+ AdvancedAggregatorComponent.propDecorators = {
3754
+ config: [{ type: Input, args: ['config',] }],
3755
+ aggregators: [{ type: Input, args: ['aggregators',] }],
3756
+ postProcessors: [{ type: Input, args: ['postProcessors',] }],
3757
+ params: [{ type: Input, args: ['params',] }],
3758
+ selectedGroups$: [{ type: Input, args: ['selectedGroups',] }],
3759
+ filters$: [{ type: Input, args: ['filters',] }],
3760
+ searchExpression: [{ type: Input, args: ['searchExpression',] }],
3761
+ eventHandler: [{ type: Output }]
3762
+ };
3763
+
3764
+ var Lengths;
3765
+ (function (Lengths) {
3766
+ Lengths["XL"] = "xl";
3767
+ Lengths["LG"] = "lg";
3768
+ Lengths["MD"] = "md";
3769
+ Lengths["SM"] = "sm";
3770
+ Lengths["XS"] = "xs";
3771
+ })(Lengths || (Lengths = {}));
3772
+ class AdvancedAggregatorListComponent {
3773
+ constructor(_template) {
3774
+ this._template = _template;
3775
+ this.clickEvent = new EventEmitter();
3776
+ }
3777
+ ngOnInit() {
3778
+ this.config.columns.forEach(x => {
3779
+ x.length = Object.values(Lengths).includes(x.length) ? this._template.transform(`sis--list-group-item_${x.length}_column`, x) :
3780
+ !x.length ? 'sis--list-group-item_sm_column' : x.length;
3781
+ });
3782
+ }
3783
+ /**
3784
+ *
3785
+ * Triggers the output event emitter
3786
+ *
3787
+ * @param type The type of the event as it's declared in the configuration
3788
+ * @param item The value of the current item
3789
+ * }
3790
+ */
3791
+ callCallback(type, data) {
3792
+ this.clickEvent.emit({
3793
+ type,
3794
+ data
3795
+ });
3796
+ }
3797
+ }
3798
+ AdvancedAggregatorListComponent.decorators = [
3799
+ { type: Component, args: [{
3800
+ selector: 'app-advanced-aggregator-list',
3801
+ template: "<div id=\"group-list\" class=\"flex-column\" *ngIf=\"config && config.columns\">\n <ul class=\"sis--list-group sis--list-group-header\">\n <li class=\"sis--list-group-item text-wrap\">\n <div class=\"d-flex justify-content-center align-items-center\" *ngIf=\"body && body.length > 0\">\n <div *ngIf=\"config.actions && config.actions.length > 0\" class=\"sis--list-group-item_sm_column\"></div>\n <div *ngFor=\"let column of config.columns; let i = index\" class=\"text-left\"\n [class]=\"config.columns[i].length\"\n [translate]=\"config.columns[i].type === 'action' ? '' : column.title\" [ngClass]=\"{\n 'd-none': config.columns[i].hidden\n }\"></div>\n </div>\n </li>\n </ul>\n\n <!-- The list of entries -->\n <ul class=\"sis--list-group sis--list-group-body\" *ngIf=\"body && body.length > 0\">\n <li class=\"sis--list-group-item text-wrap\" *ngFor=\"let entry of body | orderBy: config.orderBy\">\n <div class=\"d-flex justify-content-center align-items-center list-color\">\n <!-- The list of actions -->\n <div *ngIf=\"config.actions && config.actions.length > 0\" class=\"sis--list-group-item_sm_column text-left pr-0\">\n <button *ngFor=\"let action of config.actions\" (click)=\"callCallback(action.identifier, entry)\"\n [title]=\"(action.title ? action.title : '') | translate \" class=\"btn btn-default px-0\">\n <i [class]=\"'fas text-indigo ml-2 ' + 'fa-' + action.icon\"></i>\n </button>\n </div>\n <!-- The list of data -->\n <div *ngFor=\"let column of config.columns; let i=index\" [class]=\"config.columns[i].length\" class=\"text-left\"\n [ngClass]=\"{\n 'text-truncate': config.columns[i].hasOwnProperty('truncate') && config.columns[i].truncate,\n 'd-none': config.columns[i].hidden,\n 'd-md-block': !config.columns[i].hidden\n }\"\n [title]=\"config.columns[i].hasOwnProperty('truncate') && config.columns[i].truncate ? entry[column.name] : ''\">\n <span class=\"text-secondary\"\n *ngIf=\"!config.columns[i].hasOwnProperty('type') || (config.columns[i].hasOwnProperty('type') && config.columns[i].type === 'text')\"\n [ngClass]=\"{\n 'font-xs': config.columns[i].variant !== 'primary',\n 'text-body': config.columns[i].variant === 'primary',\n 'text-success': config.columns[i].variant === 'redGreen' && config.columns[i].hasOwnProperty('condition') && entry[config.columns[i].condition],\n 'text-danger': config.columns[i].variant === 'redGreen' && config.columns[i].hasOwnProperty('condition') && !entry[config.columns[i].condition],\n 'font-lg': config.columns[i].variant === 'redGreen' && entry[column.name] && entry[column.name].length > 3,\n 'font-2xl': config.columns[i].variant === 'redGreen' && entry[column.name] && entry[column.name].length <= 3\n }\">\n {{entry[column.name] || '-'}}\n </span>\n\n <span *ngIf=\"config.columns[i].type === 'boolean'\">\n <i class=\"fa font-lg\" [ngClass]=\"{\n 'fa-check text-success': entry[column.name].toString() === config.columns[i].trueValue,\n 'fa-times text-danger': entry[column.name].toString() !== config.columns[i].trueValue && config.columns[i].variant !== 'checkOrDash'\n }\"></i>\n <span\n *ngIf=\"entry[column.name].toString() !== config.columns[i].trueValue && config.columns[i].variant === 'checkOrDash'\"\n class=\"text-secondary font-xs\">-</span>\n </span>\n </div>\n </div>\n <ul *ngIf=\"entry && entry.subitems\">\n <li *ngFor=\"let subitem of entry.subitems\" class=\"d-flex justify-content-center align-items-center list-color\">\n <div class=\"text-left\"></div>\n <div *ngFor=\"let column of config.columns; let i=index\" [class]=\"config.column[i].length\" class=\"text-left\"\n [ngClass]=\"{\n 'text-truncate': config.columns[i].hasOwnProperty('truncate') && config.columns[i].truncate,\n 'd-none': config.columns[i].hidden,\n 'd-md-block': !config.columns[i].hidden\n }\"\n [title]=\"config.columns[i].hasOwnProperty('truncate') && config.columns[i].truncate ? entry[column.name] : ''\">\n <span class=\"text-secondary\"\n *ngIf=\"!config.columns[i].hasOwnProperty('type') || (config.columns[i].hasOwnProperty('type') && config.columns[i].type === 'text')\"\n [ngClass]=\"{\n 'font-xs': config.columns[i].variant !== 'primary',\n 'text-body': config.columns[i].variant === 'primary',\n 'text-success': config.columns[i].variant === 'redGreen' && config.columns[i].hasOwnProperty('condition') && subitem[config.columns[i].condition],\n 'text-danger': config.columns[i].variant === 'redGreen' && config.columns[i].hasOwnProperty('condition') && !subitem[config.columns[i].condition],\n 'font-lg': config.columns[i].variant === 'redGreen' && subitem[column.name] && subitem[column.name].length > 3,\n 'font-2xl': config.columns[i].variant === 'redGreen' && subitem[column.name] && subitem[column.name].length <= 3\n }\">\n {{subitem[column.name] || '-'}}\n </span>\n </div>\n </li>\n </ul>\n </li>\n </ul>\n</div>\n"
3802
+ }] }
3803
+ ];
3804
+ /** @nocollapse */
3805
+ AdvancedAggregatorListComponent.ctorParameters = () => [
3806
+ { type: TemplatePipe }
3807
+ ];
3808
+ AdvancedAggregatorListComponent.propDecorators = {
3809
+ config: [{ type: Input }],
3810
+ body: [{ type: Input }],
3811
+ clickEvent: [{ type: Output }]
3812
+ };
3813
+
3814
+ /**
3815
+ *
3816
+ * @class AdvancedAggregatorPipe
3817
+ * @classdesc Given a list of items runs a user defined function against that list
3818
+ *
3819
+ */
3820
+ class AdvancedAggregatorPipe {
3821
+ /**
3822
+ *
3823
+ * @override
3824
+ *
3825
+ * @param items The list of items to run the aggregator against
3826
+ * @param functionPool The functions that are available from the list caller
3827
+ * @param functionName The name of the function to be called
3828
+ *
3829
+ * @returns {string} The value to show at the UI
3830
+ *
3831
+ */
3832
+ transform(items, functionPool, functionName) {
3833
+ if (Array.isArray(items) && functionName && functionName in functionPool) {
3834
+ return functionPool[functionName](items);
3835
+ }
3836
+ else {
3837
+ return '-';
3838
+ }
3839
+ }
3840
+ }
3841
+ AdvancedAggregatorPipe.decorators = [
3842
+ { type: Pipe, args: [{
3843
+ name: 'callAggregator'
3844
+ },] }
3845
+ ];
3846
+
3847
+ /**
3848
+ *
3849
+ * @class ParseTitlePipe
3850
+ * @classdesc Given a title of an aggregator list, translates and evaluates the parameter if any
3851
+ *
3852
+ */
3853
+ class ParseTitlePipe {
3854
+ constructor(_translateService) {
3855
+ this._translateService = _translateService;
3856
+ }
3857
+ /**
3858
+ *
3859
+ * @override
3860
+ *
3861
+ * @param {string} title The title of the list
3862
+ * @param {string} key The current list key that is being used
3863
+ *
3864
+ */
3865
+ transform(title, key) {
3866
+ if (!title) {
3867
+ return '';
3868
+ }
3869
+ else {
3870
+ if (key === undefined || key === null || key === 'undefined' || key === 'null') {
3871
+ key = this._translateService.instant('Tables.NotDefined');
3872
+ }
3873
+ return title.split(' ')
3874
+ .map((part) => this._translateService.instant(part))
3875
+ .map((part) => part.replace('${param}', key))
3876
+ .join(' ');
3877
+ }
3878
+ }
3879
+ }
3880
+ ParseTitlePipe.decorators = [
3881
+ { type: Pipe, args: [{
3882
+ name: 'aggregatorListTitle'
3883
+ },] }
3884
+ ];
3885
+ /** @nocollapse */
3886
+ ParseTitlePipe.ctorParameters = () => [
3887
+ { type: TranslateService }
3888
+ ];
3889
+
3890
+ class AddItemsComponent extends RouterModalOkCancel {
3891
+ constructor(router, activatedRoute, _errorService, _modalService, _appEvent, _toastService, _loadingService, _translateService, _context) {
3892
+ super(router, activatedRoute);
3893
+ this._errorService = _errorService;
3894
+ this._modalService = _modalService;
3895
+ this._appEvent = _appEvent;
3896
+ this._toastService = _toastService;
3897
+ this._loadingService = _loadingService;
3898
+ this._translateService = _translateService;
3899
+ this._context = _context;
3900
+ this.loading = false;
3901
+ this.items = [];
3902
+ this.messages = [];
3903
+ // set modal size
3904
+ this.modalClass = 'modal-lg';
3905
+ this.okButtonDisabled = true;
3906
+ }
3907
+ ngOnInit() {
3908
+ this.selectedSubscription = this.selectComponent.advancedTable.selectedItems.subscribe((items) => {
3909
+ if (this.formName == null) {
3910
+ this.okButtonDisabled = items.length === 0;
3911
+ }
3912
+ });
3913
+ if (this.formName != null) {
3914
+ this.formChangeSubscription = this.formComponent.form.change.subscribe((event) => {
3915
+ if (Object.prototype.hasOwnProperty.call(event, 'isValid')) {
3916
+ // enable or disable button based on form status
3917
+ this.okButtonDisabled = (!event.isValid);
3918
+ }
3919
+ });
3920
+ }
3921
+ if (this.target) {
3922
+ const target = this.target;
3923
+ }
3924
+ }
3925
+ cancel() {
3926
+ this.clearMessages();
3927
+ if (this.loading) {
3928
+ return;
3929
+ }
3930
+ // close
3931
+ if (this._modalService.modalRef) {
3932
+ return this._modalService.modalRef.hide();
3933
+ }
3934
+ }
3935
+ ngOnDestroy() {
3936
+ if (this.formLoadSubscription) {
3937
+ this.formLoadSubscription.unsubscribe();
3938
+ }
3939
+ if (this.formChangeSubscription) {
3940
+ this.formChangeSubscription.unsubscribe();
3941
+ }
3942
+ if (this.selectedSubscription) {
3943
+ this.selectedSubscription.unsubscribe();
3944
+ }
3945
+ }
3946
+ get formData() {
3947
+ if (this.formComponent === null) {
3948
+ return null;
3949
+ }
3950
+ return this.formComponent.form.formio.data;
3951
+ }
3952
+ ok() {
3953
+ try {
3954
+ this.clearMessages();
3955
+ return new Promise((resolve, reject) => {
3956
+ this.loading = false;
3957
+ this.lastError = null;
3958
+ // set courses
3959
+ this.items = this.selectComponent.advancedTable.selected;
3960
+ // execute add
3961
+ this.executeSubscription = this.execute.subscribe((result) => {
3962
+ this.loading = false;
3963
+ if (this.executeSubscription) {
3964
+ this.executeSubscription.unsubscribe();
3965
+ }
3966
+ // close modal
3967
+ if (this._modalService.modalRef) {
3968
+ this._modalService.modalRef.hide();
3969
+ }
3970
+ return resolve();
3971
+ }, (err) => {
3972
+ this.loading = false;
3973
+ // ensure that loading is hidden
3974
+ this._loadingService.hideLoading();
3975
+ // set last error
3976
+ this.lastError = err;
3977
+ return resolve();
3978
+ });
3979
+ });
3980
+ }
3981
+ catch (err) {
3982
+ this.loading = false;
3983
+ this._loadingService.hideLoading();
3984
+ this.lastError = err;
3985
+ }
3986
+ }
3987
+ onChange($event) { }
3988
+ clearMessages() {
3989
+ this.messages = [];
3990
+ }
3991
+ onFilterChange(event) { }
3992
+ }
3993
+ AddItemsComponent.decorators = [
3994
+ { type: Component, args: [{
3995
+ selector: 'app-add-items',
3996
+ template: "<div>\n <div class=\"modal-header\" *ngIf=\"modalTitle\">\n <h5 class=\"modal-title\">{{modalTitle | translate}}</h5>\n <button (click)=\"cancel()\" [disabled]=\"loading\"\n type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Cancel\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n <div class=\"modal-body pt-0\">\n <div class=\"row\" *ngIf=\"lastError\">\n <div class=\"alert alert-danger col-12\" role=\"alert\">\n <span [translate]=\"'E' + (lastError.statusCode || 500) + '.title'\"></span><br>\n <small *ngIf=\"lastError.statusCode >= 0\" [translate]=\"'E' + (lastError.statusCode || 500) + '.message'\"></small>\n <small *ngIf=\"lastError.statusCode == null\">{{lastError.message}}</small>\n </div>\n </div>\n <div class=\"row d-none\" *ngIf=\"items\">\n <div class=\"col-12\">\n <p [translate]=\"'StudyPrograms.AddCoursesHelp'\" [translateParams]=\"items\" ></p>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-12\">\n <ng-container>\n <ul style=\"margin-left: -24px\" class=\"nav nav-tabs mb-1\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\" *ngIf=\"tableConfiguration\">\n <a class=\"nav-link active\" id=\"courses-tab\" data-toggle=\"tab\" href=\"#courses\" role=\"tab\" aria-controls=\"home\" aria-selected=\"true\" [translate]=\"'AddItems.SelectCourses'\" ></a>\n </li>\n <li class=\"nav-item\" *ngIf=\"formName\">\n <a class=\"nav-link\" [ngClass]=\"{ 'disabled': !(selectComponent.advancedTable.selected && selectComponent.advancedTable.selected.length) }\" id=\"attributes-tab\" data-toggle=\"tab\" href=\"#attributes\" role=\"tab\" aria-controls=\"profile\" aria-selected=\"false\" [translate]=\"'AddItems.EditAttributes'\"></a>\n </li>\n <li class=\"nav-item\" *ngIf=\"messages && messages.length > 0\">\n <a class=\"nav-link\" id=\"messages-tab\" data-toggle=\"tab\" href=\"#messages\" role=\"tab\" aria-controls=\"profile\" aria-selected=\"false\" [translate]=\"'AddItems.Messages'\"></a>\n </li>\n </ul>\n <div class=\"tab-content border-0\" id=\"myTabContent\">\n <div class=\"tab-pane fade show active\" id=\"courses\" role=\"tabpanel\" aria-labelledby=\"home-tab\">\n <app-advanced-table-modal #selectComponent (filter)=\"onFilterChange($event)\" [tableConfig]=\"tableConfiguration\"></app-advanced-table-modal>\n </div>\n <div class=\"tab-pane fade\" id=\"attributes\" role=\"tabpanel\" aria-labelledby=\"profile-tab\"\n [ngClass]=\"{'invisible':(formName == null)}\">\n<!-- <advanced-form-edit #formComponent [formName]=\"formName\" ></advanced-form-edit>-->\n </div>\n <div class=\"tab-pane fade\" id=\"messages\" role=\"tabpanel\" aria-labelledby=\"profile-tab\"\n [ngClass]=\"{'invisible':(messages == null)}\">\n <ul *ngIf=\"messages && messages.length>0\" class=\"sis--list-group sis--list-group-header\">\n <li class=\"sis--list-group-item\">\n <div class=\"d-flex justify-content-center align-items-center\">\n <div class=\"sis--list-group-item_lg_column\" [translate]=\"'AddItems.Messages'\"></div>\n </div>\n </li>\n </ul>\n <ul class=\"sis--list-group sis--list-group-body\">\n <li *ngFor=\"let message of messages\" class=\"sis--list-group-item\">\n <div class=\"d-flex justify-content-center align-items-center list-color\">\n <div class=\"sis--list-group-item_lg_column\">{{ message?.message }}</div>\n </div>\n </li>\n </ul>\n <div *ngIf=\"messages && messages.length>0\">\n <button [disabled]=\"loading\" (click)=\"clearMessages()\" type=\"button\" class=\"btn btn-indigo mt-3\">\n {{'Clear' | translate}}\n </button>\n </div>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n </div>\n <div class=\"modal-footer justify-content-end\">\n <button [disabled]=\"okButtonDisabled || loading || !(selectComponent.advancedTable.selected && selectComponent.advancedTable.selected.length) \" (click)=\"ok()\"\n type=\"button\" [ngClass]=\"okButtonClass\">\n {{'OK' | translate}}\n </button>\n <button [disabled]=\"cancelButtonDisabled || loading\"\n (click)=\"cancel()\" type=\"button\" [ngClass]=\"cancelButtonClass\" data-dismiss=\"modal\">\n {{'Cancel' | translate}}\n </button>\n </div>\n</div>\n"
3997
+ }] }
3998
+ ];
3999
+ /** @nocollapse */
4000
+ AddItemsComponent.ctorParameters = () => [
4001
+ { type: Router },
4002
+ { type: ActivatedRoute },
4003
+ { type: ErrorService },
4004
+ { type: ModalService },
4005
+ { type: AppEventService },
4006
+ { type: ToastService },
4007
+ { type: LoadingService },
4008
+ { type: TranslateService },
4009
+ { type: AngularDataContext }
4010
+ ];
4011
+ AddItemsComponent.propDecorators = {
4012
+ execute: [{ type: Input }],
4013
+ items: [{ type: Input }],
4014
+ target: [{ type: Input }],
4015
+ tableConfiguration: [{ type: Input }],
4016
+ formName: [{ type: Input }],
4017
+ messages: [{ type: Input }],
4018
+ formComponent: [{ type: ViewChild, args: ['formComponent',] }],
4019
+ selectComponent: [{ type: ViewChild, args: ['selectComponent',] }]
4020
+ };
4021
+
4022
+ const ɵ0 = FORMATTERS;
4023
+ class TablesModule {
4024
+ constructor(_translateService) {
4025
+ this._translateService = _translateService;
4026
+ this.ngOnInit().catch(err => {
4027
+ console.error('An error occurred while loading tables module');
4028
+ console.error(err);
4029
+ });
4030
+ }
4031
+ ngOnInit() {
4032
+ return __awaiter(this, void 0, void 0, function* () {
4033
+ this._translateService.setTranslation('en', TABLES_EN, true);
4034
+ this._translateService.setTranslation('el', TABLES_EL, true);
4035
+ });
4036
+ }
4037
+ }
4038
+ TablesModule.decorators = [
4039
+ { type: NgModule, args: [{
4040
+ imports: [
4041
+ CommonModule,
4042
+ SharedModule,
4043
+ TranslateModule,
4044
+ FormsModule,
4045
+ ReactiveFormsModule,
4046
+ RouterModule,
4047
+ RouterModalModule,
4048
+ FormioModule,
4049
+ AdvancedFormsModule,
4050
+ SharedModule,
4051
+ ModalModule,
4052
+ BsDropdownModule,
4053
+ ProgressbarModule,
4054
+ NgPipesModule
4055
+ ],
4056
+ providers: [DatePipe, TemplatePipe,
4057
+ {
4058
+ provide: COLUMN_FORMATTERS,
4059
+ useValue: ɵ0
4060
+ },
4061
+ AdvancedTableConfigurationResolver,
4062
+ AdvancedSearchConfigurationResolver,
4063
+ AdvancedSelectService,
4064
+ AdvancedAggregatorService,
4065
+ GroupByPipe
4066
+ ],
4067
+ declarations: [
4068
+ AdvancedTableComponent,
4069
+ AdvancedTableSearchComponent,
4070
+ BtnExpDirective,
4071
+ AdvancedTableSettingsComponent,
4072
+ AdvancedTableModalBaseComponent,
4073
+ AdvancedSearchFormComponent,
4074
+ AdvancedListComponent,
4075
+ AdvancedRowActionComponent,
4076
+ TableSimpleHeaderComponent,
4077
+ SimpleHeaderToolsComponent,
4078
+ AdvancedSelectComponent,
4079
+ AdvancedTableEditorDirective,
4080
+ AdvancedAggregatorComponent,
4081
+ AdvancedAggregatorListComponent,
4082
+ AdvancedAggregatorPipe,
4083
+ ParseTitlePipe,
4084
+ AddItemsComponent
4085
+ ],
4086
+ exports: [
4087
+ AdvancedTableComponent,
4088
+ AdvancedTableSearchComponent,
4089
+ BtnExpDirective,
4090
+ AdvancedTableSettingsComponent,
4091
+ AdvancedSearchFormComponent,
4092
+ AdvancedListComponent,
4093
+ AdvancedRowActionComponent,
4094
+ TableSimpleHeaderComponent,
4095
+ SimpleHeaderToolsComponent,
4096
+ AdvancedSelectComponent,
4097
+ AdvancedTableEditorDirective,
4098
+ AdvancedAggregatorComponent,
4099
+ AdvancedAggregatorListComponent,
4100
+ AddItemsComponent
4101
+ ],
4102
+ entryComponents: [
4103
+ AdvancedTableModalBaseComponent,
4104
+ AdvancedTableSettingsComponent,
4105
+ AdvancedRowActionComponent,
4106
+ AdvancedSelectComponent,
4107
+ AddItemsComponent
4108
+ ],
4109
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
4110
+ },] }
4111
+ ];
4112
+ /** @nocollapse */
4113
+ TablesModule.ctorParameters = () => [
4114
+ { type: TranslateService }
4115
+ ];
4116
+
4117
+ /*
4118
+ * Public API Surface of tables
4119
+ */
4120
+
4121
+ /**
4122
+ * Generated bundle index. Do not edit.
4123
+ */
4124
+
4125
+ export { AdvancedFilterValueProvider as ɵb, AdvancedTableSearchBaseComponent as ɵa, AdvancedTableSettingsComponent as ɵe, SimpleHeaderToolsComponent as ɵf, TableSimpleHeaderComponent as ɵg, BtnExpDirective as ɵd, AdvancedAggregatorPipe as ɵh, ParseTitlePipe as ɵi, AdvancedAggregatorService as ɵc, AdvancedListComponent, AdvancedRowActionComponent, AdvancedSearchFormComponent, AdvancedTableConfigurationResolver, AdvancedSearchConfigurationResolver, AdvancedColumnFormatter, NestedPropertyFormatter, ButtonFormatter, LinkFormatter, ActionLinkFormatter, SelectRowFormatter, TemplateFormatter, TrueFalseFormatter, TranslationFormatter, DateTimeFormatter, NgClassFormatter, LanguageFormatter, COLUMN_FORMATTERS, AdvancedTableConfiguration, AdvancedTableComponent, AdvancedTableSearchComponent, FORMATTERS, AdvancedTableModalBaseTemplate, AdvancedTableModalBaseComponent, AdvancedSelectComponent, AdvancedSelectService, TablesModule, ɵ0, ActivatedTableService, AdvancedTableNotNullError, AdvancedTableEditorDirective, AdvancedAggregatorComponent, AdvancedAggregatorListComponent, AddItemsComponent };
4126
+
4127
+ //# sourceMappingURL=universis-ngx-tables.js.map