@universis/ngx-tables 0.6.13 → 0.6.18

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