return data;
};
- const doSearch = (search: string, expectedLength: number, firstObject: object) => {
- component.search = search;
- component.updateFilter(true);
- expect(component.rows.length).toBe(expectedLength);
- expect(component.rows[0]).toEqual(firstObject);
+ const clearLocalStorage = () => {
+ component.localStorage.clear();
};
beforeEach(async(() => {
beforeEach(() => {
component.data = createFakeData(100);
- component.useData();
component.columns = [
{ prop: 'a', name: 'Index' },
{ prop: 'b', name: 'Power ofA' },
expect(component).toBeTruthy();
});
- it('should have rows', () => {
- expect(component.data.length).toBe(100);
- expect(component.rows.length).toBe(component.data.length);
- });
-
- it('should have an int in setLimit parsing a string', () => {
- expect(component.limit).toBe(10);
- expect(component.limit).toEqual(jasmine.any(Number));
-
- const e = { target: { value: '1' } };
- component.setLimit(e);
- expect(component.limit).toBe(1);
- expect(component.limit).toEqual(jasmine.any(Number));
- e.target.value = '-20';
- component.setLimit(e);
- expect(component.limit).toBe(1);
- });
+ describe('after useData', () => {
+ beforeEach(() => {
+ component.useData();
+ });
- it('should search for 13', () => {
- doSearch('13', 9, { a: 7, b: 49, c: [-7, 'score13'], d: false });
- expect(component.rows[1].a).toBe(13);
- expect(component.rows[8].a).toBe(87);
- });
+ it('should force an identifier', () => {
+ component.identifier = 'x';
+ component.forceIdentifier = true;
+ component.ngOnInit();
+ expect(component.identifier).toBe('x');
+ expect(component.sorts[0].prop).toBe('a');
+ expect(component.sorts).toEqual(component.createSortingDefinition('a'));
+ });
- it('should search for true', () => {
- doSearch('true', 50, { a: 0, b: 0, c: [-0, 'score6'], d: true });
- expect(component.rows[0].d).toBe(true);
- expect(component.rows[1].d).toBe(true);
- });
+ it('should have rows', () => {
+ expect(component.data.length).toBe(100);
+ expect(component.rows.length).toBe(component.data.length);
+ });
- it('should search for false', () => {
- doSearch('false', 50, { a: 1, b: 1, c: [-1, 'score7'], d: false });
- expect(component.rows[0].d).toBe(false);
- expect(component.rows[1].d).toBe(false);
- });
+ it('should have an int in setLimit parsing a string', () => {
+ expect(component.limit).toBe(10);
+ expect(component.limit).toEqual(jasmine.any(Number));
+
+ const e = { target: { value: '1' } };
+ component.setLimit(e);
+ expect(component.userConfig.limit).toBe(1);
+ expect(component.userConfig.limit).toEqual(jasmine.any(Number));
+ e.target.value = '-20';
+ component.setLimit(e);
+ expect(component.userConfig.limit).toBe(1);
+ });
- it('should test search manipulation', () => {
- let searchTerms = [];
- spyOn(component, 'subSearch').and.callFake((d, search, c) => {
- expect(search).toEqual(searchTerms);
+ it('should force an identifier', () => {
+ clearLocalStorage();
+ component.identifier = 'x';
+ component.forceIdentifier = true;
+ component.ngOnInit();
+ expect(component.identifier).toBe('x');
+ expect(component.sorts[0].prop).toBe('a');
+ expect(component.sorts).toEqual(component.createSortingDefinition('a'));
});
- const searchTest = (s: string, st: string[]) => {
- component.search = s;
- searchTerms = st;
- component.updateFilter(true);
- };
- searchTest('a b c', ['a', 'b', 'c']);
- searchTest('a+b c', ['a+b', 'c']);
- searchTest('a,,,, b,,, c', ['a', 'b', 'c']);
- searchTest('a,,,+++b,,, c', ['a+++b', 'c']);
- searchTest('"a b c" "d e f", "g, h i"', ['a+b+c', 'd+e++f', 'g+h+i']);
- });
- it('should search for multiple values', () => {
- doSearch('7 5 3', 5, { a: 57, b: 3249, c: [-7, 'score15'], d: false });
- });
+ describe('test search', () => {
+ const doSearch = (search: string, expectedLength: number, firstObject: object) => {
+ component.search = search;
+ component.updateFilter(true);
+ expect(component.rows.length).toBe(expectedLength);
+ expect(component.rows[0]).toEqual(firstObject);
+ };
+
+ it('should search for 13', () => {
+ doSearch('13', 9, { a: 7, b: 49, c: [-7, 'score13'], d: false });
+ expect(component.rows[1].a).toBe(13);
+ expect(component.rows[8].a).toBe(87);
+ });
- it('should search with column filter', () => {
- doSearch('power:1369', 1, { a: 37, b: 1369, c: [-7, 'score11'], d: false });
- doSearch('ndex:7 ofa:5 poker:3', 3, { a: 71, b: 5041, c: [-1, 'score13'], d: false });
- });
+ it('should search for true', () => {
+ doSearch('true', 50, { a: 0, b: 0, c: [-0, 'score6'], d: true });
+ expect(component.rows[0].d).toBe(true);
+ expect(component.rows[1].d).toBe(true);
+ });
- it('should search with through array', () => {
- doSearch('array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
- });
+ it('should search for false', () => {
+ doSearch('false', 50, { a: 1, b: 1, c: [-1, 'score7'], d: false });
+ expect(component.rows[0].d).toBe(false);
+ expect(component.rows[1].d).toBe(false);
+ });
- it('should search with spaces', () => {
- doSearch(`'poker array':score21`, 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
- doSearch('"poker array":score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
- doSearch('poker+array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
- });
+ it('should test search manipulation', () => {
+ let searchTerms = [];
+ spyOn(component, 'subSearch').and.callFake((d, search, c) => {
+ expect(search).toEqual(searchTerms);
+ });
+ const searchTest = (s: string, st: string[]) => {
+ component.search = s;
+ searchTerms = st;
+ component.updateFilter(true);
+ };
+ searchTest('a b c', ['a', 'b', 'c']);
+ searchTest('a+b c', ['a+b', 'c']);
+ searchTest('a,,,, b,,, c', ['a', 'b', 'c']);
+ searchTest('a,,,+++b,,, c', ['a+++b', 'c']);
+ searchTest('"a b c" "d e f", "g, h i"', ['a+b+c', 'd+e++f', 'g+h+i']);
+ });
- it('should not search if column name is incomplete', () => {
- doSearch(`'poker array'`, 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
- doSearch('pok', 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
- doSearch('pok:', 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
- });
+ it('should search for multiple values', () => {
+ doSearch('7 5 3', 5, { a: 57, b: 3249, c: [-7, 'score15'], d: false });
+ });
- it('should restore full table after search', () => {
- expect(component.rows.length).toBe(100);
- component.search = '13';
- component.updateFilter(true);
- expect(component.rows.length).toBe(9);
- component.updateFilter();
- expect(component.rows.length).toBe(100);
- });
+ it('should search with column filter', () => {
+ doSearch('power:1369', 1, { a: 37, b: 1369, c: [-7, 'score11'], d: false });
+ doSearch('ndex:7 ofa:5 poker:3', 3, { a: 71, b: 5041, c: [-1, 'score13'], d: false });
+ });
+
+ it('should search with through array', () => {
+ doSearch('array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
+ });
+
+ it('should search with spaces', () => {
+ doSearch(`'poker array':score21`, 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
+ doSearch('"poker array":score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
+ doSearch('poker+array:score21', 6, { a: 15, b: 225, c: [-5, 'score21'], d: false });
+ });
+
+ it('should not search if column name is incomplete', () => {
+ doSearch(`'poker array'`, 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
+ doSearch('pok', 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
+ doSearch('pok:', 100, { a: 0, b: 0, c: [-0, 'score6'], d: true });
+ });
- it('should force an identifier', () => {
- component.identifier = 'x';
- component.forceIdentifier = true;
- component.ngOnInit();
- expect(component.identifier).toBe('x');
- expect(component.sorts[0].prop).toBe('a');
- expect(component.sorts).toEqual(component.createSortingDefinition('a'));
+ it('should restore full table after search', () => {
+ expect(component.rows.length).toBe(100);
+ component.search = '13';
+ component.updateFilter(true);
+ expect(component.rows.length).toBe(9);
+ component.updateFilter();
+ expect(component.rows.length).toBe(100);
+ });
+ });
});
describe('after ngInit', () => {
});
};
+ const equalStorageConfig = () => {
+ expect(JSON.stringify(component.userConfig)).toBe(
+ component.localStorage.getItem(component.tableName)
+ );
+ };
+
beforeEach(() => {
component.ngOnInit();
- component.table.sorts = component.sorts;
});
it('should have updated the column definitions', () => {
expect(component.tableColumns).toEqual(component.columns);
});
- it('should have a unique identifier which is search for', () => {
+ it('should have a unique identifier which it searches for', () => {
expect(component.identifier).toBe('a');
- expect(component.sorts[0].prop).toBe('a');
- expect(component.sorts).toEqual(component.createSortingDefinition('a'));
+ expect(component.userConfig.sorts[0].prop).toBe('a');
+ expect(component.userConfig.sorts).toEqual(component.createSortingDefinition('a'));
+ equalStorageConfig();
});
it('should remove column "a"', () => {
+ expect(component.userConfig.sorts[0].prop).toBe('a');
toggleColumn('a', false);
- expect(component.table.sorts[0].prop).toBe('b');
+ expect(component.userConfig.sorts[0].prop).toBe('b');
expect(component.tableColumns.length).toBe(3);
+ equalStorageConfig();
});
it('should not be able to remove all columns', () => {
+ expect(component.userConfig.sorts[0].prop).toBe('a');
toggleColumn('a', false);
toggleColumn('b', false);
toggleColumn('c', false);
toggleColumn('d', false);
- expect(component.table.sorts[0].prop).toBe('d');
+ expect(component.userConfig.sorts[0].prop).toBe('d');
expect(component.tableColumns.length).toBe(1);
+ equalStorageConfig();
});
it('should enable column "a" again', () => {
+ expect(component.userConfig.sorts[0].prop).toBe('a');
toggleColumn('a', false);
toggleColumn('a', true);
- expect(component.table.sorts[0].prop).toBe('b');
+ expect(component.userConfig.sorts[0].prop).toBe('b');
expect(component.tableColumns.length).toBe(4);
+ equalStorageConfig();
+ });
+
+ afterEach(() => {
+ clearLocalStorage();
});
});
});
import { CellTemplate } from '../../enum/cell-template.enum';
import { CdTableColumn } from '../../models/cd-table-column';
import { CdTableSelection } from '../../models/cd-table-selection';
+import { CdUserConfig } from '../../models/cd-user-config';
@Component({
selector: 'cd-table',
// If `true` selected item details will be updated on table refresh
@Input() updateSelectionOnRefresh = true;
+ @Input() autoSave = true;
+
/**
* Should be a function to update the input data if undefined nothing will be triggered
*
pagerPrevious: 'i fa fa-angle-left',
pagerNext: 'i fa fa-angle-right'
};
- private subscriber;
+ userConfig: CdUserConfig = {};
+ tableName: string;
+ localStorage = window.localStorage;
+ private saveSubscriber;
+ private reloadSubscriber;
private updating = false;
// Internal variable to check if it is necessary to recalculate the
this.identifier = this.columns[0].prop + '';
}
}
- this.columns.map(c => {
+ this.initUserConfig();
+ this.columns.forEach(c => {
if (c.cellTransformation) {
c.cellTemplate = this.cellTemplates[c.cellTransformation];
}
if (!c.resizeable) {
c.resizeable = false;
}
- return c;
});
- this.tableColumns = this.columns.filter(c => !c.isHidden);
+ this.filterHiddenColumns();
// Load the data table content every N ms or at least once.
// Force showing the loading indicator if there are subscribers to the fetchData
// event. This is necessary because it has been set to False in useData() when
}
if (_.isInteger(this.autoReload) && (this.autoReload > 0)) {
this.ngZone.runOutsideAngular(() => {
- this.subscriber = Observable.timer(0, this.autoReload).subscribe(x => {
+ this.reloadSubscriber = Observable.timer(0, this.autoReload).subscribe(x => {
this.ngZone.run(() => {
return this.reloadData();
});
}
}
+ initUserConfig () {
+ if (this.autoSave) {
+ this.tableName = this._calculateUniqueTableName(this.columns);
+ this._loadUserConfig();
+ this._initUserConfigAutoSave();
+ }
+ if (!this.userConfig.limit) {
+ this.userConfig.limit = this.limit;
+ }
+ if (!this.userConfig.sorts) {
+ this.userConfig.sorts = this.sorts;
+ }
+ if (!this.userConfig.columns) {
+ this.updateUserColumns();
+ } else {
+ this.columns.forEach((c, i) => {
+ c.isHidden = this.userConfig.columns[i].isHidden;
+ });
+ }
+ }
+
+ _calculateUniqueTableName (columns) {
+ const stringToNumber = (s) => {
+ if (!_.isString(s)) {
+ return 0;
+ }
+ let result = 0;
+ for (let i = 0; i < s.length; i++) {
+ result += s.charCodeAt(i) * i;
+ }
+ return result;
+ };
+ return columns.reduce((result, value, index) =>
+ ((stringToNumber(value.prop) + stringToNumber(value.name)) * (index + 1)) + result,
+ 0).toString();
+ }
+
+ _loadUserConfig () {
+ const loaded = this.localStorage.getItem(this.tableName);
+ if (loaded) {
+ this.userConfig = JSON.parse(loaded);
+ }
+ }
+
+ _initUserConfigAutoSave() {
+ const source = Observable.create(this._initUserConfigProxy.bind(this));
+ this.saveSubscriber = source.subscribe(this._saveUserConfig.bind(this));
+ }
+
+ _initUserConfigProxy (observer) {
+ this.userConfig = new Proxy(this.userConfig, {
+ set(config, prop, value) {
+ config[prop] = value;
+ observer.next(config);
+ return true;
+ }
+ });
+ }
+
+ _saveUserConfig (config) {
+ this.localStorage.setItem(this.tableName, JSON.stringify(config));
+ }
+
+ updateUserColumns () {
+ this.userConfig.columns = this.columns.map(c => ({
+ prop: c.prop,
+ name: c.name,
+ isHidden: !!c.isHidden
+ }));
+ }
+
+ filterHiddenColumns () {
+ this.tableColumns = this.columns.filter(c => !c.isHidden);
+ }
+
ngOnDestroy() {
- if (this.subscriber) {
- this.subscriber.unsubscribe();
+ if (this.reloadSubscriber) {
+ this.reloadSubscriber.unsubscribe();
+ }
+ if (this.saveSubscriber) {
+ this.saveSubscriber.unsubscribe();
}
}
setLimit(e) {
const value = parseInt(e.target.value, 10);
if (value > 0) {
- this.limit = value;
+ this.userConfig.limit = value;
}
}
}
updateColumns () {
- this.tableColumns = this.columns.filter(c => !c.isHidden);
- const sortProp = this.table.sorts[0].prop;
+ this.updateUserColumns();
+ this.filterHiddenColumns();
+ const sortProp = this.userConfig.sorts[0].prop;
if (!_.find(this.tableColumns, (c: CdTableColumn) => c.prop === sortProp)) {
- this.table.onColumnSort({sorts: this.createSortingDefinition(this.tableColumns[0].prop)});
+ this.userConfig.sorts = this.createSortingDefinition(this.tableColumns[0].prop);
+ this.table.onColumnSort({sorts: this.userConfig.sorts});
}
this.table.recalculate();
}
];
}
+ changeSorting ({sorts}) {
+ this.userConfig.sorts = sorts;
+ }
+
updateFilter(event?: any) {
if (!event) {
this.search = '';