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';
12 * Main grid entry point.
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
25 export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
27 // Source of row data.
28 @Input() dataSource: GridDataSource;
30 // IDL class for auto-generation of columns
31 @Input() idlClass: string;
33 // Comma-separated list of column names, to set the order of columns
34 // from auto-generated columns only
35 @Input() autoGeneratedColumnOrder: string;
37 // True if any columns are sortable
38 @Input() sortable: boolean;
40 // True if the grid supports sorting of multiple columns at once
41 @Input() multiSortable: boolean;
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;
48 // Storage persist key / per-grid-type unique identifier
49 // The value is prefixed with 'eg.grid.'
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;
55 @Input() disableSelect: boolean;
57 // Prevent selection of multiple rows
58 @Input() disableMultiSelect: boolean;
60 // Show an extra column in the grid where the caller can apply
61 // row-specific flair (material icons).
62 @Input() rowFlairIsEnabled: boolean;
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;
68 // Returns a space-separated list of CSS class names to apply to
70 @Input() rowClassCallback: (row: any) => string;
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;
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
80 @Input() showFields: string;
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;
87 // comma-separated list of fields to ignore when generating columns
89 // This does not imply all other fields should be available, only
90 // that the selected fields will be ignored.
91 @Input() ignoreFields: string;
93 // When true, only display columns that are declared in the markup
94 // and leave all auto-generated fields hidden.
95 @Input() showDeclaredFieldsOnly: boolean;
97 // Allow the caller to jump directly to a specific page of
99 @Input() pageOffset: number;
100 // Pass in a default page size. May be overridden by settings.
101 @Input() pageSize: number;
103 @Input() showLinkSelectors: boolean;
105 @Input() disablePaging: boolean;
109 // filterable: true if the result filtering controls
110 // should be displayed
111 @Input() filterable: boolean;
113 // initialFilterValues: a hash of values you want the
114 // filters to start off with
115 @Input() initialFilterValues: {[field: string]: string};
117 // allowNamedFilterSets: true if the result filtering
118 // controls can be saved or loaded via a name
119 @Input() allowNamedFilterSets: boolean;
121 // migrateLegacyFilterSets: if set to a legacy filter interface type
122 // (i.e. url_verify), attempt to migrate any legacy filter sets for
124 @Input() migrateLegacyFilterSets: string;
126 // sticky grid header
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;
134 @Input() cellTextGenerator: GridCellTextGenerator;
136 // If set, appears along the top left side of the grid.
137 @Input() toolbarLabel: string;
139 // If true, showing/hiding columns will force the data source to
140 // refresh the current page of data.
141 @Input() reloadOnColumnChange = false;
143 context: GridContext;
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>;
150 // Emits an array of grid row indexes on any row selection change.
151 @Output() rowSelectionChange: EventEmitter<string[]>;
153 @ViewChild('toolbar', { static: true }) toolbar: GridToolbarComponent;
156 private idl: IdlService,
157 private org: OrgService,
158 private store: ServerStoreService,
159 private format: FormatService
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[]>();
170 if (!this.dataSource) {
171 throw new Error('<eg-grid/> requires a [dataSource]');
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;
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(',');
203 if (this.hideFields) {
204 this.hideFields = this.hideFields.replace(/\s+/g, '');
205 this.context.defaultHiddenFields = this.hideFields.split(',');
207 if (this.ignoreFields) {
208 this.ignoreFields = this.ignoreFields.replace(/\s+/g, '');
209 this.context.ignoredFields = this.ignoreFields.split(',');
212 if (this.pageOffset) {
213 this.context.pager.offset = this.pageOffset;
217 this.context.pager.limit = this.pageSize;
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') {
230 val = this.nestedItemFieldValue(row, col);
231 } else if (col.name in row) {
232 val = this.getObjectFieldValue(row, col.name);
234 if (Number(val) < 0) {
235 return 'negative-money-amount';
241 this.context.rowSelector.selectionChange.subscribe(
242 keys => this.rowSelectionChange.emit(keys)
245 if (this.showLinkSelectors) {
247 'showLinkSelectors is deprecated and no longer has any effect');
254 this.context.initData();
258 // eslint-disable-next-line rxjs/no-subject-unsubscribe
259 this.context.rowSelector.selectionChange.unsubscribe();
260 this.context.destroy();
264 this.toolbar.printHtml();
268 this.context.reload();
270 reloadWithoutPagerReset() {
271 this.context.reloadWithoutPagerReset();