@universis/ngx-tables 0.6.14 → 0.6.19

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.
@@ -4,16 +4,16 @@ import 'datatables.net';
4
4
  import { template, at } from 'lodash';
5
5
  import 'datatables.net-scroller';
6
6
  import 'rxjs/add/observable/combineLatest';
7
- import { BehaviorSubject } from 'rxjs';
8
7
  import { HttpClient } from '@angular/common/http';
9
- import { AngularDataContext } from '@themost/angular';
10
8
  import { ClientDataQueryable, ResponseError, TextUtils } from '@themost/client';
9
+ import { BehaviorSubject } from 'rxjs';
10
+ import { AngularDataContext } from '@themost/angular';
11
11
  import { __awaiter } from 'tslib';
12
12
  import { Injectable, Directive, Output, EventEmitter, Pipe, Component, Input, ViewChild, ViewEncapsulation, ChangeDetectorRef, ElementRef, Inject, InjectionToken, Injector, NgModule, CUSTOM_ELEMENTS_SCHEMA, defineInjectable, inject, HostListener, ChangeDetectionStrategy } from '@angular/core';
13
13
  import { DatePipe, CommonModule } from '@angular/common';
14
14
  import { TranslateService, TranslatePipe, TranslateModule } from '@ngx-translate/core';
