]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
LP 2061136 follow-up: ng lint --fix
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / share / grid / grid.component.ts
1 import {Component, Input, Output, OnInit, AfterViewInit, EventEmitter,
2     OnDestroy, ViewChild, ViewEncapsulation} from '@angular/core';
3 import {IdlService} from '@eg/core/idl.service';
4 import {OrgService} from '@eg/core/org.service';
5 import {ServerStoreService} from '@eg/core/server-store.service';
6 import {FormatService} from '@eg/core/format.service';
7 import {GridContext, GridColumn, GridDataSource,
8     GridCellTextGenerator, GridRowFlairEntry} from './grid';
9 import {GridToolbarComponent} from './grid-toolbar.component';
10
11 /**
12  * Main grid entry point.
13  */
14
15 @Component({
16     selector: 'eg-grid',
17     templateUrl: './grid.component.html',
18     styleUrls: ['grid.component.css'],
19     // share grid css globally once imported so all grid component CSS
20     // can live in grid.component.css and to avoid multiple copies of
21     // the CSS when multiple grids are displayed.
22     encapsulation: ViewEncapsulation.None
23 })
24
25 export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
26
27     // Source of row data.
28     @Input() dataSource: GridDataSource;
29
30     // IDL class for auto-generation of columns
31     @Input() idlClass: string;
32
33     // Comma-separated list of column names, to set the order of columns
34     // from auto-generated columns only
35     @Input() autoGeneratedColumnOrder: string;
36
37     // True if any columns are sortable
38     @Input() sortable: boolean;
39
40     // True if the grid supports sorting of multiple columns at once
41     @Input() multiSortable: boolean;
42
43     // If true, grid sort requests only operate on data that
44     // already exists in the grid data source -- no row fetching.
45     // The assumption is all data is already available.
46     @Input() useLocalSort: boolean;
47
48     // Storage persist key / per-grid-type unique identifier
49     // The value is prefixed with 'eg.grid.'
50     //
51     // If persistKey is set to "disabled", or does not exist,
52     // the grid will not display a Save button to the user
53     @Input() persistKey: string;
54
55     @Input() disableSelect: boolean;
56
57     // Prevent selection of multiple rows
58     @Input() disableMultiSelect: boolean;
59
60     // Show an extra column in the grid where the caller can apply
61     // row-specific flair (material icons).
62     @Input() rowFlairIsEnabled: boolean;
63
64     // Returns a material icon name to display in the flar column
65     // (if enabled) for the given row.
66     @Input() rowFlairCallback: (row: any) => GridRowFlairEntry;
67
68     // Returns a space-separated list of CSS class names to apply to
69     // a given row
70     @Input() rowClassCallback: (row: any) => string;
71
72     // Returns a space-separated list of CSS class names to apply to
73     // a given cell or all cells in a column.
74     @Input() cellClassCallback: (row: any, col: GridColumn) => string;
75
76     // comma-separated list of fields to show by default.
77     // This field takes precedence over hideFields.
78     // When a value is applied, any field not in this list will
79     // be hidden.
80     @Input() showFields: string;
81
82     // comma-separated list of fields to hide.
83     // This does not imply all other fields should be visible, only that
84     // the selected fields will be hidden.
85     @Input() hideFields: string;
86
87     // comma-separated list of fields to ignore when generating columns
88     // from the IDL.
89     // This does not imply all other fields should be available, only
90     // that the selected fields will be ignored.
91     @Input() ignoreFields: string;
92
93     // When true, only display columns that are declared in the markup
94     // and leave all auto-generated fields hidden.
95     @Input() showDeclaredFieldsOnly: boolean;
96
97     // Allow the caller to jump directly to a specific page of
98     // grid data.
99     @Input() pageOffset: number;
100     // Pass in a default page size.  May be overridden by settings.
101     @Input() pageSize: number;
102
103     @Input() showLinkSelectors: boolean;
104
105     @Input() disablePaging: boolean;
106
107     // result filtering
108     //
109     // filterable: true if the result filtering controls
110     // should be displayed
111     @Input() filterable: boolean;
112
113     // initialFilterValues: a hash of values you want the
114     // filters to start off with
115     @Input() initialFilterValues: {[field: string]: string};
116
117     // allowNamedFilterSets: true if the result filtering
118     // controls can be saved or loaded via a name
119     @Input() allowNamedFilterSets: boolean;
120
121     // migrateLegacyFilterSets: if set to a legacy filter interface type
122     // (i.e. url_verify), attempt to migrate any legacy filter sets for
123     // that interface.
124     @Input() migrateLegacyFilterSets: string;
125
126     // sticky grid header
127     //
128     // stickyHeader: true of the grid header should be
129     // "sticky", i.e., remain visible if if the table is long
130     // and the user has scrolled far enough that the header
131     // would go out of view
132     @Input() stickyHeader: boolean;
133
134     @Input() cellTextGenerator: GridCellTextGenerator;
135
136     // If set, appears along the top left side of the grid.
137     @Input() toolbarLabel: string;
138
139     // If true, showing/hiding columns will force the data source to
140     // refresh the current page of data.
141     @Input() reloadOnColumnChange = false;
142
143     context: GridContext;
144
145     // These events are emitted from our grid-body component.
146     // They are defined here for ease of access to the caller.
147     @Output() onRowActivate: EventEmitter<any>;
148     @Output() onRowClick: EventEmitter<any>;
149
150     // Emits an array of grid row indexes on any row selection change.
151     @Output() rowSelectionChange: EventEmitter<string[]>;
152
153     @ViewChild('toolbar', { static: true }) toolbar: GridToolbarComponent;
154
155     constructor(
156         private idl: IdlService,
157         private org: OrgService,
158         private store: ServerStoreService,
159         private format: FormatService
160     ) {
161         this.context =
162             new GridContext(this.idl, this.org, this.store, this.format);
163         this.onRowActivate = new EventEmitter<any>();
164         this.onRowClick = new EventEmitter<any>();
165         this.rowSelectionChange = new EventEmitter<string[]>();
166     }
167
168     ngOnInit() {
169
170         if (!this.dataSource) {
171             throw new Error('<eg-grid/> requires a [dataSource]');
172         }
173
174         this.context.idlClass = this.idlClass;
175         this.context.dataSource = this.dataSource;
176         this.context.persistKey = this.persistKey;
177         this.context.autoGeneratedColumnOrder = this.autoGeneratedColumnOrder;
178         this.context.isSortable = this.sortable === true;
179         this.context.isFilterable = this.filterable === true;
180         this.context.initialFilterValues = this.initialFilterValues || null;
181         this.context.allowNamedFilterSets = this.allowNamedFilterSets === true;
182         this.context.migrateLegacyFilterSets = this.migrateLegacyFilterSets;
183         this.context.stickyGridHeader = this.stickyHeader === true;
184         this.context.isMultiSortable = this.multiSortable === true;
185         this.context.useLocalSort = this.useLocalSort === true;
186         this.context.disableSelect = this.disableSelect === true;
187         this.context.disableMultiSelect = this.disableMultiSelect === true;
188         this.context.rowFlairIsEnabled = this.rowFlairIsEnabled  === true;
189         this.context.showDeclaredFieldsOnly = this.showDeclaredFieldsOnly;
190         this.context.rowFlairCallback = this.rowFlairCallback;
191         this.context.toolbarLabel = this.toolbarLabel;
192         this.context.disablePaging = this.disablePaging === true;
193         this.context.cellTextGenerator = this.cellTextGenerator;
194         this.context.ignoredFields = [];
195         this.context.reloadOnColumnChange = this.reloadOnColumnChange;
196
197         if (this.showFields) {
198             // Stripping spaces allows users to add newlines to
199             // long lists of field names without consequence.
200             this.showFields = this.showFields.replace(/\s+/g, '');
201             this.context.defaultVisibleFields = this.showFields.split(',');
202         }
203         if (this.hideFields) {
204             this.hideFields = this.hideFields.replace(/\s+/g, '');
205             this.context.defaultHiddenFields = this.hideFields.split(',');
206         }
207         if (this.ignoreFields) {
208             this.ignoreFields = this.ignoreFields.replace(/\s+/g, '');
209             this.context.ignoredFields = this.ignoreFields.split(',');
210         }
211
212         if (this.pageOffset) {
213             this.context.pager.offset = this.pageOffset;
214         }
215
216         if (this.pageSize) {
217             this.context.pager.limit = this.pageSize;
218         }
219
220         // TS doesn't seem to like: let foo = bar || () => '';
221         this.context.rowClassCallback =
222             this.rowClassCallback || function () { return ''; };
223         this.context.cellClassCallback =
224             this.cellClassCallback ||
225             function (row: any, col: GridColumn) {
226                 if (col.datatype === 'money') {
227                     // get raw value
228                     let val;
229                     if (col.path) {
230                         val = this.nestedItemFieldValue(row, col);
231                     } else if (col.name in row) {
232                         val = this.getObjectFieldValue(row, col.name);
233                     }
234                     if (Number(val) < 0) {
235                         return 'negative-money-amount';
236                     }
237                 }
238                 return '';
239             };
240
241         this.context.rowSelector.selectionChange.subscribe(
242             keys => this.rowSelectionChange.emit(keys)
243         );
244
245         if (this.showLinkSelectors) {
246             console.debug(
247                 'showLinkSelectors is deprecated and no longer has any effect');
248         }
249
250         this.context.init();
251     }
252
253     ngAfterViewInit() {
254         this.context.initData();
255     }
256
257     ngOnDestroy() {
258         // eslint-disable-next-line rxjs/no-subject-unsubscribe
259         this.context.rowSelector.selectionChange.unsubscribe();
260         this.context.destroy();
261     }
262
263     print = () => {
264         this.toolbar.printHtml();
265     };
266
267     reload() {
268         this.context.reload();
269     }
270     reloadWithoutPagerReset() {
271         this.context.reloadWithoutPagerReset();
272     }
273
274
275 }
276
277
278