/**
* Collection of grid related classses and interfaces.
*/
-import {TemplateRef} from '@angular/core';
-import {Observable} from 'rxjs/Observable';
-import {Subscription} from 'rxjs/Subscription';
+import {TemplateRef, EventEmitter} from '@angular/core';
+import {Observable, Subscription} from 'rxjs';
import {IdlService, IdlObject} from '@eg/core/idl.service';
import {OrgService} from '@eg/core/org.service';
import {ServerStoreService} from '@eg/core/server-store.service';
hidden: boolean;
visible: boolean;
sort: number;
+ // IDL class of the object which contains this field.
+ // Not to be confused with the class of a linked object.
idlClass: string;
idlFieldDef: any;
datatype: string;
isDragTarget: boolean;
isSortable: boolean;
isMultiSortable: boolean;
+ disableTooltip: boolean;
comparator: (valueA: any, valueB: any) => number;
// True if the column was automatically generated.
const idlInfo = this.idlInfoFromDotpath(col.path);
if (idlInfo) {
col.idlFieldDef = idlInfo.idlField;
+ col.idlClass = idlInfo.idlClass.name;
if (!col.label) {
col.label = col.idlFieldDef.label || col.idlFieldDef.name;
col.datatype = col.idlFieldDef.datatype;
export interface GridRowFlairEntry {
icon: string; // name of material icon
- title: string; // tooltip string
+ title?: string; // tooltip string
}
export class GridColumnPersistConf {
useLocalSort: boolean;
persistKey: string;
disableMultiSelect: boolean;
+ disableSelect: boolean;
dataSource: GridDataSource;
columnSet: GridColumnSet;
rowSelector: GridRowSelector;
defaultVisibleFields: string[];
defaultHiddenFields: string[];
overflowCells: boolean;
+ showLinkSelectors: boolean;
+ disablePaging: boolean;
+
+ // Allow calling code to know when the select-all-rows-in-page
+ // action has occurred.
+ selectRowsInPageEmitter: EventEmitter<void>;
// Services injected by our grid component
idl: IdlService;
this.store = store;
this.format = format;
this.pager = new Pager();
- this.pager.limit = 10;
this.rowSelector = new GridRowSelector();
this.toolbarButtons = [];
this.toolbarCheckboxes = [];
}
init() {
+ this.selectRowsInPageEmitter = new EventEmitter<void>();
this.columnSet = new GridColumnSet(this.idl, this.idlClass);
this.columnSet.isSortable = this.isSortable === true;
this.columnSet.isMultiSortable = this.isMultiSortable === true;
this.columnSet.defaultHiddenFields = this.defaultHiddenFields;
this.columnSet.defaultVisibleFields = this.defaultVisibleFields;
+ if (!this.pager.limit) {
+ this.pager.limit = this.disablePaging ? MAX_ALL_ROW_COUNT : 10;
+ }
this.generateColumns();
}
sortLocalData() {
const sortDefs = this.dataSource.sort.map(sort => {
+ const column = this.columnSet.getColByName(sort.name);
+
const def = {
name: sort.name,
dir: sort.dir,
- col: this.columnSet.getColByName(sort.name)
+ col: column
};
if (!def.col.comparator) {
- def.col.comparator = (a, b) => {
- if (a < b) { return -1; }
- if (a > b) { return 1; }
- return 0;
- };
+ switch (def.col.datatype) {
+ case 'id':
+ case 'money':
+ case 'int':
+ def.col.comparator = (a, b) => {
+ a = Number(a);
+ b = Number(b);
+ if (a < b) { return -1; }
+ if (a > b) { return 1; }
+ return 0;
+ };
+ break;
+ default:
+ def.col.comparator = (a, b) => {
+ if (a < b) { return -1; }
+ if (a > b) { return 1; }
+ return 0;
+ };
+ }
}
return def;
const diff = sortDef.col.comparator(valueA, valueB);
if (diff === 0) { continue; }
- console.log(valueA, valueB, diff);
-
return sortDef.dir === 'DESC' ? -diff : diff;
}
getRowColumnValue(row: any, col: GridColumn): string {
let val;
- if (col.name in row) {
+
+ if (col.path) {
+ val = this.nestedItemFieldValue(row, col);
+ } else if (col.name in row) {
val = this.getObjectFieldValue(row, col.name);
- } else {
- if (col.path) {
- val = this.nestedItemFieldValue(row, col);
- }
}
return this.format.transform({
value: val,
+ idlClass: col.idlClass,
+ idlField: col.idlFieldDef ? col.idlFieldDef.name : col.name,
datatype: col.datatype,
datePlusTime: Boolean(col.datePlusTime)
});
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
- if (typeof obj !== 'object') {
+ if (obj === null || obj === undefined || typeof obj !== 'object') {
// We have run out of data to step through before
// reaching the end of the path. Conclude fleshing via
// callback if provided then exit.
if (!col.datatype) {
col.datatype = idlField.datatype;
}
+ if (!col.idlFieldDef) {
+ idlField = col.idlFieldDef;
+ }
+ if (!col.idlClass) {
+ col.idlClass = idlClassDef.name;
+ }
if (!col.label) {
col.label = idlField.label || idlField.name;
}
this.lastSelectedIndex = index;
}
+ selectMultipleRows(indexes: any[]) {
+ this.rowSelector.clear();
+ this.rowSelector.select(indexes);
+ this.lastSelectedIndex = indexes[indexes.length - 1];
+ }
+
// selects or deselects an item, without affecting the others.
// returns true if the item is selected; false if de-selected.
toggleSelectOneRow(index: any) {
}
}
+ // shift-up-arrow
+ // Select the previous row in addition to any currently selected row.
+ // However, if the previous row is already selected, assume the user
+ // has reversed direction and now wants to de-select the last selected row.
+ selectMultiRowsPrevious() {
+ if (!this.lastSelectedIndex) { return; }
+ const pos = this.getRowPosition(this.lastSelectedIndex);
+ const selectedIndexes = this.rowSelector.selected();
+
+ const promise = // load the previous page of data if needed
+ (pos === this.pager.offset) ? this.toPrevPage() : Promise.resolve();
+
+ promise.then(
+ ok => {
+ const row = this.dataSource.data[pos - 1];
+ const newIndex = this.getRowIndex(row);
+ if (selectedIndexes.filter(i => i === newIndex).length > 0) {
+ // Prev row is already selected. User is reversing direction.
+ this.rowSelector.deselect(this.lastSelectedIndex);
+ this.lastSelectedIndex = newIndex;
+ } else {
+ this.selectMultipleRows(selectedIndexes.concat(newIndex));
+ }
+ },
+ err => {}
+ );
+ }
+
+ // shift-down-arrow
+ // Select the next row in addition to any currently selected row.
+ // However, if the next row is already selected, assume the user
+ // has reversed direction and wants to de-select the last selected row.
+ selectMultiRowsNext() {
+ if (!this.lastSelectedIndex) { return; }
+ const pos = this.getRowPosition(this.lastSelectedIndex);
+ const selectedIndexes = this.rowSelector.selected();
+
+ const promise = // load the next page of data if needed
+ (pos === (this.pager.offset + this.pager.limit - 1)) ?
+ this.toNextPage() : Promise.resolve();
+
+ promise.then(
+ ok => {
+ const row = this.dataSource.data[pos + 1];
+ const newIndex = this.getRowIndex(row);
+ if (selectedIndexes.filter(i => i === newIndex).length > 0) {
+ // Next row is already selected. User is reversing direction.
+ this.rowSelector.deselect(this.lastSelectedIndex);
+ this.lastSelectedIndex = newIndex;
+ } else {
+ this.selectMultipleRows(selectedIndexes.concat(newIndex));
+ }
+ },
+ err => {}
+ );
+ }
+
+ getFirstRowInPage(): any {
+ return this.dataSource.data[this.pager.offset];
+ }
+
+ getLastRowInPage(): any {
+ return this.dataSource.data[this.pager.offset + this.pager.limit - 1];
+ }
+
selectFirstRow() {
- this.selectRowByPos(this.pager.offset);
+ this.selectOneRow(this.getRowIndex(this.getFirstRowInPage()));
}
selectLastRow() {
- this.selectRowByPos(this.pager.offset + this.pager.limit - 1);
+ this.selectOneRow(this.getRowIndex(this.getLastRowInPage()));
+ }
+
+ selectRowsInPage() {
+ const rows = this.dataSource.getPageOfRows(this.pager);
+ const indexes = rows.map(r => this.getRowIndex(r));
+ this.rowSelector.select(indexes);
+ this.selectRowsInPageEmitter.emit();
}
toPrevPage(): Promise<any> {
col.name = field.name;
col.label = field.label || field.name;
col.idlFieldDef = field;
+ col.idlClass = this.columnSet.idlClass;
col.datatype = field.datatype;
col.isIndex = (field.name === pkeyField);
col.isAuto = true;
+
+ if (this.showLinkSelectors) {
+ const selector = this.idl.getLinkSelector(
+ this.columnSet.idlClass, field.name);
+ if (selector) {
+ col.path = field.name + '.' + selector;
+ }
+ }
+
this.columnSet.add(col);
});
}
// Actions apply to specific rows
export class GridToolbarAction {
label: string;
- action: (rows: any[]) => any;
+ onClick: EventEmitter<any []>;
+ action: (rows: any[]) => any; // DEPRECATED
+ group: string;
+ isGroup: boolean; // used for group placeholder entries
+ disableOnRows: (rows: any[]) => boolean;
}
// Buttons are global actions
export class GridToolbarButton {
label: string;
- action: () => any;
+ onClick: EventEmitter<any []>;
+ action: () => any; // DEPRECATED
disabled: boolean;
}
export class GridToolbarCheckbox {
label: string;
- onChange: (checked: boolean) => void;
+ isChecked: boolean;
+ onChange: EventEmitter<boolean>;
}
export class GridDataSource {
data: any[];
sort: any[];
allRowsRetrieved: boolean;
+ requestingData: boolean;
getRows: (pager: Pager, sort: any[]) => Observable<any>;
constructor() {
return Promise.resolve();
}
+ // If we have to call out for data, set inFetch
+ this.requestingData = true;
+
return new Promise((resolve, reject) => {
let idx = pager.offset;
return this.getRows(pager, this.sort).subscribe(
- row => this.data[idx++] = row,
+ row => {
+ this.data[idx++] = row;
+ this.requestingData = false;
+ },
err => {
console.error(`grid getRows() error ${err}`);
reject(err);
},
() => {
this.checkAllRetrieved(pager, idx);
+ this.requestingData = false;
resolve();
}
);