15
15
  import { FormArray, FormBuilder, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
16
- import { TemplatePipe, LoadingService, AppEventService, ErrorService, ModalService, ToastService, SharedModule, ConfigurationService, DIALOG_BUTTONS, UserActivityService, UserStorageService } from '@universis/common';
16
+ import { TemplatePipe, LoadingService, ErrorService, DIALOG_BUTTONS, ModalService, UserActivityService, ToastService, AppEventService, UserStorageService, ConfigurationService, SharedModule } from '@universis/common';
17
17
  import { RouterModalOkCancel, RouterModalModule } from '@universis/common/routing';
18
18
  import { ServiceUrlPreProcessor, TranslatePreProcessor, AdvancedFormsModule } from '@universis/forms';
19
19
  import { FormioModule } from 'angular-formio';
@@ -667,32 +667,55 @@ class AdvancedTableComponent {
667
667
  // simple search
668
668
  search(searchText) {
669
669
  this.query = this.query || this._context.model(this.config.model).asQueryable();
670
- if (searchText && this.config.searchExpression) {
671
- // validate search text in double quotes
672
- if (/^"(.*?)"$/.test(searchText)) {
673
- this.query.setParam('$filter', this._template.transform(this.config.searchExpression, {
674
- text: searchText.replace(/^"|"$/g, ''),
675
- }));
670
+ if (this.serverSide) {
671
+ if (searchText && this.config.searchExpression) {
672
+ // validate search text in double quotes
673
+ if (/^"(.*?)"$/.test(searchText)) {
674
+ this.query.setParam('$filter', this._template.transform(this.config.searchExpression, {
675
+ text: searchText.replace(/^"|"$/g, ''),
676
+ }));
677
+ }
678
+ else {
679
+ // try to split words
680
+ const words = searchText.split(' ');
681
+ // join words to a single filter
682
+ const filter = words.map((word) => {
683
+ return this._template.transform(this.config.searchExpression, {
684
+ text: word,
685
+ });
686
+ }).join(' and ');
687
+ // set filter
688
+ this.query.setParam('$filter', filter);
689
+ }
676
690
  }
677
691
  else {
678
- // try to split words
679
- const words = searchText.split(' ');
680
- // join words to a single filter
681
- const filter = words.map((word) => {
682
- return this._template.transform(this.config.searchExpression, {
683
- text: word,
684
- });
685
- }).join(' and ');
686
- // set filter
687
- this.query.setParam('$filter', filter);
692
+ // clear filter
693
+ this.query.setParam('$filter', null);
688
694
  }
695
+ this._firstLoad = false;
696
+ this.dataTable.draw();
689
697
  }
690
698
  else {
691
- // clear filter
692
- this.query.setParam('$filter', null);
699
+ // ensure empty table layout
700
+ this.resetEmptyTable();
701
+ // search locally
702
+ this.dataTable.search(searchText.trim()).draw();
703
+ // get indexes of filtered rows
704
+ const indexes = Array.from(this.dataTable.context[0].aiDisplay);
705
+ // set recordsFiltered
706
+ const recordsFiltered = indexes.length;
707
+ // set recordsTotal
708
+ const recordsTotal = indexes.length;
709
+ // retrieve data from indexes
710
+ const rows = Array.from(this.dataTable.rows().data());
711
+ const data = indexes.map(index => rows[index]);
712
+ // emit results to load output
713
+ this.load.emit({
714
+ data: data,
715
+ recordsFiltered: recordsFiltered,
716
+ recordsTotal: recordsTotal
717
+ });
693
718
  }
694
- this._firstLoad = false;
695
- this.dataTable.draw();
696
719
  }
697
720
  fetch(clearSelected) {
698
721
  // if data table is null
@@ -1355,6 +1378,10 @@ class AdvancedTableComponent {
1355
1378
  const query = this._context.model(this.config.model).asQueryable();
1356
1379
  // select attributes
1357
1380
  query.setParam('$select', this.lastQueryParams.$select);
1381
+ if (this.lastQueryParams.$expand) {
1382
+ // apply expand attributes
1383
+ query.setParam('$expand', this.lastQueryParams.$expand);
1384
+ }
1358
1385
  // apply filter for row
1359
1386
  findKeys.forEach((key) => {
1360
1387
  const column = this.config.columns.find((col) => {
@@ -1441,7 +1468,8 @@ class AdvancedTableComponent {
1441
1468
  // due to ng data binding
1442
1469
  // empty table template has no translated text during ngOnInit()
1443
1470
  if (this.dataTable && this.dataTable.context[0]) {
1444
- this.dataTable.context[0].oLanguage.sEmptyTable = this.emptyTable.nativeElement.innerHTML;
1471
+ this.dataTable.context[0].oLanguage.sEmptyTable =
1472
+ this.dataTable.context[0].oLanguage.sZeroRecords = this.emptyTable.nativeElement.innerHTML;
1445
1473
  }
1446
1474
  }
1447
1475
  clearEmptyTable() {
@@ -1558,418 +1586,233 @@ AdvancedTableComponent.propDecorators = {
1558
1586
  query: [{ type: Input, args: ['query',] }]
1559
1587
  };
1560
1588
 
1561
- // important: export this const due to AOT compilation errors
1562
- // see more https://angular.io/guide/aot-metadata-errors#only-initialized-variables
1563
- const AdvancedTableModalBaseTemplate = `
1564
- <div>
1565
- <!-- search -->
1566
- <div class="row mt-3">
1567
- <div class="col-12 col-lg-3">
1568
- <div class="input-group mb-3">
1569
- <input spellcheck="false" type="text"
1570
- (keyup)="onKeyUp($event)" [(ngModel)]="searchText"
1571
- class="form-control bg-gray-100 border-0"
1572
- placeholder="{{'Tables.SearchPlaceholder' | translate}}" aria-label="Search" aria-describedby="basic-addon2">
1573
- <div class="input-group-append">
1574
- <button class="btn btn-default bg-gray-100 border-0" type="button">
1575
- <i class="fa fa-search text-secondary"></i>
1576
- </button>
1577
- </div>
1578
- </div>
1579
- </div>
1580
- <div #emptyTable class="d-none">
1581
- <div>
1582
- <div class="mt-4">
1583
- <div class="icon-circle bg-gray-300 border-gray-100"></div>
1584
- </div>
1585
- <div class="mt-4">
1586
- <h4 class="font-3xl text-dark" [translate]="'Tables.EmptyTableTitle'"></h4>
1587
- <p class="font-lg text-dark" [translate]="'Tables.EmptyTableMessage'"></p>
1588
- </div>
1589
-
1590
- </div>
1591
- </div>
1592
- <div class="col-12 col-lg-5">
1593
- <div class="btn-toolbar">
1594
- <button class="btn btn-gray-100">
1595
- <i class="fa fa-sliders-h pr-2 text-secondary" aria-hidden="true"></i>{{'Tables.Filters' | translate}}
1596
- </button>
1597
- <div #dropdownFilter class="dropdown ml-3">
1598
- <button class="btn btn-gray-100 dropdown-toggle"
1599
- type="button" id="dropdownDefaultFilters"
1600
- data-toggle="dropdown" dropdownToggle>
1601
- {{ this.defaultFilter.name | translate }}
1602
- </button>
1603
- <div class="dropdown-menu" aria-labelledby="dropdownDefaultFilters">
1604
- <button type="button" class="dropdown-item" *ngFor="let filter of defaultFilters" (click)="onFilterSelect(filter)">
1605
- {{ filter.name | translate }}
1606
- </button>
1607
- </div>
1608
- </div>
1609
- </div>
1610
- </div>
1611
- <div class="col text-lg-right mt-3 mt-lg-0 d-none">
1612
- <button [disabled]="true" class="btn rounded-pill btn-outline-theme">
1613
- <span class="pr-2 font-lg">+</span><span [translate]="'Tables.AddItem'"></span>
1614
- </button>
1615
- </div>
1616
- </div>
1617
- <div class="row">
1618
- <div class="col-12" [ngClass]="{ 'component-loading': loading}">
1619
- <app-advanced-table #advancedTable (init)="onTableInit($event)"
1620
- (load)="onDataLoad($event)"
1621
- [selectable]="true"
1622
- [multipleSelect]="true"
1623
- [scrollX]="false" [scrollY]="320"
1624
- [pageLength]="pageLength"
1625
- [scrollable]="false" [showFooter]="false"
1626
- [showActions]="false" [lengthMenu]="lengthMenu"
1627
- [config]="tableConfig" [configSrc]="tableConfigSrc"></app-advanced-table>
1628
- </div>
1629
- </div>
1630
- <div class="row mt-3 table-custom-pager">
1631
- <div class="col-12 d-flex" *ngIf="recordsTotal">
1632
- <div class="mt-2 ml-auto mr-3 text-secondary" [translate]="'Tables.RecordsPerPage'"></div>
1633
- <div #dropdownLength class="dropdown mr-3">
1634
- <button class="btn btn-gray-100 dropdown-toggle"
1635
- type="button" id="dropdownPageSize" data-toggle="dropdown"
1636
- dropdownToggle>
1637
- {{pageLength}}
1638
- </button>
1639
- <div class="dropdown-menu" aria-labelledby="dropdownPageSize">
1640
- <button (click)="setLength(item)"
1641
- class="dropdown-item dropdown-item-theme"
1642
- type="button" *ngFor="let item of lengthMenu">{{item}}</button>
1643
- </div>
1644
- </div>
1645
- <div class="mt-2 mr-2" [translate]="'Tables.PageInfo'" [translateParams]="pageInfo"></div>
1646
- <button [disabled]="pageInfo && pageInfo.page === 0" class="btn text-nowrap" (click)="firstPage()">
1647
- <i class="fa fa-chevron-left text-color-theme"></i>
1648
- <i class="fa fa-chevron-left text-color-theme"></i>
1649
- </button>
1650
- <button [disabled]="pageInfo && pageInfo.page===0" class="btn" (click)="previousPage()">
1651
- <i class="fa fa-chevron-left text-color-theme"></i>
1652
- </button>
1653
- <button [disabled]="pageInfo && pageInfo.page===pageInfo.pages-1" class="btn" (click)="nextPage()">
1654
- <i class="fa fa-chevron-right text-color-theme"></i>
1655
- </button>
1656
- <button [disabled]="pageInfo && pageInfo.page===pageInfo.pages-1" class="btn text-nowrap" (click)="lastPage()">
1657
- <i class="fa fa-chevron-right text-color-theme"></i>
1658
- <i class="fa fa-chevron-right text-color-theme"></i>
1659
- </button>
1660
- </div>
1661
- </div>
1662
- </div>
1663
- `;
1664
- class AdvancedTableModalBaseComponent extends RouterModalOkCancel {
1665
- constructor(_router, _activatedRoute, _context, advancedFilterValueProvider, datePipe) {
1666
- super(_router, _activatedRoute);
1589
+ class AdvancedListComponent {
1590
+ constructor(_context, _translateService, _activatedRoute, _errorService, _toastService, _modalService, _userActivityService) {
1667
1591
  this._context = _context;
1668
- this.advancedFilterValueProvider = advancedFilterValueProvider;
1669
- this.datePipe = datePipe;
1670
- this.pageLength = 25;
1671
- this.lengthMenu = [5, 10, 25, 50, 100];
1672
- this.recordsTotal = 0;
1673
- this.loading = false;
1674
- this.recordsFiltered = 0;
1675
- this.defaultFilters = [AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER];
1676
- this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
1677
- this.pageInfo = {
1678
- serverSide: true
1679
- };
1680
- this.modalClass = 'modal-xl modal-table';
1681
- this._template = new TemplatePipe();
1682
- }
1683
- ngAfterViewInit() {
1684
- //
1685
- }
1686
- _getInputs(componentType) {
1687
- const props = componentType.__prop__metadata__;
1688
- const inputs = [];
1689
- if (props == null) {
1690
- return inputs;
1691
- }
1692
- Object.keys(props).forEach(prop => {
1693
- const member = props[prop][0];
1694
- if (member.ngMetadataName === 'Input') {
1695
- inputs.push(prop);
1696
- }
1697
- });
1698
- return inputs.sort();
1699
- }
1700
- hasInputs() {
1701
- return [];
1702
- }
1703
- ngOnInit() {
1704
- this.okButtonText = 'Tables.Apply';
1705
- this.cancelButtonText = 'Tables.Cancel';
1706
- // get route data
1707
- this.subscription = this.activatedRoute.data.subscribe(data => {
1708
- // set modal title if any
1709
- if (data.title) {
1710
- this.modalTitle = data.title;
1711
- }
1712
- if (data.config) {
1713
- this.tableConfig = data.config;
1714
- }
1715
- if (this.tableConfig) {
1716
- this.defaultFilters.push.apply(this.defaultFilters, this.tableConfig.searches);
1717
- }
1718
- // important: use hasInputs due to AOT compilation. Metadata properties cannot be used (e.g. @Input())
1719
- // because there are empty during runtime
1720
- // so we need to define manually input properties that are going to be bind through this process
1721
- // bind all inputs
1722
- const inputs = this.hasInputs();
1723
- inputs.forEach(input => {
1724
- if (Object.prototype.hasOwnProperty.call(data, input)) {
1725
- this[input] = data[input];
1726
- }
1727
- });
1728
- this.configChangesSubscription = this.advancedTable.configChanges.subscribe((config) => {
1729
- if (this.tableConfig) {
1730
- // reset default filter
1731
- this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
1732
- // init filter
1733
- this.defaultFilters = [AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER];
1734
- // add extra filters
1735
- this.defaultFilters.push.apply(this.defaultFilters, this.tableConfig.searches);
1736
- }
1737
- });
1738
- });
1739
- }
1740
- cancel() {
1741
- return this.close();
1742
- }
1743
- ok() {
1744
- return this.close();
1592
+ this._translateService = _translateService;
1593
+ this._activatedRoute = _activatedRoute;
1594
+ this._errorService = _errorService;
1595
+ this._toastService = _toastService;
1596
+ this._modalService = _modalService;
1597
+ this._userActivityService = _userActivityService;
1598
+ this.allowSaveOption = true;
1745
1599
  }
1746
1600
  ngOnDestroy() {
1601
+ if (this.dataSubscription) {
1602
+ this.dataSubscription.unsubscribe();
1603
+ }
1604
+ if (this.reloadSubscription) {
1605
+ this.reloadSubscription.unsubscribe();
1606
+ }
1747
1607
  if (this.subscription) {
1748
1608
  this.subscription.unsubscribe();
1749
1609
  }
1750
1610
  }
1751
- onDataLoad(data) {
1752
- this.recordsTotal = data.recordsTotal;
1753
- this.recordsFiltered = data.recordsFiltered;
1754
- // get first page info (because page is not going to be fired for first page)
1755
- if (data.recordsTotal > 0 && this.pageInfo.recordsTotal === 0) {
1756
- this.pageInfo.recordsTotal = data.recordsTotal;
1757
- }
1758
- }
1759
- previousPage() {
1760
- // go to next page
1761
- // noinspection TypeScriptValidateJSTypes
1762
- this.advancedTable.dataTable.page('previous').draw('page');
1763
- }
1764
- nextPage() {
1765
- // go to next page
1766
- // noinspection TypeScriptValidateJSTypes
1767
- this.advancedTable.dataTable.page('next').draw('page');
1768
- }
1769
- onTableInit(dataTable) {
1770
- const self = this;
1771
- // get initial page info
1772
- const initialPageInfo = dataTable.page.info();
1773
- initialPageInfo.start += 1;
1774
- self.pageInfo = initialPageInfo;
1775
- // change emptyTableMessage
1776
- dataTable.context[0].oLanguage.sEmptyTable = this.emptyTable.nativeElement.innerHTML;
1777
- // bind page change event
1778
- dataTable.on('page.dt', function () {
1779
- const pageInfo = dataTable.page.info();
1780
- pageInfo.start += 1;
1781
- self.pageInfo = pageInfo;
1782
- });
1783
- // bind draw change event
1784
- dataTable.on('draw.dt', function () {
1785
- // for future use
1786
- self.loading = false;
1787
- const pageInfo = dataTable.page.info();
1788
- pageInfo.start += 1;
1789
- self.pageInfo = pageInfo;
1790
- });
1791
- }
1792
- firstPage() {
1793
- // go to first page
1794
- // noinspection TypeScriptValidateJSTypes
1795
- this.advancedTable.dataTable.page('first').draw('page');
1796
- }
1797
- lastPage() {
1798
- // go to last page
1799
- // noinspection TypeScriptValidateJSTypes
1800
- this.advancedTable.dataTable.page('last').draw('page');
1801
- }
1802
- setLength(value) {
1803
- this.pageLength = value;
1804
- this.advancedTable.dataTable.page.len(value).draw();
1805
- }
1806
- onKeyUp(event) {
1807
- if (event.code === 'Enter') {
1808
- // loading
1809
- this.loading = true;
1810
- this.advancedTable.search(event.target.value);
1811
- }
1812
- }
1813
- /**
1814
- *
1815
- * Reads the modal table and returns a list of filters to use in the table
1816
- * query.
1817
- *
1818
- * @param tableConfig {object} The table configuration from json file
1819
- * @param defaultFilterValues {object} A list of default filters
1820
- * @param {object} searchItem The searchItem to use
1821
- *
1822
- */
1823
- getFilters(tableConfig, defaultFilterValues = {}, searchItem) {
1824
- // Do nothing in absence of configuration.
1825
- if (!tableConfig || !Array.isArray(tableConfig.criteria)) {
1826
- return [];
1827
- }
1828
- if (searchItem.filter == null) {
1829
- return [];
1830
- }
1831
- const matchedCriterion = tableConfig.criteria.find(criterion => Object.prototype.hasOwnProperty.call(searchItem.filter, criterion.name));
1832
- if (matchedCriterion == null) {
1833
- return [];
1834
- }
1835
- const value = searchItem.filter[matchedCriterion.name];
1836
- const criterionSafeName = matchedCriterion.name !== null
1837
- ? matchedCriterion.name.toString()
1838
- : '';
1839
- let applicableFilter;
1840
- // Raw dates should be transformed to a specific format
1841
- if (criterionSafeName.includes('GMT')) {
1842
- const newDate = this.datePipe.transform(criterionSafeName, 'MM/dd/yyyy');
1843
- applicableFilter = this._template.transform(matchedCriterion.filter, {
1844
- value: newDate
1845
- });
1846
- }
1847
- else if (criterionSafeName !== 'undefined') {
1848
- applicableFilter = (this._template.transform(matchedCriterion.filter, Object.assign({
1849
- value: value
1850
- }, defaultFilterValues)));
1851
- }
1852
- else {
1853
- applicableFilter = '';
1854
- }
1855
- return [applicableFilter];
1856
- }
1857
- onFilterSelect(value) {
1858
- this.advancedSearch(value);
1859
- }
1860
- /**
1861
- *
1862
- * Runs a filter against the modal table
1863
- *
1864
- * @param {object} filter The filter to use in the search
1865
- *
1866
- */
1867
- advancedSearch(filter) {
1611
+ ngOnInit() {
1868
1612
  return __awaiter(this, void 0, void 0, function* () {
1869
- // Do nothing in absence of configuration.
1870
- if (!this.tableConfig && !Array.isArray(this.tableConfig.criteria)) {
1871
- console.warn('No table configuration available', this.tableConfig);
1872
- return;
1873
- }
1874
- // An advanced table has some filters by default
1875
- let defaultFilterValues;
1876
1613
  try {
1877
- defaultFilterValues = yield this.advancedFilterValueProvider.getValues();
1614
+ // get metadata
1615
+ const metadata = yield this._context.getMetadata();
1616
+ // get route data
1617
+ this.dataSubscription = this._activatedRoute.data.subscribe(routeData => {
1618
+ // get entityType
1619
+ const re = new RegExp(`^${routeData.model}$`, 'ig');
1620
+ const entitySet = metadata.EntityContainer.EntitySet.find(x => {
1621
+ return re.test(x.Name);
1622
+ });
1623
+ // if entity type is null
1624
+ if (entitySet == null) {
1625
+ // navigate to error
1626
+ return this._errorService.navigateToError(new ResponseError('EntitySet Not Found', 404));
1627
+ }
1628
+ // get entity type
1629
+ const entityType = metadata.EntityType.find(x => {
1630
+ return x.Name === entitySet.EntityType;
1631
+ });
1632
+ if (entityType == null) {
1633
+ return this._errorService.navigateToError(new ResponseError('EntityType Not Found', 404));
1634
+ }
1635
+ if (routeData.searchConfiguration) {
1636
+ if (Object.prototype.hasOwnProperty.call(routeData.searchConfiguration, 'allowSave')) {
1637
+ this.allowSaveOption = routeData.searchConfiguration.allowSave;
1638
+ }
1639
+ this.searchConfiguration = routeData.searchConfiguration;
1640
+ }
1641
+ if (routeData.tableConfiguration) {
1642
+ this.tableConfiguration = routeData.tableConfiguration;
1643
+ }
1644
+ if (routeData.description) {
1645
+ this.description = this._translateService.instant(routeData.description);
1646
+ }
1647
+ if (routeData.longDescription) {
1648
+ this.longDescription = this._translateService.instant(routeData.longDescription);
1649
+ }
1650
+ if (routeData.category) {
1651
+ this.category = this._translateService.instant(routeData.category);
1652
+ }
1653
+ // clone default configuration
1654
+ const defaultConfiguration = AdvancedTableConfiguration.cast(this.tableConfiguration, true);
1655
+ // correct edit button link
1656
+ const column = defaultConfiguration.columns.find(col => {
1657
+ return col.formatter === 'ButtonFormatter' && col.formatString === '(modal:edit)';
1658
+ });
1659
+ if (column) {
1660
+ const url = [];
1661
+ this._activatedRoute.snapshot.pathFromRoot.forEach(route => {
1662
+ url.push.apply(url, route.url);
1663
+ });
1664
+ column.formatString = `#/${url.join('/')}/(modal:\$\{id\}/edit)`;
1665
+ }
1666
+ // create list configuration by assigning list entity set
1667
+ this.table.config = Object.assign({
1668
+ model: entitySet.Name
1669
+ }, defaultConfiguration);
1670
+ // fetch data
1671
+ this.table.fetch(true);
1672
+ // do reload by using hidden fragment e.g. /classes#reload
1673
+ this.reloadSubscription = this._activatedRoute.fragment.subscribe(fragment => {
1674
+ if (fragment && fragment === 'reload') {
1675
+ this._toastService.show(this.description, this._translateService.instant('Settings.OperationCompleted'));
1676
+ this.table.fetch(true);
1677
+ }
1678
+ });
1679
+ // finally add user activity
1680
+ this._userActivityService.setItem({
1681
+ category: this.category,
1682
+ description: this.description,
1683
+ url: window.location.hash.substring(1),
1684
+ dateCreated: new Date
1685
+ });
1686
+ });
1878
1687
  }
1879
1688
  catch (err) {
1880
- defaultFilterValues = {};
1881
- }
1882
- const expressions = [];
1883
- let predefinedCriteria;
1884
- if (filter) {
1885
- this.defaultFilter = filter;
1886
- predefinedCriteria = this.getFilters(this.tableConfig, defaultFilterValues, filter);
1887
- }
1888
- else {
1889
- // Empty a falsy filter resets the results
1890
- this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
1689
+ console.error(err);
1690
+ return this._errorService.showError(err);
1891
1691
  }
1892
- // search filter is applied separately, there is no need to add here.
1893
- expressions.push(predefinedCriteria);
1894
- const query = this._context.model(this.advancedTable.config.model).asQueryable();
1895
- query.setParam('filter', expressions.join(' and ')).prepare();
1896
- this.advancedTable.query = query;
1897
- this.advancedTable.fetch();
1898
1692
  });
1899
1693
  }
1694
+ /**
1695
+ * Data load event handler of advanced table component
1696
+ * @param {AdvancedTableDataResult} data
1697
+ */
1698
+ onDataLoad(data) {
1699
+ this.recordsTotal = data.recordsTotal;
1700
+ }
1701
+ /**
1702
+ * Remove selected items
1703
+ */
1704
+ remove() {
1705
+ if (this.table && this.table.selected && this.table.selected.length) {
1706
+ // get items to remove
1707
+ const items = this.table.selected;
1708
+ return this._modalService.showWarningDialog(this._translateService.instant('Tables.RemoveItemsTitle'), this._translateService.instant('Tables.RemoveItemsMessage'), DIALOG_BUTTONS.OkCancel).then(result => {
1709
+ if (result === 'ok') {
1710
+ this._context.model(this.table.config.model).remove(items).then(() => {
1711
+ this._toastService.show(this.description + ':' + this._translateService.instant('Tables.RemoveItemMessage.title'), this._translateService.instant((items.length === 1 ?
1712
+ 'Tables.RemoveItemMessage.one' : 'Tables.RemoveItemMessage.many'), { value: items.length }));
1713
+ this.table.fetch(true);
1714
+ }).catch(err => {
1715
+ this._errorService.showError(err, {
1716
+ continueLink: '.'
1717
+ });
1718
+ });
1719
+ }
1720
+ });
1721
+ }
1722
+ }
1900
1723
  }
1901
- AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER = {
1902
- name: 'Tables.All',
1903
- filter: null
1904
- };
1905
- AdvancedTableModalBaseComponent.decorators = [
1724
+ AdvancedListComponent.decorators = [
1906
1725
  { type: Component, args: [{
1907
- // tslint:disable-next-line: component-selector
1908
- selector: 'app-advanced-table-modal',
1909
- template: AdvancedTableModalBaseTemplate,
1910
- styles: [`
1911
-
1912
- `]
1726
+ selector: 'app-advanced-list',
1727
+ 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 [showFooter]=\"false\"\n (load)=\"onDataLoad($event)\" [height]=\"600\"></app-advanced-table>\n </div>\n </div>\n <router-modal></router-modal>\n</div>\n"
1913
1728
  }] }
1914
1729
  ];
1915
1730
  /** @nocollapse */
1916
- AdvancedTableModalBaseComponent.ctorParameters = () => [
1917
- { type: Router },
1918
- { type: ActivatedRoute },
1731
+ AdvancedListComponent.ctorParameters = () => [
1919
1732
  { type: AngularDataContext },
1920
- { type: AdvancedFilterValueProvider },
1921
- { type: DatePipe }
1733
+ { type: TranslateService },
1734
+ { type: ActivatedRoute },
1735
+ { type: ErrorService },
1736
+ { type: ToastService },
1737
+ { type: ModalService },
1738
+ { type: UserActivityService }
1922
1739
  ];
1923
- AdvancedTableModalBaseComponent.propDecorators = {
1924
- advancedTable: [{ type: ViewChild, args: ['advancedTable',] }],
1925
- emptyTable: [{ type: ViewChild, args: ['emptyTable',] }],
1926
- dropdownFilter: [{ type: ViewChild, args: ['dropdownFilter',] }],
1927
- dropdownLength: [{ type: ViewChild, args: ['dropdownLength',] }],
1928
- tableConfig: [{ type: Input, args: ['tableConfig',] }],
1929
- tableConfigSrc: [{ type: Input, args: ['tableConfigSrc',] }],
1930
- pageLength: [{ type: Input, args: ['pageLength',] }],
1931
- searchText: [{ type: Input, args: ['searchText',] }],
1932
- lengthMenu: [{ type: Input, args: ['lengthMenu',] }],
1933
- defaultFilter: [{ type: Input, args: ['defaultFilter',] }]
1740
+ AdvancedListComponent.propDecorators = {
1741
+ entitySet: [{ type: Input, args: ['entitySet',] }],
1742
+ category: [{ type: Input, args: ['category',] }],
1743
+ description: [{ type: Input, args: ['description',] }],
1744
+ longDescription: [{ type: Input, args: ['longDescription',] }],
1745
+ tableConfiguration: [{ type: Input, args: ['tableConfiguration',] }],
1746
+ searchConfiguration: [{ type: Input, args: ['searchConfiguration',] }],
1747
+ table: [{ type: ViewChild, args: ['table',] }],
1748
+ allowSaveOption: [{ type: Input, args: ['allowSaveOption',] }]
1934
1749
  };
1935
1750
 
1936
- class AddItemsComponent extends RouterModalOkCancel {
1937
- constructor(router, activatedRoute, _errorService, _modalService, _appEvent, _toastService, _loadingService, _translateService, _context) {
1751
+ class AdvancedRowActionComponent extends RouterModalOkCancel {
1752
+ constructor(router, activatedRoute, _translateService, _context, _errorService, _modalService, _appEvent) {
1938
1753
  super(router, activatedRoute);
1939
- this._errorService = _errorService;
1940
- this._modalService = _modalService;
1941
- this._appEvent = _appEvent;
1942
- this._toastService = _toastService;
1943
- this._loadingService = _loadingService;
1944
1754
  this._translateService = _translateService;
1945
1755
  this._context = _context;
1756
+ this._errorService = _errorService;
1757
+ this._modalService = _modalService;
1758
+ this._appEvent = _appEvent;
1759
+ this.modalIcon = 'fas fa-project-diagram';
1760
+ this.progressType = 'indigo';
1761
+ this.errorMessage = 'Tables.Actions.CompletedWithErrors';
1946
1762
  this.loading = false;
1947
- this.items = [];
1948
- this.messages = [];
1763
+ this.refresh = new EventEmitter();
1949
1764
  // set modal size
1950
1765
  this.modalClass = 'modal-lg';
1951
- this.okButtonDisabled = true;
1766
+ // set modal title
1767
+ this.modalTitle = 'Tables.Actions.Label';
1768
+ // disable ok button
1769
+ this.okButtonDisabled = false;
1770
+ // set button text
1771
+ this.okButtonText = 'Tables.Actions.Start';
1772
+ }
1773
+ get form() {
1774
+ return this.formComponent;
1952
1775
  }
1953
1776
  ngOnInit() {
1954
- this.selectedSubscription = this.selectComponent.advancedTable.selectedItems.subscribe((items) => {
1955
- if (this.formName == null) {
1956
- this.okButtonDisabled = items.length === 0;
1777
+ this.refreshSubscription = this.refresh.subscribe((value) => {
1778
+ if (value && value.progress) {
1779
+ let progress = 0;
1780
+ if (value.progress < 0) {
1781
+ progress = 0;
1782
+ }
1783
+ else if (value.progress > 100) {
1784
+ progress = 100;
1785
+ }
1786
+ else {
1787
+ progress = value.progress;
1788
+ }
1789
+ this.progressComponent.value = progress;
1790
+ this.showProgress();
1957
1791
  }
1958
1792
  });
1959
- if (this.formName != null) {
1960
- this.formChangeSubscription = this.formComponent.form.change.subscribe((event) => {
1961
- if (Object.prototype.hasOwnProperty.call(event, 'isValid')) {
1962
- // enable or disable button based on form status
1963
- this.okButtonDisabled = (!event.isValid);
1793
+ }
1794
+ ngAfterViewInit() {
1795
+ if (this.formComponent && this.formComponent.form) {
1796
+ this.okButtonDisabled = true;
1797
+ this.invalidSubscription = this.formComponent.form.change.subscribe((event) => {
1798
+ if (Object.prototype.hasOwnProperty.call(event, "isValid") && (event.changed != null)) {
1799
+ this.okButtonDisabled = !event.isValid;
1964
1800
  }
1965
1801
  });
1966
1802
  }
1967
- if (this.target) {
1968
- const target = this.target;
1803
+ }
1804
+ ngOnDestroy() {
1805
+ if (this.refreshSubscription) {
1806
+ this.refreshSubscription.unsubscribe();
1807
+ }
1808
+ if (this.executeSubscription) {
1809
+ this.executeSubscription.unsubscribe();
1810
+ }
1811
+ if (this.invalidSubscription) {
1812
+ this.invalidSubscription.unsubscribe();
1969
1813
  }
1970
1814
  }
1971
1815
  cancel() {
1972
- this.clearMessages();
1973
1816
  if (this.loading) {
1974
1817
  return;
1975
1818
  }
@@ -1978,847 +1821,626 @@ class AddItemsComponent extends RouterModalOkCancel {
1978
1821
  return this._modalService.modalRef.hide();
1979
1822
  }
1980
1823
  }
1981
- ngOnDestroy() {
1982
- if (this.formLoadSubscription) {
1983
- this.formLoadSubscription.unsubscribe();
1984
- }
1985
- if (this.formChangeSubscription) {
1986
- this.formChangeSubscription.unsubscribe();
1987
- }
1988
- if (this.selectedSubscription) {
1989
- this.selectedSubscription.unsubscribe();
1990
- }
1991
- }
1992
- get formData() {
1993
- if (this.formComponent === null) {
1994
- return null;
1995
- }
1996
- return this.formComponent.form.formio.data;
1997
- }
1998
1824
  ok() {
1999
- try {
2000
- this.clearMessages();
2001
- return new Promise((resolve, reject) => {
2002
- this.loading = false;
1825
+ return __awaiter(this, void 0, void 0, function* () {
1826
+ try {
1827
+ this.loading = true;
2003
1828
  this.lastError = null;
2004
- // set courses
2005
- this.items = this.selectComponent.advancedTable.selected;
2006
- // execute add
2007
- this.executeSubscription = this.execute.subscribe((result) => {
1829
+ if (this.formComponent && this.formComponent.form &&
1830
+ this.formComponent.form.formio && this.formComponent.form.formio.data) {
1831
+ // set data
1832
+ this.data = this.formComponent.form.formio.data;
1833
+ }
1834
+ // do action
1835
+ this.execute.subscribe((result) => {
2008
1836
  this.loading = false;
2009
- if (this.executeSubscription) {
2010
- this.executeSubscription.unsubscribe();
1837
+ this.refresh.emit({
1838
+ progress: 100
1839
+ });
1840
+ if (result && result.errors && result.errors > 0) {
1841
+ // show message for partial success
1842
+ if (result.errors === 1) {
1843
+ this.lastError = new Error(this._translateService.instant(`${this.errorMessage}.Description.One`));
1844
+ }
1845
+ else {
1846
+ this.lastError = new Error(this._translateService.instant(`${this.errorMessage}.Description.Many`, result));
1847
+ }
1848
+ this.refresh.emit({
1849
+ progress: 1
1850
+ });
1851
+ this.hideProgress();
1852
+ return;
2011
1853
  }
2012
- // close modal
2013
1854
  if (this._modalService.modalRef) {
2014
1855
  this._modalService.modalRef.hide();
2015
1856
  }
2016
- return resolve();
2017
1857
  }, (err) => {
2018
1858
  this.loading = false;
2019
- // ensure that loading is hidden
2020
- this._loadingService.hideLoading();
2021
- // set last error
2022
1859
  this.lastError = err;
2023
- return resolve();
1860
+ this.refresh.emit({
1861
+ progress: 0
1862
+ });
2024
1863
  });
2025
- });
2026
- }
2027
- catch (err) {
2028
- this.loading = false;
2029
- this._loadingService.hideLoading();
2030
- this.lastError = err;
2031
- }
1864
+ }
1865
+ catch (err) {
1866
+ this.loading = false;
1867
+ this.lastError = err;
1868
+ }
1869
+ });
2032
1870
  }
2033
- onChange($event) { }
2034
- clearMessages() {
2035
- this.messages = [];
1871
+ showProgress() {
1872
+ this.progressContainer.nativeElement.classList.replace('invisible', 'visible');
1873
+ }
1874
+ hideProgress() {
1875
+ this.progressContainer.nativeElement.classList.replace('visible', 'invisible');
2036
1876
  }
2037
- onFilterChange(event) { }
2038
1877
  }
2039
- AddItemsComponent.decorators = [
1878
+ AdvancedRowActionComponent.decorators = [
2040
1879
  { type: Component, args: [{
2041
- selector: 'app-add-items',
2042
- 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"
1880
+ selector: 'app-advanced-row-action',
1881
+ 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 invisible\" #progressContainer>\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",
1882
+ encapsulation: ViewEncapsulation.None,
1883
+ styles: [`
1884
+ .fa-1_5x {
1885
+ font-size: 1.5rem;
1886
+ }
1887
+ .form-control:disabled, .form-control[readonly] {
1888
+ background-color: inherit;
1889
+ }
1890
+ .form-check.checkbox {
1891
+ position: relative
1892
+ }
1893
+ `]
2043
1894
  }] }
2044
1895
  ];
2045
1896
  /** @nocollapse */
2046
- AddItemsComponent.ctorParameters = () => [
1897
+ AdvancedRowActionComponent.ctorParameters = () => [
2047
1898
  { type: Router },
2048
1899
  { type: ActivatedRoute },
1900
+ { type: TranslateService },
1901
+ { type: AngularDataContext },
2049
1902
  { type: ErrorService },
2050
1903
  { type: ModalService },
2051
- { type: AppEventService },
2052
- { type: ToastService },
2053
- { type: LoadingService },
2054
- { type: TranslateService },
2055
- { type: AngularDataContext }
1904
+ { type: AppEventService }
2056
1905
  ];
2057
- AddItemsComponent.propDecorators = {
2058
- execute: [{ type: Input }],
1906
+ AdvancedRowActionComponent.propDecorators = {
2059
1907
  items: [{ type: Input }],
2060
- target: [{ type: Input }],
2061
- tableConfiguration: [{ type: Input }],
2062
- formName: [{ type: Input }],
2063
- messages: [{ type: Input }],
1908
+ modalIcon: [{ type: Input }],
1909
+ description: [{ type: Input }],
1910
+ formTemplate: [{ type: Input }],
1911
+ progressType: [{ type: Input }],
1912
+ data: [{ type: Output }],
1913
+ additionalMessage: [{ type: Input }],
1914
+ errorMessage: [{ type: Input }],
1915
+ progressComponent: [{ type: ViewChild, args: ['progress',] }],
1916
+ progressContainer: [{ type: ViewChild, args: ['progressContainer',] }],
2064
1917
  formComponent: [{ type: ViewChild, args: ['formComponent',] }],
2065
- selectComponent: [{ type: ViewChild, args: ['selectComponent',] }]
1918
+ refresh: [{ type: Input }],
1919
+ execute: [{ type: Input }]
2066
1920
  };
2067
1921
 
2068
- class AdvancedAggregatorService {
2069
- constructor(_context, _template) {
1922
+ class AdvancedTableSearchBaseComponent {
1923
+ constructor() {
1924
+ this.filterChange = new EventEmitter();
1925
+ }
1926
+ get filter() {
1927
+ return this._filter;
1928
+ }
1929
+ set filter(value) {
1930
+ this._filter = value;
1931
+ }
1932
+ onKeyEnter(event) {
1933
+ if (event.code === 'Enter') {
1934
+ this.filterChange.emit(this._filter);
1935
+ }
1936
+ }
1937
+ }
1938
+ AdvancedTableSearchBaseComponent.propDecorators = {
1939
+ filterChange: [{ type: Output }],
1940
+ filter: [{ type: Input }]
1941
+ };
1942
+
1943
+ class AdvancedSearchFormComponent extends AdvancedTableSearchBaseComponent {
1944
+ constructor(_context, _translateService, _activatedRoute, _http) {
1945
+ super();
2070
1946
  this._context = _context;
2071
- this._template = _template;
1947
+ this._translateService = _translateService;
1948
+ this._activatedRoute = _activatedRoute;
1949
+ this._http = _http;
1950
+ this.form = {};
1951
+ this.refreshForm = new EventEmitter();
1952
+ this.submission = { data: {} };
1953
+ this.loading = true;
2072
1954
  }
2073
- /**
2074
- *
2075
- * Formats and executes the data query
2076
- *
2077
- * @param {string} model The target model
2078
- * @param {object} fixedParams Pairs of parameters to always be applied
2079
- * @param {string} queryStatement The default query to use for fetching the data (e.g. expands etc)
2080
- * @param {Array<string>} filters A list of filter expressions to append at the query
2081
- * @param {string} searchTemplate The template to use for searching expressions
2082
- * @param {string} searchValue The value of the user search
2083
- *
2084
- * @returns {Promise<any>} The response of the query execution
2085
- */
2086
- fetchData(model, fixedParams, queryStatement = '', filters, searchTemplate, searchValue) {
2087
- return __awaiter(this, void 0, void 0, function* () {
2088
- const query = this._context.model(model).asQueryable();
2089
- if (!fixedParams || typeof fixedParams !== 'object') {
2090
- fixedParams = {};
1955
+ ngOnChanges(changes) {
1956
+ if (Object.prototype.hasOwnProperty.call(changes, 'form')) {
1957
+ if (changes.form != null) {
1958
+ if (changes.form.currentValue &&
1959
+ changes.form.currentValue != changes.form.previousValue) {
1960
+ this.ngOnInit();
1961
+ }
2091
1962
  }
2092
- let filterExpressions = [];
2093
- if (fixedParams) {
2094
- for (let param of Object.keys(fixedParams)) {
2095
- filterExpressions.push(`${param} eq ${fixedParams[param]}`);
2096
- }
2097
- }
2098
- if (filters && Array.isArray(filters)) {
2099
- filterExpressions = [
2100
- ...filterExpressions,
2101
- ...filters
2102
- ];
2103
- }
2104
- if (searchTemplate && searchValue) {
2105
- let evaluatedSearch;
2106
- if (/^"(.*?)"$/.test(searchValue)) { // validate search text in double quotes
2107
- evaluatedSearch = this._template.transform(searchTemplate, Object.assign({}, fixedParams, { text: searchValue.replace(/^"|"$/g, '') }));
2108
- }
2109
- else {
2110
- // try to split words
2111
- const words = searchValue.split(' ');
2112
- // join words to a single filter
2113
- evaluatedSearch = words.map(word => {
2114
- return this._template.transform(searchTemplate, Object.assign({}, fixedParams, { text: word }));
2115
- }).join(' and ');
1963
+ }
1964
+ if (Object.prototype.hasOwnProperty.call(changes, 'formSrc')) {
1965
+ if (changes.formSrc != null) {
1966
+ if (changes.formSrc.currentValue &&
1967
+ changes.formSrc.currentValue != changes.formSrc.previousValue) {
1968
+ this._http.get(changes.formSrc.currentValue).toPromise().then((form) => {
1969
+ // set form
1970
+ this.form = form;
1971
+ // and init
1972
+ this.ngOnInit();
1973
+ }).catch((err) => {
1974
+ console.error('An error occurred while loading search configuration');
1975
+ console.error(err);
1976
+ });
2116
1977
  }
2117
- filterExpressions.push(evaluatedSearch);
2118
1978
  }
2119
- query.setParam('$filter', filterExpressions.join('and'));
2120
- return query.expand(queryStatement).take(-1).getItems();
2121
- });
2122
- }
2123
- /**
2124
- *
2125
- * Formats the received data to use flat keys
2126
- *
2127
- * @param config The applied configuration
2128
- * @param data The raw data
2129
- *
2130
- * @returns {Array<any>} The flat-key object list
2131
- *
2132
- */
2133
- formatData(config, data) {
2134
- try {
2135
- return data.map((entry) => {
2136
- if (entry.subitems && Array.isArray(entry.subitems)) {
2137
- entry.subitems = this.formatData(config, entry.subitems);
2138
- }
2139
- let entryObject = {};
2140
- for (let i = 0; i < config.columns.length; i++) {
2141
- const keys = config.columns[i].name.split('/');
2142
- let helperObject = entry[keys[0]];
2143
- if (helperObject && keys.length > 1) {
2144
- for (let i = 1; i < keys.length; i++) {
2145
- if (helperObject === undefined || helperObject === null) {
2146
- break;
2147
- }
2148
- else {
2149
- helperObject = helperObject[keys[i]] !== undefined && helperObject[keys[i]] !== null
2150
- ? helperObject[keys[i]]
2151
- : undefined;
2152
- }
2153
- }
2154
- }
2155
- entryObject[config.columns[i].name] = helperObject;
2156
- }
2157
- if (entry.subitems) {
2158
- entryObject.subitems = entry.subitems;
2159
- }
2160
- return entryObject;
2161
- });
2162
1979
  }
2163
- catch (err) {
2164
- console.error(err);
1980
+ if (Object.prototype.hasOwnProperty.call(changes, 'filter')) {
1981
+ if (changes.filter != null) {
1982
+ this.submission.data = changes.filter.currentValue || {};
1983
+ this.refreshForm.emit({
1984
+ submission: this.submission
1985
+ });
1986
+ }
2165
1987
  }
2166
1988
  }
2167
- }
2168
- AdvancedAggregatorService.decorators = [
2169
- { type: Injectable, args: [{
2170
- providedIn: 'root'
2171
- },] }
2172
- ];
2173
- /** @nocollapse */
2174
- AdvancedAggregatorService.ctorParameters = () => [
2175
- { type: AngularDataContext },
2176
- { type: TemplatePipe }
2177
- ];
2178
- AdvancedAggregatorService.ngInjectableDef = defineInjectable({ factory: function AdvancedAggregatorService_Factory() { return new AdvancedAggregatorService(inject(AngularDataContext), inject(TemplatePipe)); }, token: AdvancedAggregatorService, providedIn: "root" });
2179
-
2180
- /**
2181
- *
2182
- * @class AdvancedAggregatorComponent
2183
- * @classdesc An advanced list with the capability of aggregate data based on configurable filters
2184
- *
2185
- */
2186
- class AdvancedAggregatorComponent {
2187
- constructor(_changeDetector, _advancedAggregatorService, _loadingService) {
2188
- this._changeDetector = _changeDetector;
2189
- this._advancedAggregatorService = _advancedAggregatorService;
2190
- this._loadingService = _loadingService;
2191
- this.groups = [];
2192
- this.eventHandler = new EventEmitter();
2193
- }
2194
- set config(value) {
2195
- this._config = value;
2196
- }
2197
- set aggregators(value) {
2198
- this._aggregators = value;
2199
- }
2200
- set postProcessors(value) {
2201
- this._postProcessors = value;
2202
- }
2203
- get config() {
2204
- return this._config;
2205
- }
2206
- get aggregators() {
2207
- return this._aggregators;
2208
- }
2209
- get postProcessors() {
2210
- return this._postProcessors;
2211
- }
2212
1989
  ngOnInit() {
2213
- return __awaiter(this, void 0, void 0, function* () {
2214
- this.selectedGroups$.subscribe((data) => {
2215
- this.groups = data;
2216
- this._changeDetector.markForCheck();
1990
+ if (this.form) {
1991
+ new ServiceUrlPreProcessor(this._context).parse(this.form);
1992
+ new TranslatePreProcessor(this._translateService).parse(this.form);
1993
+ this.submission = {
1994
+ data: this.filter
1995
+ };
1996
+ this.refreshForm.emit({
1997
+ form: this.form
2217
1998
  });
2218
- this.filters$.subscribe((filters) => __awaiter(this, void 0, void 0, function* () {
2219
- try {
2220
- yield this.fetch(filters);
1999
+ this.loading = false;
2000
+ this.filterSubscription = this.filterChange.subscribe(filter => {
2001
+ this.submission.data = filter;
2002
+ this.refreshForm.emit({
2003
+ submission: this.submission
2004
+ });
2005
+ });
2006
+ }
2007
+ }
2008
+ onChange(event) {
2009
+ Object.keys(this.formComponent.submission.data).forEach(key => {
2010
+ if (Object.prototype.hasOwnProperty.call(this.formComponent.submission.data, key)) {
2011
+ const value = this.formComponent.submission.data[key];
2012
+ // the filter with type is a string and is not empty
2013
+ const isEmptyString = typeof value === 'string' && value.length === 0;
2014
+ // do not include empty objects in filter
2015
+ const isEmptyObject = typeof value === 'object' && Object.keys(value).length === 0;
2016
+ // make isEmptyString, isEmptyObject mutex
2017
+ if (!isEmptyString && !isEmptyObject) {
2018
+ Object.defineProperty(this.filter, key, {
2019
+ configurable: true,
2020
+ enumerable: true,
2021
+ value: value
2022
+ });
2221
2023
  }
2222
- catch (err) {
2223
- console.error(err);
2024
+ else {
2025
+ delete this.filter[key];
2224
2026
  }
2225
- }));
2027
+ }
2226
2028
  });
2227
2029
  }
2228
- ngOnDestroy() {
2229
- return __awaiter(this, void 0, void 0, function* () {
2230
- this.selectedGroups$.unsubscribe();
2231
- this.filters$.unsubscribe();
2232
- });
2030
+ onFormLoad(form) {
2031
+ const currentLang = this._translateService.currentLang;
2032
+ this.formComponent.formio.i18next.options.resources[currentLang] = { translation: this._translateService.instant(`Forms`) };
2033
+ this.formComponent.formio.language = currentLang;
2233
2034
  }
2234
- /**
2235
- *
2236
- * Retrieves the data
2237
- *
2238
- */
2239
- fetch(filters) {
2240
- return __awaiter(this, void 0, void 0, function* () {
2241
- try {
2242
- this._loadingService.showLoading();
2243
- let data = yield this._advancedAggregatorService.fetchData(this.config.model, this.params, this.config.query, filters.filters, this.searchExpression, filters.text);
2244
- if (this.postProcessors && Object.keys(this.postProcessors).length > 0 && this.config.postProcessors && this.config.postProcessors.length > 0) {
2245
- for (let i = 0; i < this.config.postProcessors.length; i++) {
2246
- data = yield this.postProcessors[this.config.postProcessors[i].name](data);
2247
- }
2248
- }
2249
- this.body = this._advancedAggregatorService.formatData(this.config, data);
2250
- this._changeDetector.markForCheck();
2251
- }
2252
- catch (err) {
2253
- console.error(err);
2254
- }
2255
- finally {
2256
- this._loadingService.hideLoading();
2257
- }
2258
- });
2035
+ ngAfterViewInit() {
2036
+ //
2259
2037
  }
2260
- /**
2261
- *
2262
- * Triggers the event handler event
2263
- *
2264
- * @param {*} event The event data
2265
- *
2266
- */
2267
- onActionEvent(event) {
2268
- this.eventHandler.emit(event);
2038
+ ngOnDestroy() {
2039
+ if (this.filterSubscription) {
2040
+ this.filterSubscription.unsubscribe();
2041
+ }
2269
2042
  }
2270
2043
  }
2271
- AdvancedAggregatorComponent.decorators = [
2044
+ AdvancedSearchFormComponent.decorators = [
2272
2045
  { type: Component, args: [{
2273
- selector: 'app-advanced-aggregator',
2274
- 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>",
2275
- changeDetection: ChangeDetectionStrategy.OnPush
2046
+ selector: 'app-advanced-search-form',
2047
+ template: `
2048
+ <div class="p-4">
2049
+ <formio #formioComponent class="formio-search" (keydown)="onKeyEnter($event)" (change)="onChange($event)" (formLoad)="onFormLoad($event)"
2050
+ [refresh]="refreshForm" [submission]="submission" [form]="form"></formio>
2051
+ </div>
2052
+ `,
2053
+ encapsulation: ViewEncapsulation.None,
2054
+ 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}"]
2276
2055
  }] }
2277
2056
  ];
2278
2057
  /** @nocollapse */
2279
- AdvancedAggregatorComponent.ctorParameters = () => [
2280
- { type: ChangeDetectorRef },
2281
- { type: AdvancedAggregatorService },
2282
- { type: LoadingService }
2058
+ AdvancedSearchFormComponent.ctorParameters = () => [
2059
+ { type: AngularDataContext },
2060
+ { type: TranslateService },
2061
+ { type: ActivatedRoute },
2062
+ { type: HttpClient }
2283
2063
  ];
2284
- AdvancedAggregatorComponent.propDecorators = {
2285
- config: [{ type: Input, args: ['config',] }],
2286
- aggregators: [{ type: Input, args: ['aggregators',] }],
2287
- postProcessors: [{ type: Input, args: ['postProcessors',] }],
2288
- params: [{ type: Input, args: ['params',] }],
2289
- selectedGroups$: [{ type: Input, args: ['selectedGroups',] }],
2290
- filters$: [{ type: Input, args: ['filters',] }],
2291
- searchExpression: [{ type: Input, args: ['searchExpression',] }],
2292
- eventHandler: [{ type: Output }]
2293
- };
2294
-
2295
- var Lengths;
2296
- (function (Lengths) {
2297
- Lengths["XL"] = "xl";
2298
- Lengths["LG"] = "lg";
2299
- Lengths["MD"] = "md";
2300
- Lengths["SM"] = "sm";
2301
- Lengths["XS"] = "xs";
2302
- })(Lengths || (Lengths = {}));
2303
- class AdvancedAggregatorListComponent {
2304
- constructor(_template) {
2305
- this._template = _template;
2306
- this.clickEvent = new EventEmitter();
2307
- }
2308
- ngOnInit() {
2309
- this.config.columns.forEach(x => {
2310
- x.length = Object.values(Lengths).includes(x.length) ? this._template.transform(`sis--list-group-item_${x.length}_column`, x) :
2311
- !x.length ? 'sis--list-group-item_sm_column' : x.length;
2312
- });
2313
- }
2314
- /**
2315
- *
2316
- * Triggers the output event emitter
2317
- *
2318
- * @param type The type of the event as it's declared in the configuration
2319
- * @param item The value of the current item
2320
- * }
2321
- */
2322
- callCallback(type, data) {
2323
- this.clickEvent.emit({
2324
- type,
2325
- data
2326
- });
2327
- }
2328
- }
2329
- AdvancedAggregatorListComponent.decorators = [
2330
- { type: Component, args: [{
2331
- selector: 'app-advanced-aggregator-list',
2332
- 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 *ngIf=\"action.type === 'edit'\" class=\"fas fa-edit text-indigo\"></i>\n </button>\n <span class=\"px-0\">\n <i class=\"fa fa-fw\">&nbsp;</i>\n </span>\n\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 <button *ngIf=\"config.columns[i].type === 'action' && config.columns[i].variant === 'edit'\"\n (click)=\"callCallback(config.columns[i].identifier, entry)\"\n [title]=\"(config.columns[i].title ? config.columns[i].title : '') | translate \"\n class=\"btn btn-default px-0 mx-0\">\n <i class=\"fas fa-edit text-indigo\"></i>\n </button>\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 <div *ngIf=\"entry && entry.subitems\">\n <div *ngFor=\"let subitem of entry.subitems\" class=\"d-flex justify-content-center align-items-center list-color\">\n <div *ngIf=\"config.actions && config.actions.length > 0\" class=\"sis--list-group-item_sm_column text-left pr-0\">\n <span class=\"px-0\">\n <i class=\"fa fa-fw\">&nbsp;</i>\n </span>\n <button *ngFor=\"let action of config.actions\" (click)=\"callCallback(action.identifier, subitem)\"\n [title]=\"(action.title ? action.title : '') | translate \" class=\"btn btn-default px-0\">\n <i *ngIf=\"action.type === 'edit'\" class=\"fas fa-edit text-indigo\"></i>\n </button>\n </div>\n <div class=\"text-left\"></div>\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') && 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 <span *ngIf=\"config.columns[i].type === 'boolean'\">\n <i class=\"fa font-lg\" [ngClass]=\"{\n 'fa-check text-success': subitem[column.name].toString() === config.columns[i].trueValue,\n 'fa-times text-danger': subitem[column.name].toString() !== config.columns[i].trueValue && config.columns[i].variant !== 'checkOrDash'\n }\"></i>\n <span\n *ngIf=\"subitem[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 </div>\n </li>\n </ul>\n</div>\n"
2333
- }] }
2334
- ];
2335
- /** @nocollapse */
2336
- AdvancedAggregatorListComponent.ctorParameters = () => [
2337
- { type: TemplatePipe }
2338
- ];
2339
- AdvancedAggregatorListComponent.propDecorators = {
2340
- config: [{ type: Input }],
2341
- body: [{ type: Input }],
2342
- clickEvent: [{ type: Output }]
2064
+ AdvancedSearchFormComponent.propDecorators = {
2065
+ formComponent: [{ type: ViewChild, args: ['formioComponent',] }],
2066
+ form: [{ type: Input }],
2067
+ formSrc: [{ type: Input }],
2068
+ refreshForm: [{ type: Output }]
2343
2069
  };
2344
2070
 
2345
- class AdvancedListComponent {
2346
- constructor(_context, _translateService, _activatedRoute, _errorService, _toastService, _modalService, _userActivityService) {
2347
- this._context = _context;
2348
- this._translateService = _translateService;
2349
- this._activatedRoute = _activatedRoute;
2350
- this._errorService = _errorService;
2351
- this._toastService = _toastService;
2352
- this._modalService = _modalService;
2353
- this._userActivityService = _userActivityService;
2354
- this.allowSaveOption = true;
2355
- }
2356
- ngOnDestroy() {
2357
- if (this.dataSubscription) {
2358
- this.dataSubscription.unsubscribe();
2359
- }
2360
- if (this.reloadSubscription) {
2361
- this.reloadSubscription.unsubscribe();
2362
- }
2363
- if (this.subscription) {
2364
- this.subscription.unsubscribe();
2365
- }
2366
- }
2367
- ngOnInit() {
2368
- return __awaiter(this, void 0, void 0, function* () {
2369
- try {
2370
- // get metadata
2371
- const metadata = yield this._context.getMetadata();
2372
- // get route data
2373
- this.dataSubscription = this._activatedRoute.data.subscribe(routeData => {
2374
- // get entityType
2375
- const re = new RegExp(`^${routeData.model}$`, 'ig');
2376
- const entitySet = metadata.EntityContainer.EntitySet.find(x => {
2377
- return re.test(x.Name);
2378
- });
2379
- // if entity type is null
2380
- if (entitySet == null) {
2381
- // navigate to error
2382
- return this._errorService.navigateToError(new ResponseError('EntitySet Not Found', 404));
2383
- }
2384
- // get entity type
2385
- const entityType = metadata.EntityType.find(x => {
2386
- return x.Name === entitySet.EntityType;
2387
- });
2388
- if (entityType == null) {
2389
- return this._errorService.navigateToError(new ResponseError('EntityType Not Found', 404));
2390
- }
2391
- if (routeData.searchConfiguration) {
2392
- if (Object.prototype.hasOwnProperty.call(routeData.searchConfiguration, 'allowSave')) {
2393
- this.allowSaveOption = routeData.searchConfiguration.allowSave;
2394
- }
2395
- this.searchConfiguration = routeData.searchConfiguration;
2396
- }
2397
- if (routeData.tableConfiguration) {
2398
- this.tableConfiguration = routeData.tableConfiguration;
2399
- }
2400
- if (routeData.description) {
2401
- this.description = this._translateService.instant(routeData.description);
2402
- }
2403
- if (routeData.longDescription) {
2404
- this.longDescription = this._translateService.instant(routeData.longDescription);
2405
- }
2406
- if (routeData.category) {
2407
- this.category = this._translateService.instant(routeData.category);
2408
- }
2409
- // clone default configuration
2410
- const defaultConfiguration = AdvancedTableConfiguration.cast(this.tableConfiguration, true);
2411
- // correct edit button link
2412
- const column = defaultConfiguration.columns.find(col => {
2413
- return col.formatter === 'ButtonFormatter' && col.formatString === '(modal:edit)';
2414
- });
2415
- if (column) {
2416
- const url = [];
2417
- this._activatedRoute.snapshot.pathFromRoot.forEach(route => {
2418
- url.push.apply(url, route.url);
2419
- });
2420
- column.formatString = `#/${url.join('/')}/(modal:\$\{id\}/edit)`;
2421
- }
2422
- // create list configuration by assigning list entity set
2423
- this.table.config = Object.assign({
2424
- model: entitySet.Name
2425
- }, defaultConfiguration);
2426
- // fetch data
2427
- this.table.fetch(true);
2428
- // do reload by using hidden fragment e.g. /classes#reload
2429
- this.reloadSubscription = this._activatedRoute.fragment.subscribe(fragment => {
2430
- if (fragment && fragment === 'reload') {
2431
- this._toastService.show(this.description, this._translateService.instant('Settings.OperationCompleted'));
2432
- this.table.fetch(true);
2433
- }
2434
- });
2435
- // finally add user activity
2436
- this._userActivityService.setItem({
2437
- category: this.category,
2438
- description: this.description,
2439
- url: window.location.hash.substring(1),
2440
- dateCreated: new Date
2441
- });
2442
- });
2443
- }
2444
- catch (err) {
2445
- console.error(err);
2446
- return this._errorService.showError(err);
2447
- }
2448
- });
2449
- }
2450
- /**
2451
- * Data load event handler of advanced table component
2452
- * @param {AdvancedTableDataResult} data
2453
- */
2454
- onDataLoad(data) {
2455
- this.recordsTotal = data.recordsTotal;
2456
- }
2457
- /**
2458
- * Remove selected items
2459
- */
2460
- remove() {
2461
- if (this.table && this.table.selected && this.table.selected.length) {
2462
- // get items to remove
2463
- const items = this.table.selected;
2464
- return this._modalService.showWarningDialog(this._translateService.instant('Tables.RemoveItemsTitle'), this._translateService.instant('Tables.RemoveItemsMessage'), DIALOG_BUTTONS.OkCancel).then(result => {
2465
- if (result === 'ok') {
2466
- this._context.model(this.table.config.model).remove(items).then(() => {
2467
- this._toastService.show(this.description + ':' + this._translateService.instant('Tables.RemoveItemMessage.title'), this._translateService.instant((items.length === 1 ?
2468
- 'Tables.RemoveItemMessage.one' : 'Tables.RemoveItemMessage.many'), { value: items.length }));
2469
- this.table.fetch(true);
2470
- }).catch(err => {
2471
- this._errorService.showError(err, {
2472
- continueLink: '.'
2473
- });
2474
- });
2475
- }
2476
- });
2477
- }
2478
- }
2479
- }
2480
- AdvancedListComponent.decorators = [
2481
- { type: Component, args: [{
2482
- selector: 'app-advanced-list',
2483
- 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"
2484
- }] }
2485
- ];
2486
- /** @nocollapse */
2487
- AdvancedListComponent.ctorParameters = () => [
2488
- { type: AngularDataContext },
2489
- { type: TranslateService },
2490
- { type: ActivatedRoute },
2491
- { type: ErrorService },
2492
- { type: ToastService },
2493
- { type: ModalService },
2494
- { type: UserActivityService }
2495
- ];
2496
- AdvancedListComponent.propDecorators = {
2497
- entitySet: [{ type: Input, args: ['entitySet',] }],
2498
- category: [{ type: Input, args: ['category',] }],
2499
- description: [{ type: Input, args: ['description',] }],
2500
- longDescription: [{ type: Input, args: ['longDescription',] }],
2501
- tableConfiguration: [{ type: Input, args: ['tableConfiguration',] }],
2502
- searchConfiguration: [{ type: Input, args: ['searchConfiguration',] }],
2503
- table: [{ type: ViewChild, args: ['table',] }],
2504
- allowSaveOption: [{ type: Input, args: ['allowSaveOption',] }]
2505
- };
2071
+ // important: export this const due to AOT compilation errors
2072
+ // see more https://angular.io/guide/aot-metadata-errors#only-initialized-variables
2073
+ const AdvancedTableModalBaseTemplate = `
2074
+ <div>
2075
+ <!-- search -->
2076
+ <div class="row mt-3">
2077
+ <div class="col-12 col-lg-3">
2078
+ <div class="input-group mb-3">
2079
+ <input spellcheck="false" type="text"
2080
+ (keyup)="onKeyUp($event)" [(ngModel)]="searchText"
2081
+ class="form-control bg-gray-100 border-0"
2082
+ placeholder="{{'Tables.SearchPlaceholder' | translate}}" aria-label="Search" aria-describedby="basic-addon2">
2083
+ <div class="input-group-append">
2084
+ <button class="btn btn-default bg-gray-100 border-0" type="button">
2085
+ <i class="fa fa-search text-secondary"></i>
2086
+ </button>
2087
+ </div>
2088
+ </div>
2089
+ </div>
2090
+ <div #emptyTable class="d-none">
2091
+ <div>
2092
+ <div class="mt-4">
2093
+ <div class="icon-circle bg-gray-300 border-gray-100"></div>
2094
+ </div>
2095
+ <div class="mt-4">
2096
+ <h4 class="font-3xl text-dark" [translate]="'Tables.EmptyTableTitle'"></h4>
2097
+ <p class="font-lg text-dark" [translate]="'Tables.EmptyTableMessage'"></p>
2098
+ </div>
2506
2099
 
2507
- class AdvancedRowActionComponent extends RouterModalOkCancel {
2508
- constructor(router, activatedRoute, _translateService, _context, _errorService, _modalService, _appEvent) {
2509
- super(router, activatedRoute);
2510
- this._translateService = _translateService;
2100
+ </div>
2101
+ </div>
2102
+ <div class="col-12 col-lg-5">
2103
+ <div class="btn-toolbar">
2104
+ <button class="btn btn-gray-100">
2105
+ <i class="fa fa-sliders-h pr-2 text-secondary" aria-hidden="true"></i>{{'Tables.Filters' | translate}}
2106
+ </button>
2107
+ <div #dropdownFilter class="dropdown ml-3">
2108
+ <button class="btn btn-gray-100 dropdown-toggle"
2109
+ type="button" id="dropdownDefaultFilters"
2110
+ data-toggle="dropdown" dropdownToggle>
2111
+ {{ this.defaultFilter.name | translate }}
2112
+ </button>
2113
+ <div class="dropdown-menu" aria-labelledby="dropdownDefaultFilters">
2114
+ <button type="button" class="dropdown-item" *ngFor="let filter of defaultFilters" (click)="onFilterSelect(filter)">
2115
+ {{ filter.name | translate }}
2116
+ </button>
2117
+ </div>
2118
+ </div>
2119
+ </div>
2120
+ </div>
2121
+ <div class="col text-lg-right mt-3 mt-lg-0 d-none">
2122
+ <button [disabled]="true" class="btn rounded-pill btn-outline-theme">
2123
+ <span class="pr-2 font-lg">+</span><span [translate]="'Tables.AddItem'"></span>
2124
+ </button>
2125
+ </div>
2126
+ </div>
2127
+ <div class="row">
2128
+ <div class="col-12" [ngClass]="{ 'component-loading': loading}">
2129
+ <app-advanced-table #advancedTable (init)="onTableInit($event)"
2130
+ (load)="onDataLoad($event)"
2131
+ [selectable]="true"
2132
+ [multipleSelect]="true"
2133
+ [scrollX]="false" [scrollY]="320"
2134
+ [pageLength]="pageLength"
2135
+ [scrollable]="false" [showFooter]="false"
2136
+ [showActions]="false" [lengthMenu]="lengthMenu"
2137
+ [config]="tableConfig" [configSrc]="tableConfigSrc"></app-advanced-table>
2138
+ </div>
2139
+ </div>
2140
+ <div class="row mt-3 table-custom-pager">
2141
+ <div class="col-12 d-flex" *ngIf="recordsTotal">
2142
+ <div class="mt-2 ml-auto mr-3 text-secondary" [translate]="'Tables.RecordsPerPage'"></div>
2143
+ <div #dropdownLength class="dropdown mr-3">
2144
+ <button class="btn btn-gray-100 dropdown-toggle"
2145
+ type="button" id="dropdownPageSize" data-toggle="dropdown"
2146
+ dropdownToggle>
2147
+ {{pageLength}}
2148
+ </button>
2149
+ <div class="dropdown-menu" aria-labelledby="dropdownPageSize">
2150
+ <button (click)="setLength(item)"
2151
+ class="dropdown-item dropdown-item-theme"
2152
+ type="button" *ngFor="let item of lengthMenu">{{item}}</button>
2153
+ </div>
2154
+ </div>
2155
+ <div class="mt-2 mr-2" [translate]="'Tables.PageInfo'" [translateParams]="pageInfo"></div>
2156
+ <button [disabled]="pageInfo && pageInfo.page === 0" class="btn text-nowrap" (click)="firstPage()">
2157
+ <i class="fa fa-chevron-left text-color-theme"></i>
2158
+ <i class="fa fa-chevron-left text-color-theme"></i>
2159
+ </button>
2160
+ <button [disabled]="pageInfo && pageInfo.page===0" class="btn" (click)="previousPage()">
2161
+ <i class="fa fa-chevron-left text-color-theme"></i>
2162
+ </button>
2163
+ <button [disabled]="pageInfo && pageInfo.page===pageInfo.pages-1" class="btn" (click)="nextPage()">
2164
+ <i class="fa fa-chevron-right text-color-theme"></i>
2165
+ </button>
2166
+ <button [disabled]="pageInfo && pageInfo.page===pageInfo.pages-1" class="btn text-nowrap" (click)="lastPage()">
2167
+ <i class="fa fa-chevron-right text-color-theme"></i>
2168
+ <i class="fa fa-chevron-right text-color-theme"></i>
2169
+ </button>
2170
+ </div>
2171
+ </div>
2172
+ </div>
2173
+ `;
2174
+ class AdvancedTableModalBaseComponent extends RouterModalOkCancel {
2175
+ constructor(_router, _activatedRoute, _context, advancedFilterValueProvider, datePipe) {
2176
+ super(_router, _activatedRoute);
2511
2177
  this._context = _context;
2512
- this._errorService = _errorService;
2513
- this._modalService = _modalService;
2514
- this._appEvent = _appEvent;
2515
- this.modalIcon = 'fas fa-project-diagram';
2516
- this.progressType = 'indigo';
2517
- this.errorMessage = 'Tables.Actions.CompletedWithErrors';
2178
+ this.advancedFilterValueProvider = advancedFilterValueProvider;
2179
+ this.datePipe = datePipe;
2180
+ this.pageLength = 25;
2181
+ this.lengthMenu = [5, 10, 25, 50, 100];
2182
+ this.recordsTotal = 0;
2518
2183
  this.loading = false;
2519
- this.refresh = new EventEmitter();
2520
- // set modal size
2521
- this.modalClass = 'modal-lg';
2522
- // set modal title
2523
- this.modalTitle = 'Tables.Actions.Label';
2524
- // disable ok button
2525
- this.okButtonDisabled = false;
2526
- // set button text
2527
- this.okButtonText = 'Tables.Actions.Start';
2528
- }
2529
- get form() {
2530
- return this.formComponent;
2531
- }
2532
- ngOnInit() {
2533
- this.refreshSubscription = this.refresh.subscribe((value) => {
2534
- if (value && value.progress) {
2535
- let progress = 0;
2536
- if (value.progress < 0) {
2537
- progress = 0;
2538
- }
2539
- else if (value.progress > 100) {
2540
- progress = 100;
2541
- }
2542
- else {
2543
- progress = value.progress;
2544
- }
2545
- this.progressComponent.value = progress;
2546
- this.showProgress();
2547
- }
2548
- });
2184
+ this.recordsFiltered = 0;
2185
+ this.defaultFilters = [AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER];
2186
+ this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
2187
+ this.pageInfo = {
2188
+ serverSide: true
2189
+ };
2190
+ this.modalClass = 'modal-xl modal-table';
2191
+ this._template = new TemplatePipe();
2549
2192
  }
2550
2193
  ngAfterViewInit() {
2551
- if (this.formComponent && this.formComponent.form) {
2552
- this.okButtonDisabled = true;
2553
- this.invalidSubscription = this.formComponent.form.change.subscribe((event) => {
2554
- if (Object.prototype.hasOwnProperty.call(event, "isValid") && (event.changed != null)) {
2555
- this.okButtonDisabled = !event.isValid;
2556
- }
2557
- });
2558
- }
2194
+ //
2559
2195
  }
2560
- ngOnDestroy() {
2561
- if (this.refreshSubscription) {
2562
- this.refreshSubscription.unsubscribe();
2563
- }
2564
- if (this.executeSubscription) {
2565
- this.executeSubscription.unsubscribe();
2566
- }
2567
- if (this.invalidSubscription) {
2568
- this.invalidSubscription.unsubscribe();
2196
+ _getInputs(componentType) {
2197
+ const props = componentType.__prop__metadata__;
2198
+ const inputs = [];
2199
+ if (props == null) {
2200
+ return inputs;
2569
2201
  }
2202
+ Object.keys(props).forEach(prop => {
2203
+ const member = props[prop][0];
2204
+ if (member.ngMetadataName === 'Input') {
2205
+ inputs.push(prop);
2206
+ }
2207
+ });
2208
+ return inputs.sort();
2570
2209
  }
2571
- cancel() {
2572
- if (this.loading) {
2573
- return;
2574
- }
2575
- // close
2576
- if (this._modalService.modalRef) {
2577
- return this._modalService.modalRef.hide();
2578
- }
2210
+ hasInputs() {
2211
+ return [];
2579
2212
  }
2580
- ok() {
2581
- return __awaiter(this, void 0, void 0, function* () {
2582
- try {
2583
- this.loading = true;
2584
- this.lastError = null;
2585
- if (this.formComponent && this.formComponent.form &&
2586
- this.formComponent.form.formio && this.formComponent.form.formio.data) {
2587
- // set data
2588
- this.data = this.formComponent.form.formio.data;
2589
- }
2590
- // do action
2591
- this.execute.subscribe((result) => {
2592
- this.loading = false;
2593
- this.refresh.emit({
2594
- progress: 100
2595
- });
2596
- if (result && result.errors && result.errors > 0) {
2597
- // show message for partial success
2598
- if (result.errors === 1) {
2599
- this.lastError = new Error(this._translateService.instant(`${this.errorMessage}.Description.One`));
2600
- }
2601
- else {
2602
- this.lastError = new Error(this._translateService.instant(`${this.errorMessage}.Description.Many`, result));
2603
- }
2604
- this.refresh.emit({
2605
- progress: 1
2606
- });
2607
- this.hideProgress();
2608
- return;
2609
- }
2610
- if (this._modalService.modalRef) {
2611
- this._modalService.modalRef.hide();
2612
- }
2613
- }, (err) => {
2614
- this.loading = false;
2615
- this.lastError = err;
2616
- this.refresh.emit({
2617
- progress: 0
2618
- });
2619
- });
2213
+ ngOnInit() {
2214
+ this.okButtonText = 'Tables.Apply';
2215
+ this.cancelButtonText = 'Tables.Cancel';
2216
+ // get route data
2217
+ this.subscription = this.activatedRoute.data.subscribe(data => {
2218
+ // set modal title if any
2219
+ if (data.title) {
2220
+ this.modalTitle = data.title;
2620
2221
  }
2621
- catch (err) {
2622
- this.loading = false;
2623
- this.lastError = err;
2222
+ if (data.config) {
2223
+ this.tableConfig = data.config;
2224
+ }
2225
+ if (this.tableConfig) {
2226
+ this.defaultFilters.push.apply(this.defaultFilters, this.tableConfig.searches);
2624
2227
  }
2228
+ // important: use hasInputs due to AOT compilation. Metadata properties cannot be used (e.g. @Input())
2229
+ // because there are empty during runtime
2230
+ // so we need to define manually input properties that are going to be bind through this process
2231
+ // bind all inputs
2232
+ const inputs = this.hasInputs();
2233
+ inputs.forEach(input => {
2234
+ if (Object.prototype.hasOwnProperty.call(data, input)) {
2235
+ this[input] = data[input];
2236
+ }
2237
+ });
2238
+ this.configChangesSubscription = this.advancedTable.configChanges.subscribe((config) => {
2239
+ if (this.tableConfig) {
2240
+ // reset default filter
2241
+ this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
2242
+ // init filter
2243
+ this.defaultFilters = [AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER];
2244
+ // add extra filters
2245
+ this.defaultFilters.push.apply(this.defaultFilters, this.tableConfig.searches);
2246
+ }
2247
+ });
2625
2248
  });
2626
2249
  }
2627
- showProgress() {
2628
- this.progressContainer.nativeElement.classList.replace('invisible', 'visible');
2250
+ cancel() {
2251
+ return this.close();
2629
2252
  }
2630
- hideProgress() {
2631
- this.progressContainer.nativeElement.classList.replace('visible', 'invisible');
2253
+ ok() {
2254
+ return this.close();
2632
2255
  }
2633
- }
2634
- AdvancedRowActionComponent.decorators = [
2635
- { type: Component, args: [{
2636
- selector: 'app-advanced-row-action',
2637
- 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 invisible\" #progressContainer>\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",
2638
- encapsulation: ViewEncapsulation.None,
2639
- styles: [`
2640
- .fa-1_5x {
2641
- font-size: 1.5rem;
2642
- }
2643
- .form-control:disabled, .form-control[readonly] {
2644
- background-color: inherit;
2645
- }
2646
- .form-check.checkbox {
2647
- position: relative
2648
- }
2649
- `]
2650
- }] }
2651
- ];
2652
- /** @nocollapse */
2653
- AdvancedRowActionComponent.ctorParameters = () => [
2654
- { type: Router },
2655
- { type: ActivatedRoute },
2656
- { type: TranslateService },
2657
- { type: AngularDataContext },
2658
- { type: ErrorService },
2659
- { type: ModalService },
2660
- { type: AppEventService }
2661
- ];
2662
- AdvancedRowActionComponent.propDecorators = {
2663
- items: [{ type: Input }],
2664
- modalIcon: [{ type: Input }],
2665
- description: [{ type: Input }],
2666
- formTemplate: [{ type: Input }],
2667
- progressType: [{ type: Input }],
2668
- data: [{ type: Output }],
2669
- additionalMessage: [{ type: Input }],
2670
- errorMessage: [{ type: Input }],
2671
- progressComponent: [{ type: ViewChild, args: ['progress',] }],
2672
- progressContainer: [{ type: ViewChild, args: ['progressContainer',] }],
2673
- formComponent: [{ type: ViewChild, args: ['formComponent',] }],
2674
- refresh: [{ type: Input }],
2675
- execute: [{ type: Input }]
2676
- };
2677
-
2678
- class AdvancedTableSearchBaseComponent {
2679
- constructor() {
2680
- this.filterChange = new EventEmitter();
2256
+ ngOnDestroy() {
2257
+ if (this.subscription) {
2258
+ this.subscription.unsubscribe();
2259
+ }
2681
2260
  }
2682
- get filter() {
2683
- return this._filter;
2261
+ onDataLoad(data) {
2262
+ this.recordsTotal = data.recordsTotal;
2263
+ this.recordsFiltered = data.recordsFiltered;
2264
+ // get first page info (because page is not going to be fired for first page)
2265
+ if (data.recordsTotal > 0 && this.pageInfo.recordsTotal === 0) {
2266
+ this.pageInfo.recordsTotal = data.recordsTotal;
2267
+ }
2684
2268
  }
2685
- set filter(value) {
2686
- this._filter = value;
2269
+ previousPage() {
2270
+ // go to next page
2271
+ // noinspection TypeScriptValidateJSTypes
2272
+ this.advancedTable.dataTable.page('previous').draw('page');
2687
2273
  }
2688
- onKeyEnter(event) {
2274
+ nextPage() {
2275
+ // go to next page
2276
+ // noinspection TypeScriptValidateJSTypes
2277
+ this.advancedTable.dataTable.page('next').draw('page');
2278
+ }
2279
+ onTableInit(dataTable) {
2280
+ const self = this;
2281
+ // get initial page info
2282
+ const initialPageInfo = dataTable.page.info();
2283
+ initialPageInfo.start += 1;
2284
+ self.pageInfo = initialPageInfo;
2285
+ // change emptyTableMessage
2286
+ dataTable.context[0].oLanguage.sEmptyTable = this.emptyTable.nativeElement.innerHTML;
2287
+ // bind page change event
2288
+ dataTable.on('page.dt', function () {
2289
+ const pageInfo = dataTable.page.info();
2290
+ pageInfo.start += 1;
2291
+ self.pageInfo = pageInfo;
2292
+ });
2293
+ // bind draw change event
2294
+ dataTable.on('draw.dt', function () {
2295
+ // for future use
2296
+ self.loading = false;
2297
+ const pageInfo = dataTable.page.info();
2298
+ pageInfo.start += 1;
2299
+ self.pageInfo = pageInfo;
2300
+ });
2301
+ }
2302
+ firstPage() {
2303
+ // go to first page
2304
+ // noinspection TypeScriptValidateJSTypes
2305
+ this.advancedTable.dataTable.page('first').draw('page');
2306
+ }
2307
+ lastPage() {
2308
+ // go to last page
2309
+ // noinspection TypeScriptValidateJSTypes
2310
+ this.advancedTable.dataTable.page('last').draw('page');
2311
+ }
2312
+ setLength(value) {
2313
+ this.pageLength = value;
2314
+ this.advancedTable.dataTable.page.len(value).draw();
2315
+ }
2316
+ onKeyUp(event) {
2689
2317
  if (event.code === 'Enter') {
2690
- this.filterChange.emit(this._filter);
2318
+ // loading
2319
+ this.loading = true;
2320
+ this.advancedTable.search(event.target.value);
2691
2321
  }
2692
2322
  }
2693
- }
2694
- AdvancedTableSearchBaseComponent.propDecorators = {
2695
- filterChange: [{ type: Output }],
2696
- filter: [{ type: Input }]
2697
- };
2698
-
2699
- class AdvancedSearchFormComponent extends AdvancedTableSearchBaseComponent {
2700
- constructor(_context, _translateService, _activatedRoute, _http) {
2701
- super();
2702
- this._context = _context;
2703
- this._translateService = _translateService;
2704
- this._activatedRoute = _activatedRoute;
2705
- this._http = _http;
2706
- this.form = {};
2707
- this.refreshForm = new EventEmitter();
2708
- this.submission = { data: {} };
2709
- this.loading = true;
2710
- }
2711
- ngOnChanges(changes) {
2712
- if (Object.prototype.hasOwnProperty.call(changes, 'form')) {
2713
- if (changes.form != null) {
2714
- if (changes.form.currentValue &&
2715
- changes.form.currentValue != changes.form.previousValue) {
2716
- this.ngOnInit();
2717
- }
2718
- }
2323
+ /**
2324
+ *
2325
+ * Reads the modal table and returns a list of filters to use in the table
2326
+ * query.
2327
+ *
2328
+ * @param tableConfig {object} The table configuration from json file
2329
+ * @param defaultFilterValues {object} A list of default filters
2330
+ * @param {object} searchItem The searchItem to use
2331
+ *
2332
+ */
2333
+ getFilters(tableConfig, defaultFilterValues = {}, searchItem) {
2334
+ // Do nothing in absence of configuration.
2335
+ if (!tableConfig || !Array.isArray(tableConfig.criteria)) {
2336
+ return [];
2719
2337
  }
2720
- if (Object.prototype.hasOwnProperty.call(changes, 'formSrc')) {
2721
- if (changes.formSrc != null) {
2722
- if (changes.formSrc.currentValue &&
2723
- changes.formSrc.currentValue != changes.formSrc.previousValue) {
2724
- this._http.get(changes.formSrc.currentValue).toPromise().then((form) => {
2725
- // set form
2726
- this.form = form;
2727
- // and init
2728
- this.ngOnInit();
2729
- }).catch((err) => {
2730
- console.error('An error occurred while loading search configuration');
2731
- console.error(err);
2732
- });
2733
- }
2734
- }
2338
+ if (searchItem.filter == null) {
2339
+ return [];
2735
2340
  }
2736
- if (Object.prototype.hasOwnProperty.call(changes, 'filter')) {
2737
- if (changes.filter != null) {
2738
- this.submission.data = changes.filter.currentValue || {};
2739
- this.refreshForm.emit({
2740
- submission: this.submission
2741
- });
2742
- }
2341
+ const matchedCriterion = tableConfig.criteria.find(criterion => Object.prototype.hasOwnProperty.call(searchItem.filter, criterion.name));
2342
+ if (matchedCriterion == null) {
2343
+ return [];
2743
2344
  }
2744
- }
2745
- ngOnInit() {
2746
- if (this.form) {
2747
- new ServiceUrlPreProcessor(this._context).parse(this.form);
2748
- new TranslatePreProcessor(this._translateService).parse(this.form);
2749
- this.submission = {
2750
- data: this.filter
2751
- };
2752
- this.refreshForm.emit({
2753
- form: this.form
2754
- });
2755
- this.loading = false;
2756
- this.filterSubscription = this.filterChange.subscribe(filter => {
2757
- this.submission.data = filter;
2758
- this.refreshForm.emit({
2759
- submission: this.submission
2760
- });
2345
+ const value = searchItem.filter[matchedCriterion.name];
2346
+ const criterionSafeName = matchedCriterion.name !== null
2347
+ ? matchedCriterion.name.toString()
2348
+ : '';
2349
+ let applicableFilter;
2350
+ // Raw dates should be transformed to a specific format
2351
+ if (criterionSafeName.includes('GMT')) {
2352
+ const newDate = this.datePipe.transform(criterionSafeName, 'MM/dd/yyyy');
2353
+ applicableFilter = this._template.transform(matchedCriterion.filter, {
2354
+ value: newDate
2761
2355
  });
2762
2356
  }
2357
+ else if (criterionSafeName !== 'undefined') {
2358
+ applicableFilter = (this._template.transform(matchedCriterion.filter, Object.assign({
2359
+ value: value
2360
+ }, defaultFilterValues)));
2361
+ }
2362
+ else {
2363
+ applicableFilter = '';
2364
+ }
2365
+ return [applicableFilter];
2763
2366
  }
2764
- onChange(event) {
2765
- Object.keys(this.formComponent.submission.data).forEach(key => {
2766
- if (Object.prototype.hasOwnProperty.call(this.formComponent.submission.data, key)) {
2767
- const value = this.formComponent.submission.data[key];
2768
- // the filter with type is a string and is not empty
2769
- const isEmptyString = typeof value === 'string' && value.length === 0;
2770
- if (!isEmptyString) {
2771
- Object.defineProperty(this.filter, key, {
2772
- configurable: true,
2773
- enumerable: true,
2774
- value: value
2775
- });
2776
- }
2777
- else {
2778
- delete this.filter[key];
2779
- }
2367
+ onFilterSelect(value) {
2368
+ this.advancedSearch(value);
2369
+ }
2370
+ /**
2371
+ *
2372
+ * Runs a filter against the modal table
2373
+ *
2374
+ * @param {object} filter The filter to use in the search
2375
+ *
2376
+ */
2377
+ advancedSearch(filter) {
2378
+ return __awaiter(this, void 0, void 0, function* () {
2379
+ // Do nothing in absence of configuration.
2380
+ if (!this.tableConfig && !Array.isArray(this.tableConfig.criteria)) {
2381
+ console.warn('No table configuration available', this.tableConfig);
2382
+ return;
2383
+ }
2384
+ // An advanced table has some filters by default
2385
+ let defaultFilterValues;
2386
+ try {
2387
+ defaultFilterValues = yield this.advancedFilterValueProvider.getValues();
2388
+ }
2389
+ catch (err) {
2390
+ defaultFilterValues = {};
2391
+ }
2392
+ const expressions = [];
2393
+ let predefinedCriteria;
2394
+ if (filter) {
2395
+ this.defaultFilter = filter;
2396
+ predefinedCriteria = this.getFilters(this.tableConfig, defaultFilterValues, filter);
2397
+ }
2398
+ else {
2399
+ // Empty a falsy filter resets the results
2400
+ this.defaultFilter = AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER;
2780
2401
  }
2402
+ // search filter is applied separately, there is no need to add here.
2403
+ expressions.push(predefinedCriteria);
2404
+ const query = this._context.model(this.advancedTable.config.model).asQueryable();
2405
+ query.setParam('filter', expressions.join(' and ')).prepare();
2406
+ this.advancedTable.query = query;
2407
+ this.advancedTable.fetch();
2781
2408
  });
2782
2409
  }
2783
- onFormLoad(form) {
2784
- const currentLang = this._translateService.currentLang;
2785
- this.formComponent.formio.i18next.options.resources[currentLang] = { translation: this._translateService.instant(`Forms`) };
2786
- this.formComponent.formio.language = currentLang;
2787
- }
2788
- ngAfterViewInit() {
2789
- //
2790
- }
2791
- ngOnDestroy() {
2792
- if (this.filterSubscription) {
2793
- this.filterSubscription.unsubscribe();
2794
- }
2795
- }
2796
2410
  }
2797
- AdvancedSearchFormComponent.decorators = [
2411
+ AdvancedTableModalBaseComponent.DEFAULT_ALL_FILTER = {
2412
+ name: 'Tables.All',
2413
+ filter: null
2414
+ };
2415
+ AdvancedTableModalBaseComponent.decorators = [
2798
2416
  { type: Component, args: [{
2799
- selector: 'app-advanced-search-form',
2800
- template: `
2801
- <div class="p-4">
2802
- <formio #formioComponent class="formio-search" (keydown)="onKeyEnter($event)" (change)="onChange($event)" (formLoad)="onFormLoad($event)"
2803
- [refresh]="refreshForm" [submission]="submission" [form]="form"></formio>
2804
- </div>
2805
- `,
2806
- encapsulation: ViewEncapsulation.None,
2807
- 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}"]
2417
+ // tslint:disable-next-line: component-selector
2418
+ selector: 'app-advanced-table-modal',
2419
+ template: AdvancedTableModalBaseTemplate,
2420
+ styles: [`
2421
+
2422
+ `]
2808
2423
  }] }
2809
2424
  ];
2810
2425
  /** @nocollapse */
2811
- AdvancedSearchFormComponent.ctorParameters = () => [
2812
- { type: AngularDataContext },
2813
- { type: TranslateService },
2426
+ AdvancedTableModalBaseComponent.ctorParameters = () => [
2427
+ { type: Router },
2814
2428
  { type: ActivatedRoute },
2815
- { type: HttpClient }
2429
+ { type: AngularDataContext },
2430
+ { type: AdvancedFilterValueProvider },
2431
+ { type: DatePipe }
2816
2432
  ];
2817
- AdvancedSearchFormComponent.propDecorators = {
2818
- formComponent: [{ type: ViewChild, args: ['formioComponent',] }],
2819
- form: [{ type: Input }],
2820
- formSrc: [{ type: Input }],
2821
- refreshForm: [{ type: Output }]
2433
+ AdvancedTableModalBaseComponent.propDecorators = {
2434
+ advancedTable: [{ type: ViewChild, args: ['advancedTable',] }],
2435
+ emptyTable: [{ type: ViewChild, args: ['emptyTable',] }],
2436
+ dropdownFilter: [{ type: ViewChild, args: ['dropdownFilter',] }],
2437
+ dropdownLength: [{ type: ViewChild, args: ['dropdownLength',] }],
2438
+ tableConfig: [{ type: Input, args: ['tableConfig',] }],
2439
+ tableConfigSrc: [{ type: Input, args: ['tableConfigSrc',] }],
2440
+ pageLength: [{ type: Input, args: ['pageLength',] }],
2441
+ searchText: [{ type: Input, args: ['searchText',] }],
2442
+ lengthMenu: [{ type: Input, args: ['lengthMenu',] }],
2443
+ defaultFilter: [{ type: Input, args: ['defaultFilter',] }]
2822
2444
  };
2823
2445
 
2824
2446
  class AdvancedSelectComponent extends AdvancedTableModalBaseComponent {
@@ -3655,357 +3277,766 @@ class AdvancedTableEditorDirective {
3655
3277
  //
3656
3278
  }
3657
3279
  /**
3658
- * Add rows to table and mark them as added rows
3280
+ * Add rows to table and mark them as added rows
3281
+ */
3282
+ add(...addItems) {
3283
+ if (this.table == null) {
3284
+ throw new AdvancedTableNotNullError();
3285
+ }
3286
+ // decorate items
3287
+ const key = this.table.config.columns[0].property || this.table.config.columns[0].name;
3288
+ for (const element of addItems) {
3289
+ let tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3290
+ if (tr.hasClass('removed')) {
3291
+ tr.toggleClass('removed text-gray-400 text-decoration-line-through');
3292
+ }
3293
+ else {
3294
+ this.table.dataTable.rows.add([element]);
3295
+ tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3296
+ tr.toggleClass('added font-weight-bold text-success');
3297
+ }
3298
+ }
3299
+ this.table.dataTable.draw();
3300
+ // find removed
3301
+ const removedLength = this.table.dataTable.rows('.removed')[0].length;
3302
+ // find added
3303
+ const addedLength = this.table.dataTable.rows('.added')[0].length;
3304
+ // and emit event
3305
+ const items = this.table.dataTable.rows().data();
3306
+ this.table.load.emit({
3307
+ data: items,
3308
+ recordsFiltered: items.length - removedLength,
3309
+ recordsTotal: items.length - removedLength,
3310
+ });
3311
+ this.dirty = (removedLength > 0 || addedLength > 0);
3312
+ this.dirtyChange.emit(this.dirty);
3313
+ }
3314
+ /**
3315
+ * Marks rows as removed rows
3316
+ */
3317
+ remove(...removeItems) {
3318
+ if (this.table == null) {
3319
+ throw new AdvancedTableNotNullError();
3320
+ }
3321
+ // get key
3322
+ const key = this.table.config.columns[0].property || this.table.config.columns[0].name;
3323
+ // loop items
3324
+ for (const element of removeItems) {
3325
+ const tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3326
+ // if row is marked as added
3327
+ if (tr.hasClass('added')) {
3328
+ // remove this dirty row
3329
+ this.table.dataTable.rows(tr).remove();
3330
+ }
3331
+ else {
3332
+ // otherwise unmarked row
3333
+ tr.toggleClass('removed text-gray-400 text-decoration-line-through');
3334
+ }
3335
+ // check if item is selected
3336
+ const selectedIndex = this.table.selected.findIndex((item) => {
3337
+ return item[key] === element[key];
3338
+ });
3339
+ // and remove it from selected collection
3340
+ if (selectedIndex >= 0) {
3341
+ this.table.selected.splice(selectedIndex, 1);
3342
+ }
3343
+ }
3344
+ // re-draw table
3345
+ this.table.dataTable.draw();
3346
+ // get number of table items
3347
+ const items = this.table.dataTable.rows().data();
3348
+ // get number of removed items
3349
+ const removedLength = this.table.dataTable.rows('.removed')[0].length;
3350
+ // get number of added items
3351
+ const addedLength = this.table.dataTable.rows('.added')[0].length;
3352
+ // refresh
3353
+ this.table.load.emit({
3354
+ data: items,
3355
+ recordsFiltered: items.length - removedLength,
3356
+ recordsTotal: items.length - removedLength,
3357
+ });
3358
+ this.dirty = (removedLength > 0 || addedLength > 0);
3359
+ this.dirtyChange.emit(this.dirty);
3360
+ }
3361
+ set(items) {
3362
+ if (this.table == null) {
3363
+ throw new AdvancedTableNotNullError();
3364
+ }
3365
+ // clear rows
3366
+ this.table.selected = [];
3367
+ this.table.dataTable.clear();
3368
+ // set items
3369
+ this.table.dataTable.rows.add(items).draw();
3370
+ this.table.load.emit({
3371
+ data: items,
3372
+ recordsFiltered: items.length,
3373
+ recordsTotal: items.length,
3374
+ });
3375
+ }
3376
+ /**
3377
+ * Undo changes
3378
+ */
3379
+ undo() {
3380
+ if (this.table == null) {
3381
+ throw new AdvancedTableNotNullError();
3382
+ }
3383
+ // reset removed rows
3384
+ this.table.dataTable.rows('.removed').nodes().to$().toggleClass('removed text-gray-400 text-decoration-line-through');
3385
+ // remove added rows
3386
+ this.table.dataTable.rows('.added').remove();
3387
+ // clear selected items
3388
+ this.table.selected = [];
3389
+ const items = this.table.dataTable.rows().data();
3390
+ this.table.dataTable.draw();
3391
+ this.table.load.emit({
3392
+ data: items,
3393
+ recordsFiltered: items.length,
3394
+ recordsTotal: items.length,
3395
+ });
3396
+ this.dirty = false;
3397
+ this.dirtyChange.emit(this.dirty);
3398
+ }
3399
+ rows() {
3400
+ if (this.table == null) {
3401
+ throw new AdvancedTableNotNullError();
3402
+ }
3403
+ return Array.from(this.table.dataTable.rows(':not(.removed)').data());
3404
+ }
3405
+ }
3406
+ AdvancedTableEditorDirective.decorators = [
3407
+ { type: Directive, args: [{
3408
+ // tslint:disable-next-line: directive-selector
3409
+ selector: '[tableEditor]',
3410
+ },] }
3411
+ ];
3412
+ /** @nocollapse */
3413
+ AdvancedTableEditorDirective.ctorParameters = () => [
3414
+ { type: AdvancedTableComponent }
3415
+ ];
3416
+ AdvancedTableEditorDirective.propDecorators = {
3417
+ dirtyChange: [{ type: Output }]
3418
+ };
3419
+
3420
+ class BtnExpDirective {
3421
+ constructor(_activatedTable, _context, _config, _translate, _loadingService, _errorService) {
3422
+ this._activatedTable = _activatedTable;
3423
+ this._context = _context;
3424
+ this._config = _config;
3425
+ this._translate = _translate;
3426
+ this._loadingService = _loadingService;
3427
+ this._errorService = _errorService;
3428
+ }
3429
+ clickEvent(event) {
3430
+ return __awaiter(this, void 0, void 0, function* () {
3431
+ event.preventDefault();
3432
+ event.stopPropagation();
3433
+ if (this._activatedTable.activeTable) {
3434
+ const lastQuery = this._activatedTable.activeTable.lastQuery;
3435
+ const tableConfig = this._activatedTable.activeTable.config;
3436
+ const serviceHeaders = this._context.getService().getHeaders();
3437
+ const queryModel = lastQuery.getModel();
3438
+ let query = this._context.model(queryModel).asQueryable();
3439
+ const queryParams = lastQuery.getParams();
3440
+ query = this.constructQuery(query, queryParams, tableConfig, serviceHeaders);
3441
+ const maxExportItems = 1000;
3442
+ {
3443
+ query.skip(0).take(maxExportItems);
3444
+ }
3445
+ // This constructs the URI based on the way @themost does
3446
+ const url = this.constructURL(query);
3447
+ this.downloadFile(url, query.getService().getHeaders(), query.getModel());
3448
+ }
3449
+ });
3450
+ }
3451
+ /**
3452
+ * Sets the parameters to the query based on
3453
+ * the active filters of the activated table
3454
+ * @param {ClientDataQueryable} query
3455
+ * @param params: The filters of the last query of the activated table
3456
+ * @return ClientDataQueryable
3457
+ */
3458
+ setParams(query, params) {
3459
+ if (params && typeof params === 'object') {
3460
+ Object.keys(params).forEach((key) => {
3461
+ query.setParam(key, params[key]);
3462
+ });
3463
+ }
3464
+ return query;
3465
+ }
3466
+ /**
3467
+ * Sets the headers to the query based on
3468
+ * the default headers of the DataContext
3469
+ * and appending the accept header to get
3470
+ * an xlsx file from the API
3471
+ * @param {ClientDataQueryable} query
3472
+ * @param {Headers} headers: The headers to append to the query
3473
+ * @return ClientDataQueryable
3474
+ */
3475
+ setHeaders(query, headers) {
3476
+ Object.keys(headers).forEach((key) => {
3477
+ if (headers.hasOwnProperty(key)) {
3478
+ query.getService().setHeader(key, headers[key]);
3479
+ }
3480
+ });
3481
+ // sets a header to accept xlsx files
3482
+ query.getService().setHeader('Accept', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
3483
+ return query;
3484
+ }
3485
+ /**
3486
+ * Constructs the select parameter of the
3487
+ * ClientDataQuery by replacing the target name
3488
+ * of the field based on the localized translation
3489
+ * of that property on the advanced tabled
3490
+ * @param {ClientDataQueryable} query
3491
+ * @param tableConfig: The configuration of the activated table
3492
+ * @return ClientDataQueryable
3493
+ */
3494
+ constructQueryParam(query, tableConfig) {
3495
+ let selectQuery = query.getParams().$select;
3496
+ let selectQueryArray = selectQuery.split(',');
3497
+ selectQueryArray = selectQueryArray.map((select) => {
3498
+ const words = select.split(' ');
3499
+ if (words.length === 3) {
3500
+ if (tableConfig && tableConfig.columns) {
3501
+ for (const column of tableConfig.columns) {
3502
+ if (column.property === words[2] && column.title) {
3503
+ words[2] = `"${this._translate.instant(column.title)}"`;
3504
+ }
3505
+ else if (column.property === words[2]) {
3506
+ words[2] = `"${this._translate.instant(words[2])}"`;
3507
+ }
3508
+ }
3509
+ }
3510
+ }
3511
+ if (words.length === 1) {
3512
+ if (tableConfig && tableConfig.columns) {
3513
+ for (const column of tableConfig.columns) {
3514
+ if (column.name === words[0] && column.title) {
3515
+ words[0] = `${words[0]} as "${this._translate.instant(column.title)}"`;
3516
+ break;
3517
+ }
3518
+ else if (column.name === words[0] && column.property) {
3519
+ words[0] = `${words[0]} as "${this._translate.instant(column.property)}"`;
3520
+ break;
3521
+ }
3522
+ else if (words[0] === column.name && words[0] === "id" && column.hidden) {
3523
+ break;
3524
+ }
3525
+ else if (words[0] == column.name || words[0] === column.property) {
3526
+ words[0] = `${words[0]} as "${this._translate.instant(words[0])}"`;
3527
+ }
3528
+ }
3529
+ }
3530
+ return words[0];
3531
+ }
3532
+ return words.join(' ');
3533
+ });
3534
+ selectQuery = selectQueryArray.join(',');
3535
+ query.setParam('$select', selectQuery);
3536
+ return query;
3537
+ }
3538
+ /**
3539
+ * Constructs the fetch URL based the query parameters
3540
+ * @param {ClientDataQueryable} query
3541
+ * @return String
3659
3542
  */
3660
- add(...addItems) {
3661
- if (this.table == null) {
3662
- throw new AdvancedTableNotNullError();
3663
- }
3664
- // decorate items
3665
- const key = this.table.config.columns[0].property || this.table.config.columns[0].name;
3666
- for (const element of addItems) {
3667
- let tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3668
- if (tr.hasClass('removed')) {
3669
- tr.toggleClass('removed text-gray-400 text-decoration-line-through');
3543
+ constructURL(query) {
3544
+ const baseURL = query.getService().getBase();
3545
+ let url = baseURL.endsWith('/') ? (baseURL + query.getModel() + '?') : (baseURL + '/' + query.getModel() + '?');
3546
+ Object.keys(query.getParams()).forEach((key, index) => {
3547
+ if (index === 0) {
3548
+ url += key + '=' + query.getParams()[key];
3670
3549
  }
3671
3550
  else {
3672
- this.table.dataTable.rows.add([element]);
3673
- tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3674
- tr.toggleClass('added font-weight-bold text-success');
3551
+ url += '&' + key + '=' + query.getParams()[key];
3675
3552
  }
3676
- }
3677
- this.table.dataTable.draw();
3678
- // find removed
3679
- const removedLength = this.table.dataTable.rows('.removed')[0].length;
3680
- // find added
3681
- const addedLength = this.table.dataTable.rows('.added')[0].length;
3682
- // and emit event
3683
- const items = this.table.dataTable.rows().data();
3684
- this.table.load.emit({
3685
- data: items,
3686
- recordsFiltered: items.length - removedLength,
3687
- recordsTotal: items.length - removedLength,
3688
3553
  });
3689
- this.dirty = (removedLength > 0 || addedLength > 0);
3690
- this.dirtyChange.emit(this.dirty);
3554
+ return url;
3691
3555
  }
3692
3556
  /**
3693
- * Marks rows as removed rows
3557
+ * Constructs the query by setting the parameters of the
3558
+ * advanced table's active filters, sets the necessary headers
3559
+ * and build the select parameter of the query
3560
+ * @param {ClientDataQueryable} query
3561
+ * @param params: The filters of the last query of the activated table
3562
+ * @param tableConfig: The configuration of the activated table
3563
+ * @param {Headers} headers: The headers to append to the query
3564
+ * @return ClientDataQueryable
3694
3565
  */
3695
- remove(...removeItems) {
3696
- if (this.table == null) {
3697
- throw new AdvancedTableNotNullError();
3698
- }
3699
- // get key
3700
- const key = this.table.config.columns[0].property || this.table.config.columns[0].name;
3701
- // loop items
3702
- for (const element of removeItems) {
3703
- const tr = this.table.dataTable.rows(':has(input[data-id="' + element[key] + '"])').nodes().to$();
3704
- // if row is marked as added
3705
- if (tr.hasClass('added')) {
3706
- // remove this dirty row
3707
- this.table.dataTable.rows(tr).remove();
3566
+ constructQuery(query, params, tableConfig, headers) {
3567
+ let _query = this.setParams(query, params);
3568
+ _query = this.setHeaders(_query, headers);
3569
+ _query = this.constructQueryParam(_query, tableConfig);
3570
+ return _query;
3571
+ }
3572
+ /**
3573
+ * Fetches and saves the xlsx file
3574
+ * @param {String} url
3575
+ * @param {Headers} serviceHeaders: The headers to append to the query
3576
+ * @param modelName: The model name of the advanced table
3577
+ */
3578
+ downloadFile(url, serviceHeaders, modelName) {
3579
+ this._loadingService.showLoading();
3580
+ fetch(encodeURI(url), {
3581
+ headers: serviceHeaders,
3582
+ credentials: 'include'
3583
+ }).then(response => {
3584
+ if (response && !response.ok) {
3585
+ throw new ResponseError(response.statusText, response.status);
3586
+ }
3587
+ return response.blob();
3588
+ }).then(blob => {
3589
+ const objectUrl = window.URL.createObjectURL(blob);
3590
+ const a = document.createElement('a');
3591
+ document.body.appendChild(a);
3592
+ a.setAttribute('style', 'display: none');
3593
+ a.href = objectUrl;
3594
+ const downloadName = `${modelName}.xlsx`;
3595
+ a.download = downloadName;
3596
+ // this adds support for IE and MS Edge (up to version 44.19041)
3597
+ if (window.navigator && window.navigator.msSaveOrOpenBlob) {
3598
+ window.navigator.msSaveOrOpenBlob(blob, downloadName);
3708
3599
  }
3709
3600
  else {
3710
- // otherwise unmarked row
3711
- tr.toggleClass('removed text-gray-400 text-decoration-line-through');
3601
+ // for all other browsers
3602
+ a.click();
3712
3603
  }
3713
- // check if item is selected
3714
- const selectedIndex = this.table.selected.findIndex((item) => {
3715
- return item[key] === element[key];
3604
+ window.URL.revokeObjectURL(objectUrl);
3605
+ a.remove();
3606
+ this._loadingService.hideLoading();
3607
+ }).catch(err => {
3608
+ this._loadingService.hideLoading();
3609
+ this._errorService.showError(err, {
3610
+ continueLink: '.'
3716
3611
  });
3717
- // and remove it from selected collection
3718
- if (selectedIndex >= 0) {
3719
- this.table.selected.splice(selectedIndex, 1);
3720
- }
3721
- }
3722
- // re-draw table
3723
- this.table.dataTable.draw();
3724
- // get number of table items
3725
- const items = this.table.dataTable.rows().data();
3726
- // get number of removed items
3727
- const removedLength = this.table.dataTable.rows('.removed')[0].length;
3728
- // get number of added items
3729
- const addedLength = this.table.dataTable.rows('.added')[0].length;
3730
- // refresh
3731
- this.table.load.emit({
3732
- data: items,
3733
- recordsFiltered: items.length - removedLength,
3734
- recordsTotal: items.length - removedLength,
3735
3612
  });
3736
- this.dirty = (removedLength > 0 || addedLength > 0);
3737
- this.dirtyChange.emit(this.dirty);
3738
3613
  }
3739
- set(items) {
3740
- if (this.table == null) {
3741
- throw new AdvancedTableNotNullError();
3742
- }
3743
- // clear rows
3744
- this.table.selected = [];
3745
- this.table.dataTable.clear();
3746
- // set items
3747
- this.table.dataTable.rows.add(items).draw();
3748
- this.table.load.emit({
3749
- data: items,
3750
- recordsFiltered: items.length,
3751
- recordsTotal: items.length,
3752
- });
3614
+ }
3615
+ BtnExpDirective.decorators = [
3616
+ { type: Directive, args: [{
3617
+ selector: '.universis-btn-export'
3618
+ },] }
3619
+ ];
3620
+ /** @nocollapse */
3621
+ BtnExpDirective.ctorParameters = () => [
3622
+ { type: ActivatedTableService },
3623
+ { type: AngularDataContext },
3624
+ { type: ConfigurationService },
3625
+ { type: TranslateService },
3626
+ { type: LoadingService },
3627
+ { type: ErrorService }
3628
+ ];
3629
+ BtnExpDirective.propDecorators = {
3630
+ clickEvent: [{ type: HostListener, args: ['click', ['$event'],] }]
3631
+ };
3632
+
3633
+ class AdvancedAggregatorService {
3634
+ constructor(_context, _template) {
3635
+ this._context = _context;
3636
+ this._template = _template;
3753
3637
  }
3754
3638
  /**
3755
- * Undo changes
3639
+ *
3640
+ * Formats and executes the data query
3641
+ *
3642
+ * @param {string} model The target model
3643
+ * @param {object} fixedParams Pairs of parameters to always be applied
3644
+ * @param {string} queryStatement The default query to use for fetching the data (e.g. expands etc)
3645
+ * @param {Array<string>} filters A list of filter expressions to append at the query
3646
+ * @param {string} searchTemplate The template to use for searching expressions
3647
+ * @param {string} searchValue The value of the user search
3648
+ *
3649
+ * @returns {Promise<any>} The response of the query execution
3756
3650
  */
3757
- undo() {
3758
- if (this.table == null) {
3759
- throw new AdvancedTableNotNullError();
3760
- }
3761
- // reset removed rows
3762
- this.table.dataTable.rows('.removed').nodes().to$().toggleClass('removed text-gray-400 text-decoration-line-through');
3763
- // remove added rows
3764
- this.table.dataTable.rows('.added').remove();
3765
- // clear selected items
3766
- this.table.selected = [];
3767
- const items = this.table.dataTable.rows().data();
3768
- this.table.dataTable.draw();
3769
- this.table.load.emit({
3770
- data: items,
3771
- recordsFiltered: items.length,
3772
- recordsTotal: items.length,
3651
+ fetchData(model, fixedParams, queryStatement = '', filters, searchTemplate, searchValue) {
3652
+ return __awaiter(this, void 0, void 0, function* () {
3653
+ const query = this._context.model(model).asQueryable();
3654
+ if (!fixedParams || typeof fixedParams !== 'object') {
3655
+ fixedParams = {};
3656
+ }
3657
+ let filterExpressions = [];
3658
+ if (fixedParams) {
3659
+ for (let param of Object.keys(fixedParams)) {
3660
+ filterExpressions.push(`${param} eq ${fixedParams[param]}`);
3661
+ }
3662
+ }
3663
+ if (filters && Array.isArray(filters)) {
3664
+ filterExpressions = [
3665
+ ...filterExpressions,
3666
+ ...filters
3667
+ ];
3668
+ }
3669
+ if (searchTemplate && searchValue) {
3670
+ let evaluatedSearch;
3671
+ if (/^"(.*?)"$/.test(searchValue)) { // validate search text in double quotes
3672
+ evaluatedSearch = this._template.transform(searchTemplate, Object.assign({}, fixedParams, { text: searchValue.replace(/^"|"$/g, '') }));
3673
+ }
3674
+ else {
3675
+ // try to split words
3676
+ const words = searchValue.split(' ');
3677
+ // join words to a single filter
3678
+ evaluatedSearch = words.map(word => {
3679
+ return this._template.transform(searchTemplate, Object.assign({}, fixedParams, { text: word }));
3680
+ }).join(' and ');
3681
+ }
3682
+ filterExpressions.push(evaluatedSearch);
3683
+ }
3684
+ query.setParam('$filter', filterExpressions.join('and'));
3685
+ return query.expand(queryStatement).take(-1).getItems();
3773
3686
  });
3774
- this.dirty = false;
3775
- this.dirtyChange.emit(this.dirty);
3776
3687
  }
3777
- rows() {
3778
- if (this.table == null) {
3779
- throw new AdvancedTableNotNullError();
3688
+ /**
3689
+ *
3690
+ * Formats the received data to use flat keys
3691
+ *
3692
+ * @param config The applied configuration
3693
+ * @param data The raw data
3694
+ *
3695
+ * @returns {Array<any>} The flat-key object list
3696
+ *
3697
+ */
3698
+ formatData(config, data) {
3699
+ try {
3700
+ return data.map((entry) => {
3701
+ if (entry.subitems && Array.isArray(entry.subitems)) {
3702
+ entry.subitems = this.formatData(config, entry.subitems);
3703
+ }
3704
+ let entryObject = {};
3705
+ for (let i = 0; i < config.columns.length; i++) {
3706
+ const keys = config.columns[i].name.split('/');
3707
+ let helperObject = entry[keys[0]];
3708
+ if (helperObject && keys.length > 1) {
3709
+ for (let i = 1; i < keys.length; i++) {
3710
+ if (helperObject === undefined || helperObject === null) {
3711
+ break;
3712
+ }
3713
+ else {
3714
+ helperObject = helperObject[keys[i]] !== undefined && helperObject[keys[i]] !== null
3715
+ ? helperObject[keys[i]]
3716
+ : undefined;
3717
+ }
3718
+ }
3719
+ }
3720
+ entryObject[config.columns[i].name] = helperObject;
3721
+ }
3722
+ if (entry.subitems) {
3723
+ entryObject.subitems = entry.subitems;
3724
+ }
3725
+ return entryObject;
3726
+ });
3727
+ }
3728
+ catch (err) {
3729
+ console.error(err);
3780
3730
  }
3781
- return Array.from(this.table.dataTable.rows(':not(.removed)').data());
3782
3731
  }
3783
3732
  }
3784
- AdvancedTableEditorDirective.decorators = [
3785
- { type: Directive, args: [{
3786
- // tslint:disable-next-line: directive-selector
3787
- selector: '[tableEditor]',
3733
+ AdvancedAggregatorService.decorators = [
3734
+ { type: Injectable, args: [{
3735
+ providedIn: 'root'
3788
3736
  },] }
3789
3737
  ];
3790
3738
  /** @nocollapse */
3791
- AdvancedTableEditorDirective.ctorParameters = () => [
3792
- { type: AdvancedTableComponent }
3739
+ AdvancedAggregatorService.ctorParameters = () => [
3740
+ { type: AngularDataContext },
3741
+ { type: TemplatePipe }
3793
3742
  ];
3794
- AdvancedTableEditorDirective.propDecorators = {
3795
- dirtyChange: [{ type: Output }]
3796
- };
3743
+ AdvancedAggregatorService.ngInjectableDef = defineInjectable({ factory: function AdvancedAggregatorService_Factory() { return new AdvancedAggregatorService(inject(AngularDataContext), inject(TemplatePipe)); }, token: AdvancedAggregatorService, providedIn: "root" });
3797
3744
 
3798
- class BtnExpDirective {
3799
- constructor(_activatedTable, _context, _config, _translate, _loadingService, _errorService) {
3800
- this._activatedTable = _activatedTable;
3801
- this._context = _context;
3802
- this._config = _config;
3803
- this._translate = _translate;
3745
+ /**
3746
+ *
3747
+ * @class AdvancedAggregatorComponent
3748
+ * @classdesc An advanced list with the capability of aggregate data based on configurable filters
3749
+ *
3750
+ */
3751
+ class AdvancedAggregatorComponent {
3752
+ constructor(_changeDetector, _advancedAggregatorService, _loadingService) {
3753
+ this._changeDetector = _changeDetector;
3754
+ this._advancedAggregatorService = _advancedAggregatorService;
3804
3755
  this._loadingService = _loadingService;
3805
- this._errorService = _errorService;
3756
+ this.groups = [];
3757
+ this.eventHandler = new EventEmitter();
3806
3758
  }
3807
- clickEvent(event) {
3759
+ set config(value) {
3760
+ this._config = value;
3761
+ }
3762
+ set aggregators(value) {
3763
+ this._aggregators = value;
3764
+ }
3765
+ set postProcessors(value) {
3766
+ this._postProcessors = value;
3767
+ }
3768
+ get config() {
3769
+ return this._config;
3770
+ }
3771
+ get aggregators() {
3772
+ return this._aggregators;
3773
+ }
3774
+ get postProcessors() {
3775
+ return this._postProcessors;
3776
+ }
3777
+ ngOnInit() {
3808
3778
  return __awaiter(this, void 0, void 0, function* () {
3809
- event.preventDefault();
3810
- event.stopPropagation();
3811
- if (this._activatedTable.activeTable) {
3812
- const lastQuery = this._activatedTable.activeTable.lastQuery;
3813
- const tableConfig = this._activatedTable.activeTable.config;
3814
- const serviceHeaders = this._context.getService().getHeaders();
3815
- const queryModel = lastQuery.getModel();
3816
- let query = this._context.model(queryModel).asQueryable();
3817
- const queryParams = lastQuery.getParams();
3818
- query = this.constructQuery(query, queryParams, tableConfig, serviceHeaders);
3819
- const maxExportItems = 1000;
3820
- {
3821
- query.skip(0).take(maxExportItems);
3779
+ this.selectedGroups$.subscribe((data) => {
3780
+ this.groups = data;
3781
+ this._changeDetector.markForCheck();
3782
+ });
3783
+ this.filters$.subscribe((filters) => __awaiter(this, void 0, void 0, function* () {
3784
+ try {
3785
+ yield this.fetch(filters);
3822
3786
  }
3823
- // This constructs the URI based on the way @themost does
3824
- const url = this.constructURL(query);
3825
- this.downloadFile(url, query.getService().getHeaders(), query.getModel());
3826
- }
3787
+ catch (err) {
3788
+ console.error(err);
3789
+ }
3790
+ }));
3827
3791
  });
3828
3792
  }
3829
- /**
3830
- * Sets the parameters to the query based on
3831
- * the active filters of the activated table
3832
- * @param {ClientDataQueryable} query
3833
- * @param params: The filters of the last query of the activated table
3834
- * @return ClientDataQueryable
3835
- */
3836
- setParams(query, params) {
3837
- if (params && typeof params === 'object') {
3838
- Object.keys(params).forEach((key) => {
3839
- query.setParam(key, params[key]);
3840
- });
3841
- }
3842
- return query;
3843
- }
3844
- /**
3845
- * Sets the headers to the query based on
3846
- * the default headers of the DataContext
3847
- * and appending the accept header to get
3848
- * an xlsx file from the API
3849
- * @param {ClientDataQueryable} query
3850
- * @param {Headers} headers: The headers to append to the query
3851
- * @return ClientDataQueryable
3852
- */
3853
- setHeaders(query, headers) {
3854
- Object.keys(headers).forEach((key) => {
3855
- if (headers.hasOwnProperty(key)) {
3856
- query.getService().setHeader(key, headers[key]);
3857
- }
3793
+ ngOnDestroy() {
3794
+ return __awaiter(this, void 0, void 0, function* () {
3795
+ this.selectedGroups$.unsubscribe();
3796
+ this.filters$.unsubscribe();
3858
3797
  });
3859
- // sets a header to accept xlsx files
3860
- query.getService().setHeader('Accept', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
3861
- return query;
3862
3798
  }
3863
3799
  /**
3864
- * Constructs the select parameter of the
3865
- * ClientDataQuery by replacing the target name
3866
- * of the field based on the localized translation
3867
- * of that property on the advanced tabled
3868
- * @param {ClientDataQueryable} query
3869
- * @param tableConfig: The configuration of the activated table
3870
- * @return ClientDataQueryable
3800
+ *
3801
+ * Retrieves the data
3802
+ *
3871
3803
  */
3872
- constructQueryParam(query, tableConfig) {
3873
- let selectQuery = query.getParams().$select;
3874
- let selectQueryArray = selectQuery.split(',');
3875
- selectQueryArray = selectQueryArray.map((select) => {
3876
- const words = select.split(' ');
3877
- if (words.length === 3) {
3878
- if (tableConfig && tableConfig.columns) {
3879
- for (const column of tableConfig.columns) {
3880
- if (column.property === words[2] && column.title) {
3881
- words[2] = `"${this._translate.instant(column.title)}"`;
3882
- }
3883
- else if (column.property === words[2]) {
3884
- words[2] = `"${this._translate.instant(words[2])}"`;
3885
- }
3804
+ fetch(filters) {
3805
+ return __awaiter(this, void 0, void 0, function* () {
3806
+ try {
3807
+ this._loadingService.showLoading();
3808
+ let data = yield this._advancedAggregatorService.fetchData(this.config.model, this.params, this.config.query, filters.filters, this.searchExpression, filters.text);
3809
+ if (this.postProcessors && Object.keys(this.postProcessors).length > 0 && this.config.postProcessors && this.config.postProcessors.length > 0) {
3810
+ for (let i = 0; i < this.config.postProcessors.length; i++) {
3811
+ data = yield this.postProcessors[this.config.postProcessors[i].name](data);
3886
3812
  }
3887
3813
  }
3814
+ this.body = this._advancedAggregatorService.formatData(this.config, data);
3815
+ this._changeDetector.markForCheck();
3888
3816
  }
3889
- if (words.length === 1) {
3890
- if (tableConfig && tableConfig.columns) {
3891
- for (const column of tableConfig.columns) {
3892
- if (column.name === words[0] && column.title) {
3893
- words[0] = `${words[0]} as "${this._translate.instant(column.title)}"`;
3894
- break;
3895
- }
3896
- else if (column.name === words[0] && column.property) {
3897
- words[0] = `${words[0]} as "${this._translate.instant(column.property)}"`;
3898
- break;
3899
- }
3900
- else if (words[0] === column.name && words[0] === "id" && column.hidden) {
3901
- break;
3902
- }
3903
- else if (words[0] == column.name || words[0] === column.property) {
3904
- words[0] = `${words[0]} as "${this._translate.instant(words[0])}"`;
3905
- }
3906
- }
3907
- }
3908
- return words[0];
3817
+ catch (err) {
3818
+ console.error(err);
3909
3819
  }
3910
- return words.join(' ');
3820
+ finally {
3821
+ this._loadingService.hideLoading();
3822
+ }
3823
+ });
3824
+ }
3825
+ /**
3826
+ *
3827
+ * Triggers the event handler event
3828
+ *
3829
+ * @param {*} event The event data
3830
+ *
3831
+ */
3832
+ onActionEvent(event) {
3833
+ this.eventHandler.emit(event);
3834
+ }
3835
+ }
3836
+ AdvancedAggregatorComponent.decorators = [
3837
+ { type: Component, args: [{
3838
+ selector: 'app-advanced-aggregator',
3839
+ 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>",
3840
+ changeDetection: ChangeDetectionStrategy.OnPush
3841
+ }] }
3842
+ ];
3843
+ /** @nocollapse */
3844
+ AdvancedAggregatorComponent.ctorParameters = () => [
3845
+ { type: ChangeDetectorRef },
3846
+ { type: AdvancedAggregatorService },
3847
+ { type: LoadingService }
3848
+ ];
3849
+ AdvancedAggregatorComponent.propDecorators = {
3850
+ config: [{ type: Input, args: ['config',] }],
3851
+ aggregators: [{ type: Input, args: ['aggregators',] }],
3852
+ postProcessors: [{ type: Input, args: ['postProcessors',] }],
3853
+ params: [{ type: Input, args: ['params',] }],
3854
+ selectedGroups$: [{ type: Input, args: ['selectedGroups',] }],
3855
+ filters$: [{ type: Input, args: ['filters',] }],
3856
+ searchExpression: [{ type: Input, args: ['searchExpression',] }],
3857
+ eventHandler: [{ type: Output }]
3858
+ };
3859
+
3860
+ var Lengths;
3861
+ (function (Lengths) {
3862
+ Lengths["XL"] = "xl";
3863
+ Lengths["LG"] = "lg";
3864
+ Lengths["MD"] = "md";
3865
+ Lengths["SM"] = "sm";
3866
+ Lengths["XS"] = "xs";
3867
+ })(Lengths || (Lengths = {}));
3868
+ class AdvancedAggregatorListComponent {
3869
+ constructor(_template) {
3870
+ this._template = _template;
3871
+ this.clickEvent = new EventEmitter();
3872
+ }
3873
+ ngOnInit() {
3874
+ this.config.columns.forEach(x => {
3875
+ x.length = Object.values(Lengths).includes(x.length) ? this._template.transform(`sis--list-group-item_${x.length}_column`, x) :
3876
+ !x.length ? 'sis--list-group-item_sm_column' : x.length;
3911
3877
  });
3912
- selectQuery = selectQueryArray.join(',');
3913
- query.setParam('$select', selectQuery);
3914
- return query;
3915
3878
  }
3916
3879
  /**
3917
- * Constructs the fetch URL based the query parameters
3918
- * @param {ClientDataQueryable} query
3919
- * @return String
3880
+ *
3881
+ * Triggers the output event emitter
3882
+ *
3883
+ * @param type The type of the event as it's declared in the configuration
3884
+ * @param item The value of the current item
3885
+ * }
3920
3886
  */
3921
- constructURL(query) {
3922
- const baseURL = query.getService().getBase();
3923
- let url = baseURL.endsWith('/') ? (baseURL + query.getModel() + '?') : (baseURL + '/' + query.getModel() + '?');
3924
- Object.keys(query.getParams()).forEach((key, index) => {
3925
- if (index === 0) {
3926
- url += key + '=' + query.getParams()[key];
3927
- }
3928
- else {
3929
- url += '&' + key + '=' + query.getParams()[key];
3930
- }
3887
+ callCallback(type, data) {
3888
+ this.clickEvent.emit({
3889
+ type,
3890
+ data
3931
3891
  });
3932
- return url;
3933
3892
  }
3934
- /**
3935
- * Constructs the query by setting the parameters of the
3936
- * advanced table's active filters, sets the necessary headers
3937
- * and build the select parameter of the query
3938
- * @param {ClientDataQueryable} query
3939
- * @param params: The filters of the last query of the activated table
3940
- * @param tableConfig: The configuration of the activated table
3941
- * @param {Headers} headers: The headers to append to the query
3942
- * @return ClientDataQueryable
3943
- */
3944
- constructQuery(query, params, tableConfig, headers) {
3945
- let _query = this.setParams(query, params);
3946
- _query = this.setHeaders(_query, headers);
3947
- _query = this.constructQueryParam(_query, tableConfig);
3948
- return _query;
3893
+ }
3894
+ AdvancedAggregatorListComponent.decorators = [
3895
+ { type: Component, args: [{
3896
+ selector: 'app-advanced-aggregator-list',
3897
+ 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 *ngIf=\"action.type === 'edit'\" class=\"fas fa-edit text-indigo\"></i>\n </button>\n <span class=\"px-0\">\n <i class=\"fa fa-fw\">&nbsp;</i>\n </span>\n\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 <button *ngIf=\"config.columns[i].type === 'action' && config.columns[i].variant === 'edit'\"\n (click)=\"callCallback(config.columns[i].identifier, entry)\"\n [title]=\"(config.columns[i].title ? config.columns[i].title : '') | translate \"\n class=\"btn btn-default px-0 mx-0\">\n <i class=\"fas fa-edit text-indigo\"></i>\n </button>\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 <div *ngIf=\"entry && entry.subitems\">\n <div *ngFor=\"let subitem of entry.subitems\" class=\"d-flex justify-content-center align-items-center list-color\">\n <div *ngIf=\"config.actions && config.actions.length > 0\" class=\"sis--list-group-item_sm_column text-left pr-0\">\n <span class=\"px-0\">\n <i class=\"fa fa-fw\">&nbsp;</i>\n </span>\n <button *ngFor=\"let action of config.actions\" (click)=\"callCallback(action.identifier, subitem)\"\n [title]=\"(action.title ? action.title : '') | translate \" class=\"btn btn-default px-0\">\n <i *ngIf=\"action.type === 'edit'\" class=\"fas fa-edit text-indigo\"></i>\n </button>\n </div>\n <div class=\"text-left\"></div>\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') && 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 <span *ngIf=\"config.columns[i].type === 'boolean'\">\n <i class=\"fa font-lg\" [ngClass]=\"{\n 'fa-check text-success': subitem[column.name].toString() === config.columns[i].trueValue,\n 'fa-times text-danger': subitem[column.name].toString() !== config.columns[i].trueValue && config.columns[i].variant !== 'checkOrDash'\n }\"></i>\n <span\n *ngIf=\"subitem[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 </div>\n </li>\n </ul>\n</div>\n"
3898
+ }] }
3899
+ ];
3900
+ /** @nocollapse */
3901
+ AdvancedAggregatorListComponent.ctorParameters = () => [
3902
+ { type: TemplatePipe }
3903
+ ];
3904
+ AdvancedAggregatorListComponent.propDecorators = {
3905
+ config: [{ type: Input }],
3906
+ body: [{ type: Input }],
3907
+ clickEvent: [{ type: Output }]
3908
+ };
3909
+
3910
+ class AddItemsComponent extends RouterModalOkCancel {
3911
+ constructor(router, activatedRoute, _errorService, _modalService, _appEvent, _toastService, _loadingService, _translateService, _context) {
3912
+ super(router, activatedRoute);
3913
+ this._errorService = _errorService;
3914
+ this._modalService = _modalService;
3915
+ this._appEvent = _appEvent;
3916
+ this._toastService = _toastService;
3917
+ this._loadingService = _loadingService;
3918
+ this._translateService = _translateService;
3919
+ this._context = _context;
3920
+ this.loading = false;
3921
+ this.items = [];
3922
+ this.messages = [];
3923
+ // set modal size
3924
+ this.modalClass = 'modal-lg';
3925
+ this.okButtonDisabled = true;
3949
3926
  }
3950
- /**
3951
- * Fetches and saves the xlsx file
3952
- * @param {String} url
3953
- * @param {Headers} serviceHeaders: The headers to append to the query
3954
- * @param modelName: The model name of the advanced table
3955
- */
3956
- downloadFile(url, serviceHeaders, modelName) {
3957
- this._loadingService.showLoading();
3958
- fetch(encodeURI(url), {
3959
- headers: serviceHeaders,
3960
- credentials: 'include'
3961
- }).then(response => {
3962
- if (response && !response.ok) {
3963
- throw new ResponseError(response.statusText, response.status);
3964
- }
3965
- return response.blob();
3966
- }).then(blob => {
3967
- const objectUrl = window.URL.createObjectURL(blob);
3968
- const a = document.createElement('a');
3969
- document.body.appendChild(a);
3970
- a.setAttribute('style', 'display: none');
3971
- a.href = objectUrl;
3972
- const downloadName = `${modelName}.xlsx`;
3973
- a.download = downloadName;
3974
- // this adds support for IE and MS Edge (up to version 44.19041)
3975
- if (window.navigator && window.navigator.msSaveOrOpenBlob) {
3976
- window.navigator.msSaveOrOpenBlob(blob, downloadName);
3977
- }
3978
- else {
3979
- // for all other browsers
3980
- a.click();
3927
+ ngOnInit() {
3928
+ this.selectedSubscription = this.selectComponent.advancedTable.selectedItems.subscribe((items) => {
3929
+ if (this.formName == null) {
3930
+ this.okButtonDisabled = items.length === 0;
3981
3931
  }
3982
- window.URL.revokeObjectURL(objectUrl);
3983
- a.remove();
3984
- this._loadingService.hideLoading();
3985
- }).catch(err => {
3986
- this._loadingService.hideLoading();
3987
- this._errorService.showError(err, {
3988
- continueLink: '.'
3989
- });
3990
3932
  });
3933
+ if (this.formName != null) {
3934
+ this.formChangeSubscription = this.formComponent.form.change.subscribe((event) => {
3935
+ if (Object.prototype.hasOwnProperty.call(event, 'isValid')) {
3936
+ // enable or disable button based on form status
3937
+ this.okButtonDisabled = (!event.isValid);
3938
+ }
3939
+ });
3940
+ }
3941
+ if (this.target) {
3942
+ const target = this.target;
3943
+ }
3944
+ }
3945
+ cancel() {
3946
+ this.clearMessages();
3947
+ if (this.loading) {
3948
+ return;
3949
+ }
3950
+ // close
3951
+ if (this._modalService.modalRef) {
3952
+ return this._modalService.modalRef.hide();
3953
+ }
3954
+ }
3955
+ ngOnDestroy() {
3956
+ if (this.formLoadSubscription) {
3957
+ this.formLoadSubscription.unsubscribe();
3958
+ }
3959
+ if (this.formChangeSubscription) {
3960
+ this.formChangeSubscription.unsubscribe();
3961
+ }
3962
+ if (this.selectedSubscription) {
3963
+ this.selectedSubscription.unsubscribe();
3964
+ }
3965
+ }
3966
+ get formData() {
3967
+ if (this.formComponent === null) {
3968
+ return null;
3969
+ }
3970
+ return this.formComponent.form.formio.data;
3971
+ }
3972
+ ok() {
3973
+ try {
3974
+ this.clearMessages();
3975
+ return new Promise((resolve, reject) => {
3976
+ this.loading = false;
3977
+ this.lastError = null;
3978
+ // set courses
3979
+ this.items = this.selectComponent.advancedTable.selected;
3980
+ // execute add
3981
+ this.executeSubscription = this.execute.subscribe((result) => {
3982
+ this.loading = false;
3983
+ if (this.executeSubscription) {
3984
+ this.executeSubscription.unsubscribe();
3985
+ }
3986
+ // close modal
3987
+ if (this._modalService.modalRef) {
3988
+ this._modalService.modalRef.hide();
3989
+ }
3990
+ return resolve();
3991
+ }, (err) => {
3992
+ this.loading = false;
3993
+ // ensure that loading is hidden
3994
+ this._loadingService.hideLoading();
3995
+ // set last error
3996
+ this.lastError = err;
3997
+ return resolve();
3998
+ });
3999
+ });
4000
+ }
4001
+ catch (err) {
4002
+ this.loading = false;
4003
+ this._loadingService.hideLoading();
4004
+ this.lastError = err;
4005
+ }
3991
4006
  }
4007
+ onChange($event) { }
4008
+ clearMessages() {
4009
+ this.messages = [];
4010
+ }
4011
+ onFilterChange(event) { }
3992
4012
  }
3993
- BtnExpDirective.decorators = [
3994
- { type: Directive, args: [{
3995
- selector: '.universis-btn-export'
3996
- },] }
4013
+ AddItemsComponent.decorators = [
4014
+ { type: Component, args: [{
4015
+ selector: 'app-add-items',
4016
+ 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"
4017
+ }] }
3997
4018
  ];
3998
4019
  /** @nocollapse */
3999
- BtnExpDirective.ctorParameters = () => [
4000
- { type: ActivatedTableService },
4001
- { type: AngularDataContext },
4002
- { type: ConfigurationService },
4003
- { type: TranslateService },
4020
+ AddItemsComponent.ctorParameters = () => [
4021
+ { type: Router },
4022
+ { type: ActivatedRoute },
4023
+ { type: ErrorService },
4024
+ { type: ModalService },
4025
+ { type: AppEventService },
4026
+ { type: ToastService },
4004
4027
  { type: LoadingService },
4005
- { type: ErrorService }
4028
+ { type: TranslateService },
4029
+ { type: AngularDataContext }
4006
4030
  ];
4007
- BtnExpDirective.propDecorators = {
4008
- clickEvent: [{ type: HostListener, args: ['click', ['$event'],] }]
4031
+ AddItemsComponent.propDecorators = {
4032
+ execute: [{ type: Input }],
4033
+ items: [{ type: Input }],
4034
+ target: [{ type: Input }],
4035
+ tableConfiguration: [{ type: Input }],
4036
+ formName: [{ type: Input }],
4037
+ messages: [{ type: Input }],
4038
+ formComponent: [{ type: ViewChild, args: ['formComponent',] }],
4039
+ selectComponent: [{ type: ViewChild, args: ['selectComponent',] }]
4009
4040
  };
4010
4041
 
4011
4042
  /**
@@ -4091,6 +4122,7 @@ const TABLES_EL = {
4091
4122
  "Search": "Αναζήτηση",
4092
4123
  "SearchPlaceholder": "Αναζήτηση...",
4093
4124
  "GroupBy": "Ομαδοποίηση ανά",
4125
+ "NotDefined": "Μη προσδιορισμένο",
4094
4126
  "DataTable": {
4095
4127
  "paginate": {
4096
4128
  "first": "Πρώτη σελίδα",
@@ -4194,6 +4226,7 @@ const TABLES_EN = {
4194
4226
  "Search": "Search",
4195
4227
  "SearchPlaceholder": "Search...",
4196
4228
  "GroupBy": "Group by",
4229
+ "NotDefined": "Not defined",
4197
4230
  "DataTable": {
4198
4231
  "paginate": {
4199
4232
  "first": "First",
@@ -4391,6 +4424,6 @@ TablesModule.ctorParameters = () => [
4391
4424
  * Generated bundle index. Do not edit.
4392
4425
  */
4393
4426
 
4394
- export { AddItemsComponent, AdvancedAggregatorComponent, AdvancedAggregatorListComponent, AdvancedListComponent, AdvancedRowActionComponent, AdvancedSearchFormComponent, AdvancedSelectComponent, AdvancedSelectService, AdvancedTableConfigurationResolver, AdvancedSearchConfigurationResolver, AdvancedColumnFormatter, NestedPropertyFormatter, ButtonFormatter, LinkFormatter, ActionLinkFormatter, ActionButtonFormatter, ActionSplitButtonFormatter, SelectRowFormatter, TemplateFormatter, TrueFalseFormatter, TranslationFormatter, DateTimeFormatter, NgClassFormatter, LanguageFormatter, COLUMN_FORMATTERS, AdvancedTableConfiguration, AdvancedTableComponent, AdvancedTableSettingsComponent, AdvancedTableSearchBaseComponent, AdvancedTableSearchComponent, FORMATTERS, AdvancedFilterValueProvider, AdvancedTableModalBaseTemplate, AdvancedTableModalBaseComponent, SimpleHeaderToolsComponent, TableSimpleHeaderComponent, AdvancedTableNotNullError, AdvancedTableEditorDirective, BtnExpDirective, AdvancedAggregatorPipe, ParseTitlePipe, AdvancedAggregatorService, TablesModule, ɵ0, ActivatedTableService };
4427
+ export { AdvancedListComponent, AdvancedRowActionComponent, AdvancedSearchFormComponent, AdvancedSelectComponent, AdvancedSelectService, AdvancedTableConfigurationResolver, AdvancedSearchConfigurationResolver, AdvancedColumnFormatter, NestedPropertyFormatter, ButtonFormatter, LinkFormatter, ActionLinkFormatter, ActionButtonFormatter, ActionSplitButtonFormatter, SelectRowFormatter, TemplateFormatter, TrueFalseFormatter, TranslationFormatter, DateTimeFormatter, NgClassFormatter, LanguageFormatter, COLUMN_FORMATTERS, AdvancedTableConfiguration, AdvancedTableComponent, AdvancedTableSettingsComponent, AdvancedTableSearchBaseComponent, AdvancedTableSearchComponent, FORMATTERS, AdvancedFilterValueProvider, AdvancedTableModalBaseTemplate, AdvancedTableModalBaseComponent, SimpleHeaderToolsComponent, TableSimpleHeaderComponent, AdvancedTableNotNullError, AdvancedTableEditorDirective, BtnExpDirective, AdvancedAggregatorComponent, AdvancedAggregatorListComponent, AddItemsComponent, AdvancedAggregatorPipe, ParseTitlePipe, AdvancedAggregatorService, TablesModule, ɵ0, ActivatedTableService };
4395
4428
 
4396
4429
  //# sourceMappingURL=universis-ngx-tables.js.map