From 83f3e3795d3bb71aa0c2bee995299be82659f290 Mon Sep 17 00:00:00 2001
From: speti43
Date: Thu, 29 Nov 2018 14:04:26 +0100
Subject: [PATCH 01/14] Filter state two-way binding, custom equals
---
.../filters/datagrid-string-filter-impl.ts | 104 ++++++-----
.../datagrid/interfaces/filter.interface.ts | 8 +-
.../data/datagrid/providers/filters.ts | 176 +++++++++---------
.../data/datagrid/providers/state.provider.ts | 3 +-
.../utils/datagrid-filter-registrar.ts | 48 ++---
.../datagrid/server-driven/server-driven.html | 2 +-
.../datagrid/server-driven/server-driven.ts | 67 ++++---
.../src/app/datagrid/utils/color-filter.ts | 59 +++---
8 files changed, 251 insertions(+), 216 deletions(-)
diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
index 6bcf32938f..f2ead93188 100644
--- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
+++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
@@ -7,59 +7,69 @@ import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { ClrDatagridFilterInterface } from '../../interfaces/filter.interface';
import { ClrDatagridStringFilterInterface } from '../../interfaces/string-filter.interface';
+import { DatagridPropertyStringFilter } from './datagrid-property-string-filter';
export class DatagridStringFilterImpl implements ClrDatagridFilterInterface {
- constructor(public filterFn: ClrDatagridStringFilterInterface) {}
+ constructor(public filterFn: ClrDatagridStringFilterInterface) { }
- /**
- * The Observable required as part of the Filter interface
- */
- private _changes = new Subject();
- // We do not want to expose the Subject itself, but the Observable which is read-only
- public get changes(): Observable {
- return this._changes.asObservable();
- }
+ /**
+ * The Observable required as part of the Filter interface
+ */
+ private _changes = new Subject();
+ // We do not want to expose the Subject itself, but the Observable which is read-only
+ public get changes(): Observable {
+ return this._changes.asObservable();
+ }
- /**
- * Raw input value
- */
- private _rawValue: string = '';
- public get value(): string {
- return this._rawValue;
- }
- /**
- * Input value converted to lowercase
- */
- private _lowerCaseValue: string = '';
- public get lowerCaseValue() {
- return this._lowerCaseValue;
- }
- /**
- * Common setter for the input value
- */
- public set value(value: string) {
- if (!value) {
- value = '';
+ /**
+ * Raw input value
+ */
+ private _rawValue: string = '';
+ public get value(): string {
+ return this._rawValue;
+ }
+ /**
+ * Input value converted to lowercase
+ */
+ private _lowerCaseValue: string = '';
+ public get lowerCaseValue() {
+ return this._lowerCaseValue;
+ }
+ /**
+ * Common setter for the input value
+ */
+ public set value(value: string) {
+ if (!value) {
+ value = '';
+ }
+ if (value !== this._rawValue) {
+ this._rawValue = value;
+ this._lowerCaseValue = value.toLowerCase().trim();
+ this._changes.next(value);
+ }
}
- if (value !== this._rawValue) {
- this._rawValue = value;
- this._lowerCaseValue = value.toLowerCase().trim();
- this._changes.next(value);
+
+ /**
+ * Indicates if the filter is currently active, meaning the input is not empty
+ */
+ public isActive(): boolean {
+ return !!this.value;
}
- }
- /**
- * Indicates if the filter is currently active, meaning the input is not empty
- */
- public isActive(): boolean {
- return !!this.value;
- }
+ /**
+ * Tests if an item matches a search text
+ */
+ public accepts(item: T): boolean {
+ // We always test with the lowercase value of the input, to stay case insensitive
+ return this.filterFn.accepts(item, this.lowerCaseValue);
+ }
- /**
- * Tests if an item matches a search text
- */
- public accepts(item: T): boolean {
- // We always test with the lowercase value of the input, to stay case insensitive
- return this.filterFn.accepts(item, this.lowerCaseValue);
- }
+ /**
+ * Compare objects by property name
+ */
+ public equals(objectToCompare: DatagridStringFilterImpl): boolean {
+ // We always test with the lowercase value of the input, to stay case insensitive
+ const datagridPropertyStringFilter = this.filterFn;
+ return datagridPropertyStringFilter.prop === (objectToCompare.filterFn).prop;
+ }
}
diff --git a/src/clr-angular/data/datagrid/interfaces/filter.interface.ts b/src/clr-angular/data/datagrid/interfaces/filter.interface.ts
index a014ec4045..c37eb3a443 100644
--- a/src/clr-angular/data/datagrid/interfaces/filter.interface.ts
+++ b/src/clr-angular/data/datagrid/interfaces/filter.interface.ts
@@ -6,9 +6,11 @@
import { Observable } from 'rxjs';
export interface ClrDatagridFilterInterface {
- isActive(): boolean;
+ isActive(): boolean;
- accepts(item: T): boolean;
+ accepts(item: T): boolean;
- changes: Observable;
+ changes: Observable;
+
+ equals(objectToCompare: ClrDatagridFilterInterface): boolean;
}
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index 89435f99e6..3929f1db42 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -13,105 +13,109 @@ import { StateDebouncer } from './state-debouncer.provider';
@Injectable()
export class FiltersProvider {
- constructor(private _page: Page, private stateDebouncer: StateDebouncer) {}
- /**
- * This subject is the list of filters that changed last, not the whole list.
- * We emit a list rather than just one filter to allow batch changes to several at once.
- */
- private _change = new Subject[]>();
- // We do not want to expose the Subject itself, but the Observable which is read-only
- public get change(): Observable[]> {
- return this._change.asObservable();
- }
+ constructor(private _page: Page, private stateDebouncer: StateDebouncer) { }
+ /**
+ * This subject is the list of filters that changed last, not the whole list.
+ * We emit a list rather than just one filter to allow batch changes to several at once.
+ */
+ private _change = new Subject[]>();
+ // We do not want to expose the Subject itself, but the Observable which is read-only
+ public get change(): Observable[]> {
+ return this._change.asObservable();
+ }
- /**
- * List of all filters, whether they're active or not
- */
- private _all: RegisteredFilter>[] = [];
+ /**
+ * List of all filters, whether they're active or not
+ */
+ private _all: RegisteredFilter>[] = [];
- /**
- * Tests if at least one filter is currently active
- */
- public hasActiveFilters(): boolean {
- // We do not use getActiveFilters() because this function will be called much more often
- // and stopping the loop early might be relevant.
- for (const { filter } of this._all) {
- if (filter && filter.isActive()) {
- return true;
- }
+ /**
+ * Tests if at least one filter is currently active
+ */
+ public hasActiveFilters(): boolean {
+ // We do not use getActiveFilters() because this function will be called much more often
+ // and stopping the loop early might be relevant.
+ for (const { filter } of this._all) {
+ if (filter && filter.isActive()) {
+ return true;
+ }
+ }
+ return false;
}
- return false;
- }
- /**
- * Returns a list of all currently active filters
- */
- public getActiveFilters(): ClrDatagridFilterInterface[] {
- const ret: ClrDatagridFilterInterface[] = [];
- for (const { filter } of this._all) {
- if (filter && filter.isActive()) {
- ret.push(filter);
- }
+ /**
+ * Returns a list of all currently active filters
+ */
+ public getActiveFilters(): ClrDatagridFilterInterface[] {
+ const ret: ClrDatagridFilterInterface[] = [];
+ for (const { filter } of this._all) {
+ if (filter && filter.isActive()) {
+ ret.push(filter);
+ }
+ }
+ return ret;
}
- return ret;
- }
- /**
- * Registers a filter, and returns a deregistration function
- */
- public add>(filter: F): RegisteredFilter {
- const subscription = filter.changes.subscribe(() => this.resetPageAndEmitFilterChange([filter]));
- let hasUnregistered = false;
- const registered = new RegisteredFilter(filter, () => {
- if (hasUnregistered) {
- return;
- }
- subscription.unsubscribe();
- const index = this._all.findIndex(value => value.filter === filter);
- if (index !== -1) {
- this._all.splice(index, 1);
- }
- if (filter.isActive()) {
- this.resetPageAndEmitFilterChange([]);
- }
- hasUnregistered = true;
- });
- this._all.push(registered);
- if (filter.isActive()) {
- this.resetPageAndEmitFilterChange([filter]);
+ /**
+ * Registers a filter, and returns a deregistration function
+ */
+ public add>(filter: F): RegisteredFilter {
+ const subscription = filter.changes.subscribe(() => this.resetPageAndEmitFilterChange([filter]));
+ let hasUnregistered = false;
+ const registered = new RegisteredFilter(filter, () => {
+ if (hasUnregistered) {
+ return;
+ }
+ subscription.unsubscribe();
+ const index = this._all.findIndex(value => value.filter.equals(filter));
+ if (index !== -1) {
+ this._all.splice(index, 1);
+ }
+ if (filter.isActive()) {
+ this.resetPageAndEmitFilterChange([]);
+ }
+ hasUnregistered = true;
+ });
+ this._all.push(registered);
+ if (filter.isActive()) {
+ this.resetPageAndEmitFilterChange([filter]);
+ }
+ return registered;
}
- return registered;
- }
- /**
- * Accepts an item if it is accepted by all currently active filters
- */
- public accepts(item: T): boolean {
- for (const { filter } of this._all) {
- if (filter && filter.isActive() && !filter.accepts(item)) {
- return false;
- }
+ /**
+ * Accepts an item if it is accepted by all currently active filters
+ */
+ public accepts(item: T): boolean {
+ for (const { filter } of this._all) {
+ if (filter && filter.isActive() && !filter.accepts(item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public remove>(filter: F) {
+ const registeredFilter = this._all.find(value => value.filter === filter);
+ if (registeredFilter) {
+ registeredFilter.unregister();
+ }
}
- return true;
- }
- public remove>(filter: F) {
- const registeredFilter = this._all.find(value => value.filter === filter);
- if (registeredFilter) {
- registeredFilter.unregister();
+ public getRegisteredFilter>(filter: F): RegisteredFilter {
+ return >this._all.find(f => f.filter.equals(filter));
}
- }
- private resetPageAndEmitFilterChange(filters: ClrDatagridFilterInterface[]) {
- this.stateDebouncer.changeStart();
- // filtering may change the page number such that current page number doesn't exist in the filtered dataset.
- // So here we always set the current page to 1 so that it'll fetch first page's data with the given filter.
- this._page.current = 1;
- this._change.next(filters);
- this.stateDebouncer.changeDone();
- }
+ private resetPageAndEmitFilterChange(filters: ClrDatagridFilterInterface[]) {
+ this.stateDebouncer.changeStart();
+ // filtering may change the page number such that current page number doesn't exist in the filtered dataset.
+ // So here we always set the current page to 1 so that it'll fetch first page's data with the given filter.
+ this._page.current = 1;
+ this._change.next(filters);
+ this.stateDebouncer.changeDone();
+ }
}
export class RegisteredFilter> {
- constructor(public filter: F, public unregister: () => void) {}
+ constructor(public filter: F, public unregister: () => void) { }
}
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index e921155ee0..437e3cd919 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -112,7 +112,7 @@ export class StateProvider {
} else {
filterObject = filter as ClrDatagridFilterInterface;
}
- const existing = activeFilters.findIndex(value => value === filterObject);
+ const existing = activeFilters.findIndex(value => value.equals(filterObject));
if (existing !== -1) {
activeFilters.splice(existing, 1);
} else {
@@ -121,6 +121,7 @@ export class StateProvider {
}
activeFilters.forEach(filter => this.filters.remove(filter));
}
+
this.debouncer.changeDone();
}
}
diff --git a/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts b/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
index 09c3d502bb..54bdeb25d3 100644
--- a/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
+++ b/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
@@ -8,32 +8,38 @@ import { ClrDatagridFilterInterface } from '../interfaces/filter.interface';
import { FiltersProvider, RegisteredFilter } from '../providers/filters';
export abstract class DatagridFilterRegistrar> implements OnDestroy {
- constructor(private filters: FiltersProvider) {}
+ constructor(private filters: FiltersProvider) { }
- public registered: RegisteredFilter;
+ public registered: RegisteredFilter;
- public get filter(): F {
- return this.registered && this.registered.filter;
- }
+ public get filter(): F {
+ return this.registered && this.registered.filter;
+ }
+
+ public setFilter(filter: F | RegisteredFilter) {
+ // If we previously had another filter, we unregister it
+ this.deleteFilter();
+ if (filter instanceof RegisteredFilter) {
+ this.registered = filter;
+ } else if (filter) {
+ const existingFilter = this.filters.getRegisteredFilter(filter);
+ if (existingFilter) {
+ this.registered = existingFilter;
+ } else {
+ this.registered = this.filters.add(filter);
+ }
- public setFilter(filter: F | RegisteredFilter) {
- // If we previously had another filter, we unregister it
- this.deleteFilter();
- if (filter instanceof RegisteredFilter) {
- this.registered = filter;
- } else if (filter) {
- this.registered = this.filters.add(filter);
+ }
}
- }
- public deleteFilter() {
- if (this.registered) {
- this.registered.unregister();
- delete this.registered;
+ public deleteFilter() {
+ if (this.registered) {
+ this.registered.unregister();
+ delete this.registered;
+ }
}
- }
- public ngOnDestroy(): void {
- this.deleteFilter();
- }
+ public ngOnDestroy(): void {
+ this.deleteFilter();
+ }
}
diff --git a/src/dev/src/app/datagrid/server-driven/server-driven.html b/src/dev/src/app/datagrid/server-driven/server-driven.html
index e1cc592029..099d1e37b0 100644
--- a/src/dev/src/app/datagrid/server-driven/server-driven.html
+++ b/src/dev/src/app/datagrid/server-driven/server-driven.html
@@ -42,7 +42,7 @@ Server-driven datagrid
map.
-
+
User ID
Name
Creation date
diff --git a/src/dev/src/app/datagrid/server-driven/server-driven.ts b/src/dev/src/app/datagrid/server-driven/server-driven.ts
index f17169d481..f8678f9387 100644
--- a/src/dev/src/app/datagrid/server-driven/server-driven.ts
+++ b/src/dev/src/app/datagrid/server-driven/server-driven.ts
@@ -9,39 +9,44 @@ import { FetchResult, Inventory } from '../inventory/inventory';
import { User } from '../inventory/user';
@Component({
- selector: 'clr-datagrid-server-driven-demo',
- providers: [Inventory],
- templateUrl: 'server-driven.html',
- styleUrls: ['../datagrid.demo.scss'],
+ selector: 'clr-datagrid-server-driven-demo',
+ providers: [Inventory],
+ templateUrl: 'server-driven.html',
+ styleUrls: ['../datagrid.demo.scss'],
})
export class DatagridServerDrivenDemo {
- users: User[];
- total: number;
- loading: boolean = true;
-
- constructor(private inventory: Inventory) {
- inventory.size = 103;
- this.inventory.latency = 500;
- inventory.reset();
- }
+ users: User[];
+ total: number;
+ loading: boolean = true;
+ state: ClrDatagridStateInterface =
+ {
+ page: { from: 0, to: 8, size: 10 },
+ sort: { by: "pokemon", reverse: false },
+ filters: [{ property: "name", value: "sampleName" },{ property: "creation", value: "sampleDate" }]
+ };
+ constructor(private inventory: Inventory) {
+ inventory.size = 103;
+ this.inventory.latency = 500;
+ inventory.reset();
+ }
- refresh(state: ClrDatagridStateInterface) {
- this.loading = true;
- const filters: { [prop: string]: any[] } = {};
- if (state.filters) {
- for (const filter of state.filters) {
- const { property, value } = <{ property: string; value: string }>filter;
- filters[property] = [value];
- }
+ refresh() {
+ this.loading = true;
+ const filters: { [prop: string]: any[] } = {};
+ if (this.state.filters) {
+ for (const filter of this.state.filters) {
+ const { property, value } = <{ property: string; value: string }>filter;
+ filters[property] = [value];
+ }
+ }
+ this.inventory
+ .filter(filters)
+ .sort(<{ by: string; reverse: boolean }>this.state.sort)
+ .fetch(this.state.page.from, this.state.page.size)
+ .then((result: FetchResult) => {
+ this.users = result.users;
+ this.total = result.length;
+ this.loading = false;
+ });
}
- this.inventory
- .filter(filters)
- .sort(<{ by: string; reverse: boolean }>state.sort)
- .fetch(state.page.from, state.page.size)
- .then((result: FetchResult) => {
- this.users = result.users;
- this.total = result.length;
- this.loading = false;
- });
- }
}
diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts
index b0ac9667ac..da719aa4ff 100644
--- a/src/dev/src/app/datagrid/utils/color-filter.ts
+++ b/src/dev/src/app/datagrid/utils/color-filter.ts
@@ -9,42 +9,49 @@ import { User } from '../inventory/user';
import { COLORS } from '../inventory/values';
@Component({
- selector: 'clr-datagrid-color-filter-demo',
- template: `
+ selector: 'clr-datagrid-color-filter-demo',
+ template: `
`,
- styleUrls: ['../datagrid.demo.scss'],
+ styleUrls: ['../datagrid.demo.scss'],
})
export class ColorFilter implements ClrDatagridFilterInterface {
- allColors = COLORS;
- selectedColors: { [color: string]: boolean } = {};
- nbColors = 0;
+ allColors = COLORS;
+ selectedColors: { [color: string]: boolean } = {};
+ nbColors = 0;
- changes: EventEmitter = new EventEmitter(false);
+ changes: EventEmitter = new EventEmitter(false);
- listSelected(): string[] {
- const list: string[] = [];
- for (const color in this.selectedColors) {
- if (this.selectedColors[color]) {
- list.push(color);
- }
+ listSelected(): string[] {
+ const list: string[] = [];
+ for (const color in this.selectedColors) {
+ if (this.selectedColors[color]) {
+ list.push(color);
+ }
+ }
+ return list;
}
- return list;
- }
- toggleColor(color: string) {
- this.selectedColors[color] = !this.selectedColors[color];
- this.selectedColors[color] ? this.nbColors++ : this.nbColors--;
- this.changes.emit(true);
- }
+ toggleColor(color: string) {
+ this.selectedColors[color] = !this.selectedColors[color];
+ this.selectedColors[color] ? this.nbColors++ : this.nbColors--;
+ this.changes.emit(true);
+ }
- accepts(user: User) {
- return this.nbColors === 0 || this.selectedColors[user.color];
- }
+ accepts(user: User) {
+ return this.nbColors === 0 || this.selectedColors[user.color];
+ }
- isActive(): boolean {
- return this.nbColors > 0;
- }
+ isActive(): boolean {
+ return this.nbColors > 0;
+ }
+
+ /**
+ * Compare objects by reference
+ */
+ public equals(objectToCompare: ColorFilter): boolean {
+ return this === objectToCompare;
+ }
}
From 3fc3f82b78a7d2367019d8298631576c4bd6a28f Mon Sep 17 00:00:00 2001
From: speti43
Date: Fri, 30 Nov 2018 13:37:47 +0100
Subject: [PATCH 02/14] Serializable filter refactor
---
.../filters/datagrid-string-filter-impl.ts | 36 ++++++++++++++-----
.../data/datagrid/datagrid-filter.ts | 5 +--
.../datagrid/interfaces/filter.interface.ts | 2 --
.../interfaces/filter.state.interface.ts | 11 ++++++
.../serializable.filter.interface.ts | 13 +++++++
.../data/datagrid/providers/filters.ts | 19 +++++-----
.../data/datagrid/providers/state.provider.ts | 7 ++--
.../utils/datagrid-filter-registrar.ts | 3 +-
.../src/app/datagrid/utils/color-filter.ts | 7 ----
9 files changed, 71 insertions(+), 32 deletions(-)
create mode 100644 src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts
create mode 100644 src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts
diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
index f2ead93188..869429c6f8 100644
--- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
+++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
@@ -5,17 +5,28 @@
*/
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
-import { ClrDatagridFilterInterface } from '../../interfaces/filter.interface';
import { ClrDatagridStringFilterInterface } from '../../interfaces/string-filter.interface';
import { DatagridPropertyStringFilter } from './datagrid-property-string-filter';
+import { SerializableFilter } from '../../interfaces/serializable.filter.interface';
+import { FilterStateInterface } from '../../interfaces/filter.state.interface';
-export class DatagridStringFilterImpl implements ClrDatagridFilterInterface {
- constructor(public filterFn: ClrDatagridStringFilterInterface) { }
+export class DatagridStringFilterImpl implements SerializableFilter {
+ constructor(public filterFn: ClrDatagridStringFilterInterface) {
+ const datagridPropertyStringFilter = filterFn;
+ this._state =
+ {
+ type: 'BuiltinStringFilter',
+ property: datagridPropertyStringFilter.prop,
+ value: ''
+ }
+ }
/**
* The Observable required as part of the Filter interface
*/
private _changes = new Subject();
+ private _state: FilterStateInterface;
+
// We do not want to expose the Subject itself, but the Observable which is read-only
public get changes(): Observable {
return this._changes.asObservable();
@@ -47,6 +58,17 @@ export class DatagridStringFilterImpl implements ClrDatagridFilterInter
this._lowerCaseValue = value.toLowerCase().trim();
this._changes.next(value);
}
+ this._state.value = this.value;
+ }
+
+ public get filterState(): FilterStateInterface {
+ return this._state;
+ }
+
+ public set filterState(state: FilterStateInterface) {
+ this._state = state;
+ this._rawValue = state.value;
+ this._changes.next();
}
/**
@@ -65,11 +87,9 @@ export class DatagridStringFilterImpl implements ClrDatagridFilterInter
}
/**
- * Compare objects by property name
+ * Compare objects by properties
*/
- public equals(objectToCompare: DatagridStringFilterImpl): boolean {
- // We always test with the lowercase value of the input, to stay case insensitive
- const datagridPropertyStringFilter = this.filterFn;
- return datagridPropertyStringFilter.prop === (objectToCompare.filterFn).prop;
+ public compatibleToState(state: FilterStateInterface): boolean {
+ return state.type === this._state.type && state.property === this._state.property;
}
}
diff --git a/src/clr-angular/data/datagrid/datagrid-filter.ts b/src/clr-angular/data/datagrid/datagrid-filter.ts
index da7164b2dd..f70f72ce71 100644
--- a/src/clr-angular/data/datagrid/datagrid-filter.ts
+++ b/src/clr-angular/data/datagrid/datagrid-filter.ts
@@ -13,6 +13,7 @@ import { CustomFilter } from './providers/custom-filter';
import { FiltersProvider, RegisteredFilter } from './providers/filters';
import { DatagridFilterRegistrar } from './utils/datagrid-filter-registrar';
import { ClrCommonStrings } from '../../utils/i18n/common-strings.interface';
+import { SerializableFilter } from './interfaces/serializable.filter.interface';
/**
* Custom filter that can be added in any column to override the default object property string filter.
@@ -43,7 +44,7 @@ import { ClrCommonStrings } from '../../utils/i18n/common-strings.interface';
`,
})
-export class ClrDatagridFilter extends DatagridFilterRegistrar>
+export class ClrDatagridFilter extends DatagridFilterRegistrar>
implements CustomFilter {
constructor(_filters: FiltersProvider, public commonStrings: ClrCommonStrings) {
super(_filters);
@@ -72,7 +73,7 @@ export class ClrDatagridFilter extends DatagridFilterRegistrar(false);
@Input('clrDgFilter')
- public set customFilter(filter: ClrDatagridFilterInterface | RegisteredFilter>) {
+ public set customFilter(filter: SerializableFilter | RegisteredFilter>) {
this.setFilter(filter);
}
diff --git a/src/clr-angular/data/datagrid/interfaces/filter.interface.ts b/src/clr-angular/data/datagrid/interfaces/filter.interface.ts
index c37eb3a443..15fbfd3ef6 100644
--- a/src/clr-angular/data/datagrid/interfaces/filter.interface.ts
+++ b/src/clr-angular/data/datagrid/interfaces/filter.interface.ts
@@ -11,6 +11,4 @@ export interface ClrDatagridFilterInterface {
accepts(item: T): boolean;
changes: Observable;
-
- equals(objectToCompare: ClrDatagridFilterInterface): boolean;
}
diff --git a/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts
new file mode 100644
index 0000000000..a6961707f3
--- /dev/null
+++ b/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2016-2018 VMware, Inc. All Rights Reserved.
+ * This software is released under MIT license.
+ * The full license information can be found in LICENSE in the root directory of this project.
+ */
+
+export interface FilterStateInterface {
+ type: string;
+ property: string;
+ value: string;
+}
diff --git a/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts b/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts
new file mode 100644
index 0000000000..82b845bef7
--- /dev/null
+++ b/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2016-2018 VMware, Inc. All Rights Reserved.
+ * This software is released under MIT license.
+ * The full license information can be found in LICENSE in the root directory of this project.
+ */
+
+import { ClrDatagridFilterInterface } from './filter.interface';
+import { FilterStateInterface } from './filter.state.interface';
+
+export interface SerializableFilter extends ClrDatagridFilterInterface {
+ compatibleToState(state: FilterStateInterface): boolean;
+ filterState: FilterStateInterface;
+}
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index 3929f1db42..bc1dff0ea2 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -10,6 +10,7 @@ import { Subject } from 'rxjs';
import { ClrDatagridFilterInterface } from '../interfaces/filter.interface';
import { Page } from './page';
import { StateDebouncer } from './state-debouncer.provider';
+import { SerializableFilter } from '../interfaces/serializable.filter.interface';
@Injectable()
export class FiltersProvider {
@@ -27,7 +28,7 @@ export class FiltersProvider {
/**
* List of all filters, whether they're active or not
*/
- private _all: RegisteredFilter>[] = [];
+ private _all: RegisteredFilter>[] = [];
/**
* Tests if at least one filter is currently active
@@ -46,8 +47,8 @@ export class FiltersProvider {
/**
* Returns a list of all currently active filters
*/
- public getActiveFilters(): ClrDatagridFilterInterface[] {
- const ret: ClrDatagridFilterInterface[] = [];
+ public getActiveFilters(): SerializableFilter[] {
+ const ret: SerializableFilter[] = [];
for (const { filter } of this._all) {
if (filter && filter.isActive()) {
ret.push(filter);
@@ -59,7 +60,7 @@ export class FiltersProvider {
/**
* Registers a filter, and returns a deregistration function
*/
- public add>(filter: F): RegisteredFilter {
+ public add>(filter: F): RegisteredFilter {
const subscription = filter.changes.subscribe(() => this.resetPageAndEmitFilterChange([filter]));
let hasUnregistered = false;
const registered = new RegisteredFilter(filter, () => {
@@ -67,7 +68,7 @@ export class FiltersProvider {
return;
}
subscription.unsubscribe();
- const index = this._all.findIndex(value => value.filter.equals(filter));
+ const index = this._all.findIndex(value => value.filter.compatibleToState(filter.filterState));
if (index !== -1) {
this._all.splice(index, 1);
}
@@ -95,15 +96,15 @@ export class FiltersProvider {
return true;
}
- public remove>(filter: F) {
- const registeredFilter = this._all.find(value => value.filter === filter);
+ public remove>(filter: F) {
+ const registeredFilter = this._all.find(value => value.filter.compatibleToState(filter.filterState));
if (registeredFilter) {
registeredFilter.unregister();
}
}
- public getRegisteredFilter>(filter: F): RegisteredFilter {
- return >this._all.find(f => f.filter.equals(filter));
+ public getRegisteredFilter>(filter: F): RegisteredFilter {
+ return >this._all.find(f => f.filter.compatibleToState(filter.filterState));
}
private resetPageAndEmitFilterChange(filters: ClrDatagridFilterInterface[]) {
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index 437e3cd919..ea8d2b12ea 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -17,6 +17,7 @@ import { Page } from './page';
import { Sort } from './sort';
import { StateDebouncer } from './state-debouncer.provider';
import { ClrDatagridFilterInterface } from '../interfaces/filter.interface';
+import { SerializableFilter } from '../interfaces/serializable.filter.interface';
/**
* This provider aggregates state changes from the various providers of the Datagrid
@@ -102,7 +103,7 @@ export class StateProvider {
if (state.filters) {
const activeFilters = this.filters.getActiveFilters();
for (const filter of state.filters) {
- let filterObject: ClrDatagridFilterInterface;
+ let filterObject: SerializableFilter;
if (filter.hasOwnProperty('property') && filter.hasOwnProperty('value')) {
const defaultFilterRepresentation = filter as { property: string; value: string };
const propertyStringFilter = new DatagridPropertyStringFilter(defaultFilterRepresentation.property);
@@ -110,9 +111,9 @@ export class StateProvider {
stringFilter.value = defaultFilterRepresentation.value;
filterObject = stringFilter;
} else {
- filterObject = filter as ClrDatagridFilterInterface;
+ filterObject = filter as SerializableFilter;
}
- const existing = activeFilters.findIndex(value => value.equals(filterObject));
+ const existing = activeFilters.findIndex(value => value.compatibleToState(filterObject.filterState));
if (existing !== -1) {
activeFilters.splice(existing, 1);
} else {
diff --git a/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts b/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
index 54bdeb25d3..19ecd87732 100644
--- a/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
+++ b/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
@@ -6,8 +6,9 @@
import { OnDestroy } from '@angular/core';
import { ClrDatagridFilterInterface } from '../interfaces/filter.interface';
import { FiltersProvider, RegisteredFilter } from '../providers/filters';
+import { SerializableFilter } from '../interfaces/serializable.filter.interface';
-export abstract class DatagridFilterRegistrar> implements OnDestroy {
+export abstract class DatagridFilterRegistrar> implements OnDestroy {
constructor(private filters: FiltersProvider) { }
public registered: RegisteredFilter;
diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts
index da719aa4ff..6187e04fc3 100644
--- a/src/dev/src/app/datagrid/utils/color-filter.ts
+++ b/src/dev/src/app/datagrid/utils/color-filter.ts
@@ -47,11 +47,4 @@ export class ColorFilter implements ClrDatagridFilterInterface {
isActive(): boolean {
return this.nbColors > 0;
}
-
- /**
- * Compare objects by reference
- */
- public equals(objectToCompare: ColorFilter): boolean {
- return this === objectToCompare;
- }
}
From 63ec41ae35278602ffe5f75be6fbbfff36577ac0 Mon Sep 17 00:00:00 2001
From: speti43
Date: Tue, 4 Dec 2018 10:18:03 +0100
Subject: [PATCH 03/14] no special case for filterstate
---
.../data/datagrid/providers/state.provider.ts | 169 ++++++++----------
1 file changed, 79 insertions(+), 90 deletions(-)
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index ea8d2b12ea..99f4e4774f 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -24,105 +24,94 @@ import { SerializableFilter } from '../interfaces/serializable.filter.interface'
*/
@Injectable()
export class StateProvider {
- constructor(
- private filters: FiltersProvider,
- private sort: Sort,
- private page: Page,
- private debouncer: StateDebouncer
- ) {}
+ constructor(
+ private filters: FiltersProvider,
+ private sort: Sort,
+ private page: Page,
+ private debouncer: StateDebouncer
+ ) { }
- /**
- * The Observable that lets other classes subscribe to global state changes
- */
- change: Observable> = this.debouncer.change.pipe(map(() => this.state));
-
- /*
- * By making this a getter, we open the possibility for a setter in the future.
- * It's been requested a couple times.
+ /**
+ * The Observable that lets other classes subscribe to global state changes
*/
- get state(): ClrDatagridStateInterface {
- const state: ClrDatagridStateInterface = {};
- if (this.page.size > 0) {
- state.page = { from: this.page.firstItem, to: this.page.lastItem, size: this.page.size };
- }
- if (this.sort.comparator) {
- if (this.sort.comparator instanceof DatagridPropertyComparator) {
- /*
- * Special case for the default object property comparator,
- * we give the property name instead of the actual comparator.
- */
- state.sort = { by: (>this.sort.comparator).prop, reverse: this.sort.reverse };
- } else {
- state.sort = { by: this.sort.comparator, reverse: this.sort.reverse };
- }
- }
+ change: Observable> = this.debouncer.change.pipe(map(() => this.state));
- const activeFilters = this.filters.getActiveFilters();
- if (activeFilters.length > 0) {
- state.filters = [];
- for (const filter of activeFilters) {
- if (filter instanceof DatagridStringFilterImpl) {
- const stringFilter = filter.filterFn;
- if (stringFilter instanceof DatagridPropertyStringFilter) {
- /*
- * Special case again for the default object property filter,
- * we give the property name instead of the full filter object.
+ /*
+ * By making this a getter, we open the possibility for a setter in the future.
+ * It's been requested a couple times.
+ */
+ get state(): ClrDatagridStateInterface {
+ const state: ClrDatagridStateInterface = {};
+ if (this.page.size > 0) {
+ state.page = { from: this.page.firstItem, to: this.page.lastItem, size: this.page.size };
+ }
+ if (this.sort.comparator) {
+ if (this.sort.comparator instanceof DatagridPropertyComparator) {
+ /*
+ * Special case for the default object property comparator,
+ * we give the property name instead of the actual comparator.
*/
- state.filters.push({
- property: stringFilter.prop,
- value: filter.value,
- });
- continue;
- }
+ state.sort = { by: (>this.sort.comparator).prop, reverse: this.sort.reverse };
+ } else {
+ state.sort = { by: this.sort.comparator, reverse: this.sort.reverse };
+ }
}
- state.filters.push(filter);
- }
- }
- return state;
- }
- set state(state: ClrDatagridStateInterface) {
- this.debouncer.changeStart();
- if (state.page) {
- this.page.size = state.page.size;
- this.page.current = Math.ceil(state.page.from / state.page.size) + 1;
- }
- if (state.sort) {
- if (typeof state.sort.by === 'string') {
- if (!(this.sort.comparator instanceof DatagridPropertyComparator)) {
- this.sort.comparator = new DatagridPropertyComparator(state.sort.by);
- }
- if (this.sort.reverse !== state.sort.reverse) {
- this.sort.toggle(this.sort.comparator);
+ const activeFilters = this.filters.getActiveFilters();
+ if (activeFilters.length > 0) {
+ state.filters = [];
+ for (const filter of activeFilters) {
+ state.filters.push({
+ property: filter.filterState.property,
+ value: filter.filterState.value,
+ });
+ }
}
- } else {
- this.sort.comparator = state.sort.by;
- this.sort.reverse = state.sort.reverse;
- }
+ return state;
}
- if (state.filters) {
- const activeFilters = this.filters.getActiveFilters();
- for (const filter of state.filters) {
- let filterObject: SerializableFilter;
- if (filter.hasOwnProperty('property') && filter.hasOwnProperty('value')) {
- const defaultFilterRepresentation = filter as { property: string; value: string };
- const propertyStringFilter = new DatagridPropertyStringFilter(defaultFilterRepresentation.property);
- const stringFilter = new DatagridStringFilterImpl(propertyStringFilter);
- stringFilter.value = defaultFilterRepresentation.value;
- filterObject = stringFilter;
- } else {
- filterObject = filter as SerializableFilter;
+
+ set state(state: ClrDatagridStateInterface) {
+ this.debouncer.changeStart();
+ if (state.page) {
+ this.page.size = state.page.size;
+ this.page.current = Math.ceil(state.page.from / state.page.size) + 1;
}
- const existing = activeFilters.findIndex(value => value.compatibleToState(filterObject.filterState));
- if (existing !== -1) {
- activeFilters.splice(existing, 1);
- } else {
- this.filters.add(filterObject);
+ if (state.sort) {
+ if (typeof state.sort.by === 'string') {
+ if (!(this.sort.comparator instanceof DatagridPropertyComparator)) {
+ this.sort.comparator = new DatagridPropertyComparator(state.sort.by);
+ }
+ if (this.sort.reverse !== state.sort.reverse) {
+ this.sort.toggle(this.sort.comparator);
+ }
+ } else {
+ this.sort.comparator = state.sort.by;
+ this.sort.reverse = state.sort.reverse;
+ }
}
- }
- activeFilters.forEach(filter => this.filters.remove(filter));
+ if (state.filters) {
+ const activeFilters = this.filters.getActiveFilters();
+ for (const filter of state.filters) {
+ let filterObject: SerializableFilter;
+ if (filter.hasOwnProperty('property') && filter.hasOwnProperty('value')) {
+ const defaultFilterRepresentation = filter as { property: string; value: string };
+ const propertyStringFilter = new DatagridPropertyStringFilter(defaultFilterRepresentation.property);
+ const stringFilter = new DatagridStringFilterImpl(propertyStringFilter);
+ stringFilter.value = defaultFilterRepresentation.value;
+ filterObject = stringFilter;
+ } else {
+ filterObject = filter as SerializableFilter;
+ }
+ const existing = activeFilters.findIndex(value => value.compatibleToState(filterObject.filterState));
+ if (existing !== -1) {
+ activeFilters.splice(existing, 1);
+ } else {
+ this.filters.add(filterObject);
+ }
+ }
+ activeFilters.forEach(filter => this.filters.remove(filter));
+ }
+
+ this.debouncer.changeDone();
}
-
- this.debouncer.changeDone();
- }
}
From dbb4ed99e7f1b79634cccfc52fd6a9c9bf80982e Mon Sep 17 00:00:00 2001
From: speti43
Date: Tue, 4 Dec 2018 11:07:46 +0100
Subject: [PATCH 04/14] Dedicated StringFilterStateInterface
---
.../filters/datagrid-string-filter-impl.ts | 14 ++++++++++----
.../datagrid/interfaces/filter.state.interface.ts | 2 --
.../interfaces/string.filter.state.interface.ts | 12 ++++++++++++
.../data/datagrid/providers/state.provider.ts | 13 ++++++++-----
4 files changed, 30 insertions(+), 11 deletions(-)
create mode 100644 src/clr-angular/data/datagrid/interfaces/string.filter.state.interface.ts
diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
index 869429c6f8..055217941f 100644
--- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
+++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
@@ -9,6 +9,7 @@ import { ClrDatagridStringFilterInterface } from '../../interfaces/string-filter
import { DatagridPropertyStringFilter } from './datagrid-property-string-filter';
import { SerializableFilter } from '../../interfaces/serializable.filter.interface';
import { FilterStateInterface } from '../../interfaces/filter.state.interface';
+import { StringFilterStateInterface } from '../../interfaces/string.filter.state.interface';
export class DatagridStringFilterImpl implements SerializableFilter {
constructor(public filterFn: ClrDatagridStringFilterInterface) {
@@ -25,7 +26,7 @@ export class DatagridStringFilterImpl implements SerializableFilter
* The Observable required as part of the Filter interface
*/
private _changes = new Subject();
- private _state: FilterStateInterface;
+ private _state: StringFilterStateInterface;
// We do not want to expose the Subject itself, but the Observable which is read-only
public get changes(): Observable {
@@ -61,11 +62,11 @@ export class DatagridStringFilterImpl implements SerializableFilter
this._state.value = this.value;
}
- public get filterState(): FilterStateInterface {
+ public get filterState(): StringFilterStateInterface {
return this._state;
}
- public set filterState(state: FilterStateInterface) {
+ public set filterState(state: StringFilterStateInterface) {
this._state = state;
this._rawValue = state.value;
this._changes.next();
@@ -90,6 +91,11 @@ export class DatagridStringFilterImpl implements SerializableFilter
* Compare objects by properties
*/
public compatibleToState(state: FilterStateInterface): boolean {
- return state.type === this._state.type && state.property === this._state.property;
+ if (state.type === this._state.type) {
+ const stringState = state;
+ return stringState.property === this._state.property;
+ } else {
+ return false;
+ }
}
}
diff --git a/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts
index a6961707f3..13f8143a5b 100644
--- a/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts
+++ b/src/clr-angular/data/datagrid/interfaces/filter.state.interface.ts
@@ -6,6 +6,4 @@
export interface FilterStateInterface {
type: string;
- property: string;
- value: string;
}
diff --git a/src/clr-angular/data/datagrid/interfaces/string.filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/string.filter.state.interface.ts
new file mode 100644
index 0000000000..db8f8053ce
--- /dev/null
+++ b/src/clr-angular/data/datagrid/interfaces/string.filter.state.interface.ts
@@ -0,0 +1,12 @@
+import { FilterStateInterface } from "./filter.state.interface";
+
+/*
+ * Copyright (c) 2016-2018 VMware, Inc. All Rights Reserved.
+ * This software is released under MIT license.
+ * The full license information can be found in LICENSE in the root directory of this project.
+ */
+
+export interface StringFilterStateInterface extends FilterStateInterface {
+ property: string;
+ value: string;
+}
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index 99f4e4774f..6deef82507 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -16,8 +16,8 @@ import { FiltersProvider } from './filters';
import { Page } from './page';
import { Sort } from './sort';
import { StateDebouncer } from './state-debouncer.provider';
-import { ClrDatagridFilterInterface } from '../interfaces/filter.interface';
import { SerializableFilter } from '../interfaces/serializable.filter.interface';
+import { StringFilterStateInterface } from '../interfaces/string.filter.state.interface';
/**
* This provider aggregates state changes from the various providers of the Datagrid
@@ -61,10 +61,13 @@ export class StateProvider {
if (activeFilters.length > 0) {
state.filters = [];
for (const filter of activeFilters) {
- state.filters.push({
- property: filter.filterState.property,
- value: filter.filterState.value,
- });
+ if (filter.filterState.type === 'BuiltinStringFilter') {
+ const stringFilterState = filter.filterState;
+ state.filters.push({
+ property: stringFilterState.property,
+ value: stringFilterState.value,
+ });
+ }
}
}
return state;
From 96f81691d669203c8dde3b7955a6befea3647f6c Mon Sep 17 00:00:00 2001
From: speti43
Date: Tue, 4 Dec 2018 12:14:11 +0100
Subject: [PATCH 05/14] memory leak fix
---
.../data/datagrid/providers/state.provider.ts | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index 6deef82507..39118a50c8 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -35,7 +35,7 @@ export class StateProvider {
* The Observable that lets other classes subscribe to global state changes
*/
change: Observable> = this.debouncer.change.pipe(map(() => this.state));
-
+ _prevState: ClrDatagridStateInterface;
/*
* By making this a getter, we open the possibility for a setter in the future.
* It's been requested a couple times.
@@ -74,6 +74,9 @@ export class StateProvider {
}
set state(state: ClrDatagridStateInterface) {
+ if (this.sameAsPreviousState(state)) {
+ return;
+ }
this.debouncer.changeStart();
if (state.page) {
this.page.size = state.page.size;
@@ -117,4 +120,12 @@ export class StateProvider {
this.debouncer.changeDone();
}
+
+ sameAsPreviousState(state: ClrDatagridStateInterface) {
+ const sameAsPreviousState = JSON.stringify(this._prevState) === JSON.stringify(state);
+ if (!sameAsPreviousState) {
+ this._prevState = state;
+ }
+ return sameAsPreviousState;
+ }
}
From a12498e391532483f0b3d4e6e3bd44543eaef5f5 Mon Sep 17 00:00:00 2001
From: speti43
Date: Thu, 6 Dec 2018 13:21:42 +0100
Subject: [PATCH 06/14] Filter bugfix, dedicated state binding demo
---
.../filters/datagrid-string-filter-impl.ts | 6 +-
src/clr-angular/data/datagrid/datagrid.ts | 485 +++++++++---------
.../data/datagrid/providers/filters.ts | 9 +-
.../binding-state/binding-state.component.css | 0
.../binding-state.component.html | 79 +++
.../binding-state.component.spec.ts | 25 +
.../binding-state/binding-state.component.ts | 50 ++
src/dev/src/app/datagrid/datagrid.demo.html | 1 +
.../src/app/datagrid/datagrid.demo.module.ts | 2 +
.../src/app/datagrid/datagrid.demo.routing.ts | 2 +
.../datagrid/server-driven/server-driven.html | 2 +-
.../datagrid/server-driven/server-driven.ts | 17 +-
12 files changed, 422 insertions(+), 256 deletions(-)
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.css
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.html
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.ts
diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
index 055217941f..51c996f7f5 100644
--- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
+++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
@@ -57,9 +57,9 @@ export class DatagridStringFilterImpl implements SerializableFilter
if (value !== this._rawValue) {
this._rawValue = value;
this._lowerCaseValue = value.toLowerCase().trim();
+ this._state.value = this.value;
this._changes.next(value);
- }
- this._state.value = this.value;
+ }
}
public get filterState(): StringFilterStateInterface {
@@ -91,7 +91,7 @@ export class DatagridStringFilterImpl implements SerializableFilter
* Compare objects by properties
*/
public compatibleToState(state: FilterStateInterface): boolean {
- if (state.type === this._state.type) {
+ if (state && state.type === this._state.type) {
const stringState = state;
return stringState.property === this._state.property;
} else {
diff --git a/src/clr-angular/data/datagrid/datagrid.ts b/src/clr-angular/data/datagrid/datagrid.ts
index 08e35414f3..3e7693b0bf 100644
--- a/src/clr-angular/data/datagrid/datagrid.ts
+++ b/src/clr-angular/data/datagrid/datagrid.ts
@@ -4,20 +4,20 @@
* The full license information can be found in LICENSE in the root directory of this project.
*/
import {
- AfterContentInit,
- AfterViewInit,
- Component,
- ContentChild,
- ContentChildren,
- ElementRef,
- EventEmitter,
- Input,
- OnDestroy,
- Output,
- QueryList,
- Renderer2,
- ViewChild,
- ViewContainerRef,
+ AfterContentInit,
+ AfterViewInit,
+ Component,
+ ContentChild,
+ ContentChildren,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ Output,
+ QueryList,
+ Renderer2,
+ ViewChild,
+ ViewContainerRef,
} from '@angular/core';
import { Subscription } from 'rxjs';
@@ -44,265 +44,270 @@ import { DatagridRenderOrganizer } from './render/render-organizer';
import { ClrCommonStrings } from '../../utils/i18n/common-strings.interface';
@Component({
- selector: 'clr-datagrid',
- templateUrl: './datagrid.html',
- providers: [
- Selection,
- Sort,
- FiltersProvider,
- Page,
- Items,
- DatagridRenderOrganizer,
- RowActionService,
- ExpandableRowsCount,
- HideableColumnService,
- StateDebouncer,
- StateProvider,
- ColumnToggleButtonsService,
- TableSizeService,
- DisplayModeService,
- ],
- host: { '[class.datagrid-host]': 'true' },
+ selector: 'clr-datagrid',
+ templateUrl: './datagrid.html',
+ providers: [
+ Selection,
+ Sort,
+ FiltersProvider,
+ Page,
+ Items,
+ DatagridRenderOrganizer,
+ RowActionService,
+ ExpandableRowsCount,
+ HideableColumnService,
+ StateDebouncer,
+ StateProvider,
+ ColumnToggleButtonsService,
+ TableSizeService,
+ DisplayModeService,
+ ],
+ host: { '[class.datagrid-host]': 'true' },
})
export class ClrDatagrid implements AfterContentInit, AfterViewInit, OnDestroy {
- constructor(
- private columnService: HideableColumnService,
- private organizer: DatagridRenderOrganizer,
- public items: Items,
- public expandableRows: ExpandableRowsCount,
- public selection: Selection,
- public rowActionService: RowActionService,
- private stateProvider: StateProvider,
- private displayMode: DisplayModeService,
- private renderer: Renderer2,
- private el: ElementRef,
- public commonStrings: ClrCommonStrings
- ) {}
+ constructor(
+ private columnService: HideableColumnService,
+ private organizer: DatagridRenderOrganizer,
+ public items: Items,
+ public expandableRows: ExpandableRowsCount,
+ public selection: Selection,
+ public rowActionService: RowActionService,
+ private stateProvider: StateProvider,
+ private displayMode: DisplayModeService,
+ private renderer: Renderer2,
+ private el: ElementRef,
+ public commonStrings: ClrCommonStrings
+ ) { }
- /* reference to the enum so that template can access */
- public SELECTION_TYPE = SelectionType;
+ /* reference to the enum so that template can access */
+ public SELECTION_TYPE = SelectionType;
- /**
- * Freezes the datagrid while data is loading
- */
- public get loading(): boolean {
- return this.items.loading;
- }
+ /**
+ * Freezes the datagrid while data is loading
+ */
+ public get loading(): boolean {
+ return this.items.loading;
+ }
- @Input('clrDgLoading')
- public set loading(value: boolean) {
- this.items.loading = value;
- }
+ @Input('clrDgLoading')
+ public set loading(value: boolean) {
+ this.items.loading = value;
+ }
- /**
- * Output emitted whenever the data needs to be refreshed, based on user action or external ones
- */
- @Output('clrDgStateChange') public stateChange = new EventEmitter>(false);
+ /**
+ * Output emitted whenever the data needs to be refreshed, based on user action or external ones
+ */
+ @Output('clrDgStateChange') public stateChange = new EventEmitter>(false);
- @Output('clrDgRefresh') public refresh = this.stateChange;
+ @Output('clrDgRefresh') public refresh = this.stateChange;
- @Input('clrDgState')
- public set state(value: ClrDatagridStateInterface) {
- // ignore the initial undefined value
- if (value) {
- this.stateProvider.state = value;
+ @Input('clrDgState')
+ public set state(value: ClrDatagridStateInterface) {
+ // ignore the initial undefined value
+ if (value) {
+ this.stateProvider.state = value;
+ }
}
- }
- /**
- * Public method to re-trigger the computation of displayed items manually
- */
- public dataChanged() {
- this.items.refresh();
- }
+ /**
+ * Public method to re-trigger the computation of displayed items manually
+ */
+ public dataChanged() {
+ this.items.refresh();
+ }
- /**
- * We grab the smart iterator from projected content
- */
- @ContentChild(ClrDatagridItems) public iterator: ClrDatagridItems;
+ /**
+ * We grab the smart iterator from projected content
+ */
+ @ContentChild(ClrDatagridItems) public iterator: ClrDatagridItems;
- /**
- * Array of all selected items
- */
- @Input('clrDgSelected')
- set selected(value: T[]) {
- if (value) {
- this.selection.selectionType = SelectionType.Multi;
- } else {
- this.selection.selectionType = SelectionType.None;
+ /**
+ * Array of all selected items
+ */
+ @Input('clrDgSelected')
+ set selected(value: T[]) {
+ if (value) {
+ this.selection.selectionType = SelectionType.Multi;
+ } else {
+ this.selection.selectionType = SelectionType.None;
+ }
+ this.selection.updateCurrent(value, false);
}
- this.selection.updateCurrent(value, false);
- }
- @Output('clrDgSelectedChange') selectedChanged = new EventEmitter(false);
+ @Output('clrDgSelectedChange') selectedChanged = new EventEmitter(false);
- /**
- * Selected item in single-select mode
- */
- @Input('clrDgSingleSelected')
- set singleSelected(value: T) {
- this.selection.selectionType = SelectionType.Single;
- // the clrDgSingleSelected is updated in one of two cases:
- // 1. an explicit value is passed
- // 2. is being set to null or undefined, where previously it had a value
- if (value) {
- this.selection.currentSingle = value;
- } else if (this.selection.currentSingle) {
- this.selection.currentSingle = null;
+ /**
+ * Selected item in single-select mode
+ */
+ @Input('clrDgSingleSelected')
+ set singleSelected(value: T) {
+ this.selection.selectionType = SelectionType.Single;
+ // the clrDgSingleSelected is updated in one of two cases:
+ // 1. an explicit value is passed
+ // 2. is being set to null or undefined, where previously it had a value
+ if (value) {
+ this.selection.currentSingle = value;
+ } else if (this.selection.currentSingle) {
+ this.selection.currentSingle = null;
+ }
}
- }
- @Output('clrDgSingleSelectedChange') singleSelectedChanged = new EventEmitter(false);
+ @Output('clrDgSingleSelectedChange') singleSelectedChanged = new EventEmitter(false);
- /**
- * Selection/Deselection on row click mode
- */
- @Input('clrDgRowSelection')
- set rowSelectionMode(value: boolean) {
- this.selection.rowSelectionMode = value;
- }
-
- /**
- * Indicates if all currently displayed items are selected
- */
- public get allSelected() {
- return this.selection.isAllSelected();
- }
+ /**
+ * Selection/Deselection on row click mode
+ */
+ @Input('clrDgRowSelection')
+ set rowSelectionMode(value: boolean) {
+ this.selection.rowSelectionMode = value;
+ }
- /**
- * Selects/deselects all currently displayed items
- * @param value
- */
- public set allSelected(value: boolean) {
- /*
- * This is a setter but we ignore the value.
- * It's strange, but it lets us have an indeterminate state where only
- * some of the items are selected.
- */
- this.selection.toggleAll();
- }
+ /**
+ * Indicates if all currently displayed items are selected
+ */
+ public get allSelected() {
+ return this.selection.isAllSelected();
+ }
- /**
- * Custom placeholder detection
- */
- @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder;
+ /**
+ * Selects/deselects all currently displayed items
+ * @param value
+ */
+ public set allSelected(value: boolean) {
+ /*
+ * This is a setter but we ignore the value.
+ * It's strange, but it lets us have an indeterminate state where only
+ * some of the items are selected.
+ */
+ this.selection.toggleAll();
+ }
- /**
- * Hideable Column data source / detection.
- */
- @ContentChildren(ClrDatagridColumn) public columns: QueryList>;
+ /**
+ * Custom placeholder detection
+ */
+ @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder;
- /**
- * When the datagrid is user-managed without the smart iterator, we get the items displayed
- * by querying the projected content. This is needed to keep track of the models currently
- * displayed, typically for selection.
- */
+ /**
+ * Hideable Column data source / detection.
+ */
+ @ContentChildren(ClrDatagridColumn) public columns: QueryList>;
- @ContentChildren(ClrDatagridRow) rows: QueryList>;
- @ViewChild('scrollableColumns', { read: ViewContainerRef })
- scrollableColumns: ViewContainerRef;
+ /**
+ * When the datagrid is user-managed without the smart iterator, we get the items displayed
+ * by querying the projected content. This is needed to keep track of the models currently
+ * displayed, typically for selection.
+ */
- ngAfterContentInit() {
- if (!this.items.smart) {
- this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
- }
+ @ContentChildren(ClrDatagridRow) rows: QueryList>;
+ @ViewChild('scrollableColumns', { read: ViewContainerRef })
+ scrollableColumns: ViewContainerRef;
- this._subscriptions.push(
- this.rows.changes.subscribe(() => {
+ ngAfterContentInit() {
if (!this.items.smart) {
- this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
+ this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
}
- this.rows.forEach(row => {
- this._displayedRows.insert(row._view);
- });
- })
- );
- this._subscriptions.push(
- this.columns.changes.subscribe((columns: ClrDatagridColumn[]) => {
- this.columnService.updateColumnList(this.columns.map(col => col.hideable));
- })
- );
+ this._subscriptions.push(
+ this.rows.changes.subscribe(() => {
+ if (!this.items.smart) {
+ this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
+ }
+ this.rows.forEach(row => {
+ this._displayedRows.insert(row._view);
+ });
+ })
+ );
- // Get ColumnService ready for HideableColumns.
- this.columnService.updateColumnList(this.columns.map(col => col.hideable));
- }
+ this._subscriptions.push(
+ this.columns.changes.subscribe((columns: ClrDatagridColumn[]) => {
+ this.columnService.updateColumnList(this.columns.map(col => col.hideable));
+ })
+ );
- /**
- * Our setup happens in the view of some of our components, so we wait for it to be done before starting
- */
- ngAfterViewInit() {
- // TODO: determine if we can get rid of provider wiring in view init so that subscriptions can be done earlier
- this.stateChange.emit(this.stateProvider.state);
- this._subscriptions.push(this.stateProvider.change.subscribe(state => this.stateChange.emit(state)));
- this._subscriptions.push(
- this.selection.change.subscribe(s => {
- if (this.selection.selectionType === SelectionType.Single) {
- this.singleSelectedChanged.emit(s);
- } else if (this.selection.selectionType === SelectionType.Multi) {
- this.selectedChanged.emit(s);
- }
- })
- );
- // A subscription that listens for displayMode changes on the datagrid
- this.displayMode.view.subscribe(viewChange => {
- // Remove any projected columns from the projectedDisplayColumns container
- for (let i = this._projectedDisplayColumns.length; i > 0; i--) {
- this._projectedDisplayColumns.detach();
- }
- // Remove any projected columns from the projectedCalculationColumns container
- for (let i = this._projectedCalculationColumns.length; i > 0; i--) {
- this._projectedCalculationColumns.detach();
- }
- // Remove any projected rows from the calculationRows container
- for (let i = this._calculationRows.length; i > 0; i--) {
- this._calculationRows.detach();
- }
- // Remove any projected rows from the displayedRows container
- for (let i = this._displayedRows.length; i > 0; i--) {
- this._displayedRows.detach();
- }
- if (viewChange === DatagridDisplayMode.DISPLAY) {
- // Set state, style for the datagrid to DISPLAY and insert row & columns into containers
- this.renderer.removeClass(this.el.nativeElement, 'datagrid-calculate-mode');
- this.columns.forEach(column => {
- this._projectedDisplayColumns.insert(column._view);
- });
- this.rows.forEach(row => {
- this._displayedRows.insert(row._view);
- });
- } else {
- // Set state, style for the datagrid to CALCULATE and insert row & columns into containers
- this.renderer.addClass(this.el.nativeElement, 'datagrid-calculate-mode');
- this.columns.forEach(column => {
- this._projectedCalculationColumns.insert(column._view);
- });
- this.rows.forEach(row => {
- this._calculationRows.insert(row._view);
+ // Get ColumnService ready for HideableColumns.
+ this.columnService.updateColumnList(this.columns.map(col => col.hideable));
+ }
+
+ /**
+ * Our setup happens in the view of some of our components, so we wait for it to be done before starting
+ */
+ ngAfterViewInit() {
+ // TODO: determine if we can get rid of provider wiring in view init so that subscriptions can be done earlier
+ this.stateChange.emit(this.stateProvider.state);
+ this._subscriptions.push(this.stateProvider.change.subscribe(state => {
+ if (!state.page) {
+ return;
+ }
+ this.stateChange.emit(state);
+ }));
+ this._subscriptions.push(
+ this.selection.change.subscribe(s => {
+ if (this.selection.selectionType === SelectionType.Single) {
+ this.singleSelectedChanged.emit(s);
+ } else if (this.selection.selectionType === SelectionType.Multi) {
+ this.selectedChanged.emit(s);
+ }
+ })
+ );
+ // A subscription that listens for displayMode changes on the datagrid
+ this.displayMode.view.subscribe(viewChange => {
+ // Remove any projected columns from the projectedDisplayColumns container
+ for (let i = this._projectedDisplayColumns.length; i > 0; i--) {
+ this._projectedDisplayColumns.detach();
+ }
+ // Remove any projected columns from the projectedCalculationColumns container
+ for (let i = this._projectedCalculationColumns.length; i > 0; i--) {
+ this._projectedCalculationColumns.detach();
+ }
+ // Remove any projected rows from the calculationRows container
+ for (let i = this._calculationRows.length; i > 0; i--) {
+ this._calculationRows.detach();
+ }
+ // Remove any projected rows from the displayedRows container
+ for (let i = this._displayedRows.length; i > 0; i--) {
+ this._displayedRows.detach();
+ }
+ if (viewChange === DatagridDisplayMode.DISPLAY) {
+ // Set state, style for the datagrid to DISPLAY and insert row & columns into containers
+ this.renderer.removeClass(this.el.nativeElement, 'datagrid-calculate-mode');
+ this.columns.forEach(column => {
+ this._projectedDisplayColumns.insert(column._view);
+ });
+ this.rows.forEach(row => {
+ this._displayedRows.insert(row._view);
+ });
+ } else {
+ // Set state, style for the datagrid to CALCULATE and insert row & columns into containers
+ this.renderer.addClass(this.el.nativeElement, 'datagrid-calculate-mode');
+ this.columns.forEach(column => {
+ this._projectedCalculationColumns.insert(column._view);
+ });
+ this.rows.forEach(row => {
+ this._calculationRows.insert(row._view);
+ });
+ }
});
- }
- });
- }
+ }
- /**
- * Subscriptions to all the services and queries changes
- */
- private _subscriptions: Subscription[] = [];
+ /**
+ * Subscriptions to all the services and queries changes
+ */
+ private _subscriptions: Subscription[] = [];
- ngOnDestroy() {
- this._subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
- }
+ ngOnDestroy() {
+ this._subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
+ }
- resize(): void {
- this.organizer.resize();
- }
+ resize(): void {
+ this.organizer.resize();
+ }
- @ViewChild('projectedDisplayColumns', { read: ViewContainerRef })
- _projectedDisplayColumns: ViewContainerRef;
- @ViewChild('projectedCalculationColumns', { read: ViewContainerRef })
- _projectedCalculationColumns: ViewContainerRef;
- @ViewChild('displayedRows', { read: ViewContainerRef })
- _displayedRows: ViewContainerRef;
- @ViewChild('calculationRows', { read: ViewContainerRef })
- _calculationRows: ViewContainerRef;
+ @ViewChild('projectedDisplayColumns', { read: ViewContainerRef })
+ _projectedDisplayColumns: ViewContainerRef;
+ @ViewChild('projectedCalculationColumns', { read: ViewContainerRef })
+ _projectedCalculationColumns: ViewContainerRef;
+ @ViewChild('displayedRows', { read: ViewContainerRef })
+ _displayedRows: ViewContainerRef;
+ @ViewChild('calculationRows', { read: ViewContainerRef })
+ _calculationRows: ViewContainerRef;
}
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index bc1dff0ea2..1a267ea0d9 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -68,7 +68,13 @@ export class FiltersProvider {
return;
}
subscription.unsubscribe();
- const index = this._all.findIndex(value => value.filter.compatibleToState(filter.filterState));
+ const index = this._all.findIndex(value => {
+ if (value.filter.compatibleToState) {
+ return value.filter.compatibleToState(filter.filterState);
+ } else {
+ return false;
+ }
+ });
if (index !== -1) {
this._all.splice(index, 1);
}
@@ -112,6 +118,7 @@ export class FiltersProvider {
// filtering may change the page number such that current page number doesn't exist in the filtered dataset.
// So here we always set the current page to 1 so that it'll fetch first page's data with the given filter.
this._page.current = 1;
+ debugger;
this._change.next(filters);
this.stateDebouncer.changeDone();
}
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.css b/src/dev/src/app/datagrid/binding-state/binding-state.component.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.html b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
new file mode 100644
index 0000000000..2bb1722ba9
--- /dev/null
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
@@ -0,0 +1,79 @@
+
+
+Server-driven datagrid
+
+
+ When dealing with large amounts of data or heavy processing, a datagrid often has access the currently displayed
+ data only,
+ requesting only the necessary pieces from the server. This is a very common case that we fully support.
+
+
+ We expose events and hooks on all parts of the datagrid to make sure you can trigger any requests you need based on
+ precise
+ user actions. But an important thing to note is that when the server does handle pagination, it needs to also deal
+ with
+ sorting and filtering, because all three are tightly coupled. In light of this, we decided to expose a single
+ global output
+ (clrDgRefresh)
that emits the current "state" of the datagrid whenever it changes due
+ to a user action or an external one. This state
+ has the following format:
+
+
+
+ It contains all the information you need to send to the server in order to get the slice of data currently
+ displayed. It
+ even contains redundant information (
+ page.to
and
+ page.size
for instance), to make sure you have access to the most practical for you
+ without extra processing.
+
+
+ One important thing to note is that since you don't have all the data available at once, you cannot use the smart
+ iterator
+ *clrDgItems
: it would sort, filter and paginate a subset of the data that has already
+ gone through all that. So all you need to do
+ is to go back to a simple
+ *ngFor
, which we support.
+
+
+ Finally, since server calls are involved, we need some way of notifying the user that his action has been
+ acknowledged and
+ that we are currently working on it. To this effect, we provide an input
+ [clrDgLoading]
that you can use to display the datagrid in a loading state, while
+ fetching data.
+
+
+
+ Here is an example showcasing all this with a fake server latency of 500ms. It also demonstrates how to adapt the
+ state we
+ emit when it does not match exactly your server's API: in this example, the filters are re-formatted from an array
+ to a
+ map.
+
+
+
+ User ID
+ Name
+ Creation date
+ Pokemon
+ Favorite color
+
+
+ {{user.id}}
+ {{user.name}}
+ {{user.creation | date}}
+ {{user.pokemon.name}}
+
+
+
+
+
+
+ {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{total}} users
+
+
+
\ No newline at end of file
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts
new file mode 100644
index 0000000000..fedf8dccba
--- /dev/null
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BindingStateComponent } from './binding-state.component';
+
+describe('BindingStateComponent', () => {
+ let component: BindingStateComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ BindingStateComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BindingStateComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
new file mode 100644
index 0000000000..a5280fd9c5
--- /dev/null
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
@@ -0,0 +1,50 @@
+import { Component, OnInit } from '@angular/core';
+import { Inventory, FetchResult } from '../inventory/inventory';
+import { User } from '../inventory/user';
+import { ClrDatagridStateInterface } from '@clr/angular';
+
+@Component({
+ selector: 'app-binding-state',
+ providers: [Inventory],
+ templateUrl: './binding-state.component.html',
+ styleUrls: ['./binding-state.component.css']
+})
+export class BindingStateComponent {
+
+ users: User[];
+ total: number;
+ loading: boolean = true;
+ state: ClrDatagridStateInterface =
+ {
+ page: { from: 0, to: 8, size: 10 },
+ sort: { by: "pokemon", reverse: false },
+ filters: [{ property: "name", value: "sampleName" }, { property: "creation", value: "sampleDate" }]
+ };
+ constructor(private inventory: Inventory) {
+ inventory.size = 103;
+ this.inventory.latency = 500;
+ inventory.reset();
+ }
+
+ refresh() {
+ this.loading = true;
+ const filters: { [prop: string]: any[] } = {};
+ if (this.state.filters) {
+ for (const filter of this.state.filters) {
+ const { property, value } = <{ property: string; value: string }>filter;
+ filters[property] = [value];
+ }
+
+ this.inventory
+ .filter(filters)
+ .sort(<{ by: string; reverse: boolean }>this.state.sort)
+ .fetch(this.state.page.from, this.state.page.size)
+ .then((result: FetchResult) => {
+ this.users = result.users;
+ this.total = result.length;
+ this.loading = false;
+ });
+ }
+ }
+
+}
diff --git a/src/dev/src/app/datagrid/datagrid.demo.html b/src/dev/src/app/datagrid/datagrid.demo.html
index b363a2f76a..55f62d82af 100644
--- a/src/dev/src/app/datagrid/datagrid.demo.html
+++ b/src/dev/src/app/datagrid/datagrid.demo.html
@@ -33,6 +33,7 @@ Datagrid
Asynchronous Test Cases
Hide-show columns demo
Pinned Columns
+ Binding State
diff --git a/src/dev/src/app/datagrid/datagrid.demo.module.ts b/src/dev/src/app/datagrid/datagrid.demo.module.ts
index b19a0e435b..b7b6673724 100644
--- a/src/dev/src/app/datagrid/datagrid.demo.module.ts
+++ b/src/dev/src/app/datagrid/datagrid.demo.module.ts
@@ -39,6 +39,7 @@ import { DatagridStringFilteringDemo } from './string-filtering/string-filtering
import { DatagridTestCasesAsyncDemo } from './test-cases-async/test-cases-async';
import { DatagridTestCasesDemo } from './test-cases/test-cases';
import { ColorFilter } from './utils/color-filter';
+import { BindingStateComponent } from './binding-state/binding-state.component';
@NgModule({
imports: [CommonModule, FormsModule, ClarityModule, ROUTING, UtilsDemoModule],
@@ -71,6 +72,7 @@ import { ColorFilter } from './utils/color-filter';
DatagridKitchenSinkDemo,
ColorFilter,
DetailWrapper,
+ BindingStateComponent,
],
exports: [
DatagridDemo,
diff --git a/src/dev/src/app/datagrid/datagrid.demo.routing.ts b/src/dev/src/app/datagrid/datagrid.demo.routing.ts
index 89e997f5d9..17d77eb5c6 100644
--- a/src/dev/src/app/datagrid/datagrid.demo.routing.ts
+++ b/src/dev/src/app/datagrid/datagrid.demo.routing.ts
@@ -32,6 +32,7 @@ import { DatagridSortingDemo } from './sorting/sorting';
import { DatagridStringFilteringDemo } from './string-filtering/string-filtering';
import { DatagridTestCasesAsyncDemo } from './test-cases-async/test-cases-async';
import { DatagridTestCasesDemo } from './test-cases/test-cases';
+import { BindingStateComponent } from './binding-state/binding-state.component';
const ROUTES: Routes = [
{
@@ -64,6 +65,7 @@ const ROUTES: Routes = [
{ path: 'test-cases', component: DatagridTestCasesDemo },
{ path: 'test-cases-async', component: DatagridTestCasesAsyncDemo },
{ path: 'hide-show', component: DatagridHideShowDemo },
+ { path: 'binding-state', component: BindingStateComponent }
],
},
];
diff --git a/src/dev/src/app/datagrid/server-driven/server-driven.html b/src/dev/src/app/datagrid/server-driven/server-driven.html
index 099d1e37b0..e1cc592029 100644
--- a/src/dev/src/app/datagrid/server-driven/server-driven.html
+++ b/src/dev/src/app/datagrid/server-driven/server-driven.html
@@ -42,7 +42,7 @@ Server-driven datagrid
map.
-
+
User ID
Name
Creation date
diff --git a/src/dev/src/app/datagrid/server-driven/server-driven.ts b/src/dev/src/app/datagrid/server-driven/server-driven.ts
index f8678f9387..33adea30ea 100644
--- a/src/dev/src/app/datagrid/server-driven/server-driven.ts
+++ b/src/dev/src/app/datagrid/server-driven/server-driven.ts
@@ -18,31 +18,26 @@ export class DatagridServerDrivenDemo {
users: User[];
total: number;
loading: boolean = true;
- state: ClrDatagridStateInterface =
- {
- page: { from: 0, to: 8, size: 10 },
- sort: { by: "pokemon", reverse: false },
- filters: [{ property: "name", value: "sampleName" },{ property: "creation", value: "sampleDate" }]
- };
+
constructor(private inventory: Inventory) {
inventory.size = 103;
this.inventory.latency = 500;
inventory.reset();
}
- refresh() {
+ refresh(state: ClrDatagridStateInterface) {
this.loading = true;
const filters: { [prop: string]: any[] } = {};
- if (this.state.filters) {
- for (const filter of this.state.filters) {
+ if (state.filters) {
+ for (const filter of state.filters) {
const { property, value } = <{ property: string; value: string }>filter;
filters[property] = [value];
}
}
this.inventory
.filter(filters)
- .sort(<{ by: string; reverse: boolean }>this.state.sort)
- .fetch(this.state.page.from, this.state.page.size)
+ .sort(<{ by: string; reverse: boolean }>state.sort)
+ .fetch(state.page.from, state.page.size)
.then((result: FetchResult) => {
this.users = result.users;
this.total = result.length;
From a9db687ef578a1b5c7d6d851cef2cc2357454259 Mon Sep 17 00:00:00 2001
From: speti43
Date: Thu, 6 Dec 2018 13:21:42 +0100
Subject: [PATCH 07/14] Filter bugfix, dedicated state binding demo
---
.../filters/datagrid-string-filter-impl.ts | 6 +-
src/clr-angular/data/datagrid/datagrid.ts | 485 +++++++++---------
.../data/datagrid/providers/filters.ts | 8 +-
.../binding-state/binding-state.component.css | 0
.../binding-state.component.html | 79 +++
.../binding-state.component.spec.ts | 25 +
.../binding-state/binding-state.component.ts | 50 ++
src/dev/src/app/datagrid/datagrid.demo.html | 1 +
.../src/app/datagrid/datagrid.demo.module.ts | 2 +
.../src/app/datagrid/datagrid.demo.routing.ts | 2 +
.../datagrid/server-driven/server-driven.html | 2 +-
.../datagrid/server-driven/server-driven.ts | 17 +-
12 files changed, 421 insertions(+), 256 deletions(-)
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.css
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.html
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts
create mode 100644 src/dev/src/app/datagrid/binding-state/binding-state.component.ts
diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
index 055217941f..51c996f7f5 100644
--- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
+++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
@@ -57,9 +57,9 @@ export class DatagridStringFilterImpl implements SerializableFilter
if (value !== this._rawValue) {
this._rawValue = value;
this._lowerCaseValue = value.toLowerCase().trim();
+ this._state.value = this.value;
this._changes.next(value);
- }
- this._state.value = this.value;
+ }
}
public get filterState(): StringFilterStateInterface {
@@ -91,7 +91,7 @@ export class DatagridStringFilterImpl implements SerializableFilter
* Compare objects by properties
*/
public compatibleToState(state: FilterStateInterface): boolean {
- if (state.type === this._state.type) {
+ if (state && state.type === this._state.type) {
const stringState = state;
return stringState.property === this._state.property;
} else {
diff --git a/src/clr-angular/data/datagrid/datagrid.ts b/src/clr-angular/data/datagrid/datagrid.ts
index 08e35414f3..3e7693b0bf 100644
--- a/src/clr-angular/data/datagrid/datagrid.ts
+++ b/src/clr-angular/data/datagrid/datagrid.ts
@@ -4,20 +4,20 @@
* The full license information can be found in LICENSE in the root directory of this project.
*/
import {
- AfterContentInit,
- AfterViewInit,
- Component,
- ContentChild,
- ContentChildren,
- ElementRef,
- EventEmitter,
- Input,
- OnDestroy,
- Output,
- QueryList,
- Renderer2,
- ViewChild,
- ViewContainerRef,
+ AfterContentInit,
+ AfterViewInit,
+ Component,
+ ContentChild,
+ ContentChildren,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ Output,
+ QueryList,
+ Renderer2,
+ ViewChild,
+ ViewContainerRef,
} from '@angular/core';
import { Subscription } from 'rxjs';
@@ -44,265 +44,270 @@ import { DatagridRenderOrganizer } from './render/render-organizer';
import { ClrCommonStrings } from '../../utils/i18n/common-strings.interface';
@Component({
- selector: 'clr-datagrid',
- templateUrl: './datagrid.html',
- providers: [
- Selection,
- Sort,
- FiltersProvider,
- Page,
- Items,
- DatagridRenderOrganizer,
- RowActionService,
- ExpandableRowsCount,
- HideableColumnService,
- StateDebouncer,
- StateProvider,
- ColumnToggleButtonsService,
- TableSizeService,
- DisplayModeService,
- ],
- host: { '[class.datagrid-host]': 'true' },
+ selector: 'clr-datagrid',
+ templateUrl: './datagrid.html',
+ providers: [
+ Selection,
+ Sort,
+ FiltersProvider,
+ Page,
+ Items,
+ DatagridRenderOrganizer,
+ RowActionService,
+ ExpandableRowsCount,
+ HideableColumnService,
+ StateDebouncer,
+ StateProvider,
+ ColumnToggleButtonsService,
+ TableSizeService,
+ DisplayModeService,
+ ],
+ host: { '[class.datagrid-host]': 'true' },
})
export class ClrDatagrid implements AfterContentInit, AfterViewInit, OnDestroy {
- constructor(
- private columnService: HideableColumnService,
- private organizer: DatagridRenderOrganizer,
- public items: Items,
- public expandableRows: ExpandableRowsCount,
- public selection: Selection,
- public rowActionService: RowActionService,
- private stateProvider: StateProvider,
- private displayMode: DisplayModeService,
- private renderer: Renderer2,
- private el: ElementRef,
- public commonStrings: ClrCommonStrings
- ) {}
+ constructor(
+ private columnService: HideableColumnService,
+ private organizer: DatagridRenderOrganizer,
+ public items: Items,
+ public expandableRows: ExpandableRowsCount,
+ public selection: Selection,
+ public rowActionService: RowActionService,
+ private stateProvider: StateProvider,
+ private displayMode: DisplayModeService,
+ private renderer: Renderer2,
+ private el: ElementRef,
+ public commonStrings: ClrCommonStrings
+ ) { }
- /* reference to the enum so that template can access */
- public SELECTION_TYPE = SelectionType;
+ /* reference to the enum so that template can access */
+ public SELECTION_TYPE = SelectionType;
- /**
- * Freezes the datagrid while data is loading
- */
- public get loading(): boolean {
- return this.items.loading;
- }
+ /**
+ * Freezes the datagrid while data is loading
+ */
+ public get loading(): boolean {
+ return this.items.loading;
+ }
- @Input('clrDgLoading')
- public set loading(value: boolean) {
- this.items.loading = value;
- }
+ @Input('clrDgLoading')
+ public set loading(value: boolean) {
+ this.items.loading = value;
+ }
- /**
- * Output emitted whenever the data needs to be refreshed, based on user action or external ones
- */
- @Output('clrDgStateChange') public stateChange = new EventEmitter>(false);
+ /**
+ * Output emitted whenever the data needs to be refreshed, based on user action or external ones
+ */
+ @Output('clrDgStateChange') public stateChange = new EventEmitter>(false);
- @Output('clrDgRefresh') public refresh = this.stateChange;
+ @Output('clrDgRefresh') public refresh = this.stateChange;
- @Input('clrDgState')
- public set state(value: ClrDatagridStateInterface) {
- // ignore the initial undefined value
- if (value) {
- this.stateProvider.state = value;
+ @Input('clrDgState')
+ public set state(value: ClrDatagridStateInterface) {
+ // ignore the initial undefined value
+ if (value) {
+ this.stateProvider.state = value;
+ }
}
- }
- /**
- * Public method to re-trigger the computation of displayed items manually
- */
- public dataChanged() {
- this.items.refresh();
- }
+ /**
+ * Public method to re-trigger the computation of displayed items manually
+ */
+ public dataChanged() {
+ this.items.refresh();
+ }
- /**
- * We grab the smart iterator from projected content
- */
- @ContentChild(ClrDatagridItems) public iterator: ClrDatagridItems;
+ /**
+ * We grab the smart iterator from projected content
+ */
+ @ContentChild(ClrDatagridItems) public iterator: ClrDatagridItems;
- /**
- * Array of all selected items
- */
- @Input('clrDgSelected')
- set selected(value: T[]) {
- if (value) {
- this.selection.selectionType = SelectionType.Multi;
- } else {
- this.selection.selectionType = SelectionType.None;
+ /**
+ * Array of all selected items
+ */
+ @Input('clrDgSelected')
+ set selected(value: T[]) {
+ if (value) {
+ this.selection.selectionType = SelectionType.Multi;
+ } else {
+ this.selection.selectionType = SelectionType.None;
+ }
+ this.selection.updateCurrent(value, false);
}
- this.selection.updateCurrent(value, false);
- }
- @Output('clrDgSelectedChange') selectedChanged = new EventEmitter(false);
+ @Output('clrDgSelectedChange') selectedChanged = new EventEmitter(false);
- /**
- * Selected item in single-select mode
- */
- @Input('clrDgSingleSelected')
- set singleSelected(value: T) {
- this.selection.selectionType = SelectionType.Single;
- // the clrDgSingleSelected is updated in one of two cases:
- // 1. an explicit value is passed
- // 2. is being set to null or undefined, where previously it had a value
- if (value) {
- this.selection.currentSingle = value;
- } else if (this.selection.currentSingle) {
- this.selection.currentSingle = null;
+ /**
+ * Selected item in single-select mode
+ */
+ @Input('clrDgSingleSelected')
+ set singleSelected(value: T) {
+ this.selection.selectionType = SelectionType.Single;
+ // the clrDgSingleSelected is updated in one of two cases:
+ // 1. an explicit value is passed
+ // 2. is being set to null or undefined, where previously it had a value
+ if (value) {
+ this.selection.currentSingle = value;
+ } else if (this.selection.currentSingle) {
+ this.selection.currentSingle = null;
+ }
}
- }
- @Output('clrDgSingleSelectedChange') singleSelectedChanged = new EventEmitter(false);
+ @Output('clrDgSingleSelectedChange') singleSelectedChanged = new EventEmitter(false);
- /**
- * Selection/Deselection on row click mode
- */
- @Input('clrDgRowSelection')
- set rowSelectionMode(value: boolean) {
- this.selection.rowSelectionMode = value;
- }
-
- /**
- * Indicates if all currently displayed items are selected
- */
- public get allSelected() {
- return this.selection.isAllSelected();
- }
+ /**
+ * Selection/Deselection on row click mode
+ */
+ @Input('clrDgRowSelection')
+ set rowSelectionMode(value: boolean) {
+ this.selection.rowSelectionMode = value;
+ }
- /**
- * Selects/deselects all currently displayed items
- * @param value
- */
- public set allSelected(value: boolean) {
- /*
- * This is a setter but we ignore the value.
- * It's strange, but it lets us have an indeterminate state where only
- * some of the items are selected.
- */
- this.selection.toggleAll();
- }
+ /**
+ * Indicates if all currently displayed items are selected
+ */
+ public get allSelected() {
+ return this.selection.isAllSelected();
+ }
- /**
- * Custom placeholder detection
- */
- @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder;
+ /**
+ * Selects/deselects all currently displayed items
+ * @param value
+ */
+ public set allSelected(value: boolean) {
+ /*
+ * This is a setter but we ignore the value.
+ * It's strange, but it lets us have an indeterminate state where only
+ * some of the items are selected.
+ */
+ this.selection.toggleAll();
+ }
- /**
- * Hideable Column data source / detection.
- */
- @ContentChildren(ClrDatagridColumn) public columns: QueryList>;
+ /**
+ * Custom placeholder detection
+ */
+ @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder;
- /**
- * When the datagrid is user-managed without the smart iterator, we get the items displayed
- * by querying the projected content. This is needed to keep track of the models currently
- * displayed, typically for selection.
- */
+ /**
+ * Hideable Column data source / detection.
+ */
+ @ContentChildren(ClrDatagridColumn) public columns: QueryList>;
- @ContentChildren(ClrDatagridRow) rows: QueryList>;
- @ViewChild('scrollableColumns', { read: ViewContainerRef })
- scrollableColumns: ViewContainerRef;
+ /**
+ * When the datagrid is user-managed without the smart iterator, we get the items displayed
+ * by querying the projected content. This is needed to keep track of the models currently
+ * displayed, typically for selection.
+ */
- ngAfterContentInit() {
- if (!this.items.smart) {
- this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
- }
+ @ContentChildren(ClrDatagridRow) rows: QueryList>;
+ @ViewChild('scrollableColumns', { read: ViewContainerRef })
+ scrollableColumns: ViewContainerRef;
- this._subscriptions.push(
- this.rows.changes.subscribe(() => {
+ ngAfterContentInit() {
if (!this.items.smart) {
- this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
+ this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
}
- this.rows.forEach(row => {
- this._displayedRows.insert(row._view);
- });
- })
- );
- this._subscriptions.push(
- this.columns.changes.subscribe((columns: ClrDatagridColumn[]) => {
- this.columnService.updateColumnList(this.columns.map(col => col.hideable));
- })
- );
+ this._subscriptions.push(
+ this.rows.changes.subscribe(() => {
+ if (!this.items.smart) {
+ this.items.all = this.rows.map((row: ClrDatagridRow) => row.item);
+ }
+ this.rows.forEach(row => {
+ this._displayedRows.insert(row._view);
+ });
+ })
+ );
- // Get ColumnService ready for HideableColumns.
- this.columnService.updateColumnList(this.columns.map(col => col.hideable));
- }
+ this._subscriptions.push(
+ this.columns.changes.subscribe((columns: ClrDatagridColumn[]) => {
+ this.columnService.updateColumnList(this.columns.map(col => col.hideable));
+ })
+ );
- /**
- * Our setup happens in the view of some of our components, so we wait for it to be done before starting
- */
- ngAfterViewInit() {
- // TODO: determine if we can get rid of provider wiring in view init so that subscriptions can be done earlier
- this.stateChange.emit(this.stateProvider.state);
- this._subscriptions.push(this.stateProvider.change.subscribe(state => this.stateChange.emit(state)));
- this._subscriptions.push(
- this.selection.change.subscribe(s => {
- if (this.selection.selectionType === SelectionType.Single) {
- this.singleSelectedChanged.emit(s);
- } else if (this.selection.selectionType === SelectionType.Multi) {
- this.selectedChanged.emit(s);
- }
- })
- );
- // A subscription that listens for displayMode changes on the datagrid
- this.displayMode.view.subscribe(viewChange => {
- // Remove any projected columns from the projectedDisplayColumns container
- for (let i = this._projectedDisplayColumns.length; i > 0; i--) {
- this._projectedDisplayColumns.detach();
- }
- // Remove any projected columns from the projectedCalculationColumns container
- for (let i = this._projectedCalculationColumns.length; i > 0; i--) {
- this._projectedCalculationColumns.detach();
- }
- // Remove any projected rows from the calculationRows container
- for (let i = this._calculationRows.length; i > 0; i--) {
- this._calculationRows.detach();
- }
- // Remove any projected rows from the displayedRows container
- for (let i = this._displayedRows.length; i > 0; i--) {
- this._displayedRows.detach();
- }
- if (viewChange === DatagridDisplayMode.DISPLAY) {
- // Set state, style for the datagrid to DISPLAY and insert row & columns into containers
- this.renderer.removeClass(this.el.nativeElement, 'datagrid-calculate-mode');
- this.columns.forEach(column => {
- this._projectedDisplayColumns.insert(column._view);
- });
- this.rows.forEach(row => {
- this._displayedRows.insert(row._view);
- });
- } else {
- // Set state, style for the datagrid to CALCULATE and insert row & columns into containers
- this.renderer.addClass(this.el.nativeElement, 'datagrid-calculate-mode');
- this.columns.forEach(column => {
- this._projectedCalculationColumns.insert(column._view);
- });
- this.rows.forEach(row => {
- this._calculationRows.insert(row._view);
+ // Get ColumnService ready for HideableColumns.
+ this.columnService.updateColumnList(this.columns.map(col => col.hideable));
+ }
+
+ /**
+ * Our setup happens in the view of some of our components, so we wait for it to be done before starting
+ */
+ ngAfterViewInit() {
+ // TODO: determine if we can get rid of provider wiring in view init so that subscriptions can be done earlier
+ this.stateChange.emit(this.stateProvider.state);
+ this._subscriptions.push(this.stateProvider.change.subscribe(state => {
+ if (!state.page) {
+ return;
+ }
+ this.stateChange.emit(state);
+ }));
+ this._subscriptions.push(
+ this.selection.change.subscribe(s => {
+ if (this.selection.selectionType === SelectionType.Single) {
+ this.singleSelectedChanged.emit(s);
+ } else if (this.selection.selectionType === SelectionType.Multi) {
+ this.selectedChanged.emit(s);
+ }
+ })
+ );
+ // A subscription that listens for displayMode changes on the datagrid
+ this.displayMode.view.subscribe(viewChange => {
+ // Remove any projected columns from the projectedDisplayColumns container
+ for (let i = this._projectedDisplayColumns.length; i > 0; i--) {
+ this._projectedDisplayColumns.detach();
+ }
+ // Remove any projected columns from the projectedCalculationColumns container
+ for (let i = this._projectedCalculationColumns.length; i > 0; i--) {
+ this._projectedCalculationColumns.detach();
+ }
+ // Remove any projected rows from the calculationRows container
+ for (let i = this._calculationRows.length; i > 0; i--) {
+ this._calculationRows.detach();
+ }
+ // Remove any projected rows from the displayedRows container
+ for (let i = this._displayedRows.length; i > 0; i--) {
+ this._displayedRows.detach();
+ }
+ if (viewChange === DatagridDisplayMode.DISPLAY) {
+ // Set state, style for the datagrid to DISPLAY and insert row & columns into containers
+ this.renderer.removeClass(this.el.nativeElement, 'datagrid-calculate-mode');
+ this.columns.forEach(column => {
+ this._projectedDisplayColumns.insert(column._view);
+ });
+ this.rows.forEach(row => {
+ this._displayedRows.insert(row._view);
+ });
+ } else {
+ // Set state, style for the datagrid to CALCULATE and insert row & columns into containers
+ this.renderer.addClass(this.el.nativeElement, 'datagrid-calculate-mode');
+ this.columns.forEach(column => {
+ this._projectedCalculationColumns.insert(column._view);
+ });
+ this.rows.forEach(row => {
+ this._calculationRows.insert(row._view);
+ });
+ }
});
- }
- });
- }
+ }
- /**
- * Subscriptions to all the services and queries changes
- */
- private _subscriptions: Subscription[] = [];
+ /**
+ * Subscriptions to all the services and queries changes
+ */
+ private _subscriptions: Subscription[] = [];
- ngOnDestroy() {
- this._subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
- }
+ ngOnDestroy() {
+ this._subscriptions.forEach((sub: Subscription) => sub.unsubscribe());
+ }
- resize(): void {
- this.organizer.resize();
- }
+ resize(): void {
+ this.organizer.resize();
+ }
- @ViewChild('projectedDisplayColumns', { read: ViewContainerRef })
- _projectedDisplayColumns: ViewContainerRef;
- @ViewChild('projectedCalculationColumns', { read: ViewContainerRef })
- _projectedCalculationColumns: ViewContainerRef;
- @ViewChild('displayedRows', { read: ViewContainerRef })
- _displayedRows: ViewContainerRef;
- @ViewChild('calculationRows', { read: ViewContainerRef })
- _calculationRows: ViewContainerRef;
+ @ViewChild('projectedDisplayColumns', { read: ViewContainerRef })
+ _projectedDisplayColumns: ViewContainerRef;
+ @ViewChild('projectedCalculationColumns', { read: ViewContainerRef })
+ _projectedCalculationColumns: ViewContainerRef;
+ @ViewChild('displayedRows', { read: ViewContainerRef })
+ _displayedRows: ViewContainerRef;
+ @ViewChild('calculationRows', { read: ViewContainerRef })
+ _calculationRows: ViewContainerRef;
}
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index bc1dff0ea2..4998ae0ecd 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -68,7 +68,13 @@ export class FiltersProvider {
return;
}
subscription.unsubscribe();
- const index = this._all.findIndex(value => value.filter.compatibleToState(filter.filterState));
+ const index = this._all.findIndex(value => {
+ if (value.filter.compatibleToState) {
+ return value.filter.compatibleToState(filter.filterState);
+ } else {
+ return false;
+ }
+ });
if (index !== -1) {
this._all.splice(index, 1);
}
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.css b/src/dev/src/app/datagrid/binding-state/binding-state.component.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.html b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
new file mode 100644
index 0000000000..2bb1722ba9
--- /dev/null
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
@@ -0,0 +1,79 @@
+
+
+Server-driven datagrid
+
+
+ When dealing with large amounts of data or heavy processing, a datagrid often has access the currently displayed
+ data only,
+ requesting only the necessary pieces from the server. This is a very common case that we fully support.
+
+
+ We expose events and hooks on all parts of the datagrid to make sure you can trigger any requests you need based on
+ precise
+ user actions. But an important thing to note is that when the server does handle pagination, it needs to also deal
+ with
+ sorting and filtering, because all three are tightly coupled. In light of this, we decided to expose a single
+ global output
+ (clrDgRefresh)
that emits the current "state" of the datagrid whenever it changes due
+ to a user action or an external one. This state
+ has the following format:
+
+
+
+ It contains all the information you need to send to the server in order to get the slice of data currently
+ displayed. It
+ even contains redundant information (
+ page.to
and
+ page.size
for instance), to make sure you have access to the most practical for you
+ without extra processing.
+
+
+ One important thing to note is that since you don't have all the data available at once, you cannot use the smart
+ iterator
+ *clrDgItems
: it would sort, filter and paginate a subset of the data that has already
+ gone through all that. So all you need to do
+ is to go back to a simple
+ *ngFor
, which we support.
+
+
+ Finally, since server calls are involved, we need some way of notifying the user that his action has been
+ acknowledged and
+ that we are currently working on it. To this effect, we provide an input
+ [clrDgLoading]
that you can use to display the datagrid in a loading state, while
+ fetching data.
+
+
+
+ Here is an example showcasing all this with a fake server latency of 500ms. It also demonstrates how to adapt the
+ state we
+ emit when it does not match exactly your server's API: in this example, the filters are re-formatted from an array
+ to a
+ map.
+
+
+
+ User ID
+ Name
+ Creation date
+ Pokemon
+ Favorite color
+
+
+ {{user.id}}
+ {{user.name}}
+ {{user.creation | date}}
+ {{user.pokemon.name}}
+
+
+
+
+
+
+ {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{total}} users
+
+
+
\ No newline at end of file
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts
new file mode 100644
index 0000000000..fedf8dccba
--- /dev/null
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BindingStateComponent } from './binding-state.component';
+
+describe('BindingStateComponent', () => {
+ let component: BindingStateComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ BindingStateComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BindingStateComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
new file mode 100644
index 0000000000..a5280fd9c5
--- /dev/null
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
@@ -0,0 +1,50 @@
+import { Component, OnInit } from '@angular/core';
+import { Inventory, FetchResult } from '../inventory/inventory';
+import { User } from '../inventory/user';
+import { ClrDatagridStateInterface } from '@clr/angular';
+
+@Component({
+ selector: 'app-binding-state',
+ providers: [Inventory],
+ templateUrl: './binding-state.component.html',
+ styleUrls: ['./binding-state.component.css']
+})
+export class BindingStateComponent {
+
+ users: User[];
+ total: number;
+ loading: boolean = true;
+ state: ClrDatagridStateInterface =
+ {
+ page: { from: 0, to: 8, size: 10 },
+ sort: { by: "pokemon", reverse: false },
+ filters: [{ property: "name", value: "sampleName" }, { property: "creation", value: "sampleDate" }]
+ };
+ constructor(private inventory: Inventory) {
+ inventory.size = 103;
+ this.inventory.latency = 500;
+ inventory.reset();
+ }
+
+ refresh() {
+ this.loading = true;
+ const filters: { [prop: string]: any[] } = {};
+ if (this.state.filters) {
+ for (const filter of this.state.filters) {
+ const { property, value } = <{ property: string; value: string }>filter;
+ filters[property] = [value];
+ }
+
+ this.inventory
+ .filter(filters)
+ .sort(<{ by: string; reverse: boolean }>this.state.sort)
+ .fetch(this.state.page.from, this.state.page.size)
+ .then((result: FetchResult) => {
+ this.users = result.users;
+ this.total = result.length;
+ this.loading = false;
+ });
+ }
+ }
+
+}
diff --git a/src/dev/src/app/datagrid/datagrid.demo.html b/src/dev/src/app/datagrid/datagrid.demo.html
index b363a2f76a..55f62d82af 100644
--- a/src/dev/src/app/datagrid/datagrid.demo.html
+++ b/src/dev/src/app/datagrid/datagrid.demo.html
@@ -33,6 +33,7 @@ Datagrid
Asynchronous Test Cases
Hide-show columns demo
Pinned Columns
+ Binding State
diff --git a/src/dev/src/app/datagrid/datagrid.demo.module.ts b/src/dev/src/app/datagrid/datagrid.demo.module.ts
index b19a0e435b..b7b6673724 100644
--- a/src/dev/src/app/datagrid/datagrid.demo.module.ts
+++ b/src/dev/src/app/datagrid/datagrid.demo.module.ts
@@ -39,6 +39,7 @@ import { DatagridStringFilteringDemo } from './string-filtering/string-filtering
import { DatagridTestCasesAsyncDemo } from './test-cases-async/test-cases-async';
import { DatagridTestCasesDemo } from './test-cases/test-cases';
import { ColorFilter } from './utils/color-filter';
+import { BindingStateComponent } from './binding-state/binding-state.component';
@NgModule({
imports: [CommonModule, FormsModule, ClarityModule, ROUTING, UtilsDemoModule],
@@ -71,6 +72,7 @@ import { ColorFilter } from './utils/color-filter';
DatagridKitchenSinkDemo,
ColorFilter,
DetailWrapper,
+ BindingStateComponent,
],
exports: [
DatagridDemo,
diff --git a/src/dev/src/app/datagrid/datagrid.demo.routing.ts b/src/dev/src/app/datagrid/datagrid.demo.routing.ts
index 89e997f5d9..17d77eb5c6 100644
--- a/src/dev/src/app/datagrid/datagrid.demo.routing.ts
+++ b/src/dev/src/app/datagrid/datagrid.demo.routing.ts
@@ -32,6 +32,7 @@ import { DatagridSortingDemo } from './sorting/sorting';
import { DatagridStringFilteringDemo } from './string-filtering/string-filtering';
import { DatagridTestCasesAsyncDemo } from './test-cases-async/test-cases-async';
import { DatagridTestCasesDemo } from './test-cases/test-cases';
+import { BindingStateComponent } from './binding-state/binding-state.component';
const ROUTES: Routes = [
{
@@ -64,6 +65,7 @@ const ROUTES: Routes = [
{ path: 'test-cases', component: DatagridTestCasesDemo },
{ path: 'test-cases-async', component: DatagridTestCasesAsyncDemo },
{ path: 'hide-show', component: DatagridHideShowDemo },
+ { path: 'binding-state', component: BindingStateComponent }
],
},
];
diff --git a/src/dev/src/app/datagrid/server-driven/server-driven.html b/src/dev/src/app/datagrid/server-driven/server-driven.html
index 099d1e37b0..e1cc592029 100644
--- a/src/dev/src/app/datagrid/server-driven/server-driven.html
+++ b/src/dev/src/app/datagrid/server-driven/server-driven.html
@@ -42,7 +42,7 @@ Server-driven datagrid
map.
-
+
User ID
Name
Creation date
diff --git a/src/dev/src/app/datagrid/server-driven/server-driven.ts b/src/dev/src/app/datagrid/server-driven/server-driven.ts
index f8678f9387..33adea30ea 100644
--- a/src/dev/src/app/datagrid/server-driven/server-driven.ts
+++ b/src/dev/src/app/datagrid/server-driven/server-driven.ts
@@ -18,31 +18,26 @@ export class DatagridServerDrivenDemo {
users: User[];
total: number;
loading: boolean = true;
- state: ClrDatagridStateInterface =
- {
- page: { from: 0, to: 8, size: 10 },
- sort: { by: "pokemon", reverse: false },
- filters: [{ property: "name", value: "sampleName" },{ property: "creation", value: "sampleDate" }]
- };
+
constructor(private inventory: Inventory) {
inventory.size = 103;
this.inventory.latency = 500;
inventory.reset();
}
- refresh() {
+ refresh(state: ClrDatagridStateInterface) {
this.loading = true;
const filters: { [prop: string]: any[] } = {};
- if (this.state.filters) {
- for (const filter of this.state.filters) {
+ if (state.filters) {
+ for (const filter of state.filters) {
const { property, value } = <{ property: string; value: string }>filter;
filters[property] = [value];
}
}
this.inventory
.filter(filters)
- .sort(<{ by: string; reverse: boolean }>this.state.sort)
- .fetch(this.state.page.from, this.state.page.size)
+ .sort(<{ by: string; reverse: boolean }>state.sort)
+ .fetch(state.page.from, state.page.size)
.then((result: FetchResult) => {
this.users = result.users;
this.total = result.length;
From ab972103ef94e231412b36b275f4b8091736bfa2 Mon Sep 17 00:00:00 2001
From: speti43
Date: Thu, 6 Dec 2018 13:28:28 +0100
Subject: [PATCH 08/14] debugger; remove
---
src/clr-angular/data/datagrid/providers/filters.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index 1a267ea0d9..4998ae0ecd 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -118,7 +118,6 @@ export class FiltersProvider {
// filtering may change the page number such that current page number doesn't exist in the filtered dataset.
// So here we always set the current page to 1 so that it'll fetch first page's data with the given filter.
this._page.current = 1;
- debugger;
this._change.next(filters);
this.stateDebouncer.changeDone();
}
From 5c1ec9094d963d1e64f833848522e7cd900de06d Mon Sep 17 00:00:00 2001
From: speti43
Date: Thu, 6 Dec 2018 16:22:50 +0100
Subject: [PATCH 09/14] Color filter demo in progress
---
.../color.filter.state.interface.ts | 11 ++++
.../datagrid/interfaces/state.interface.ts | 3 +-
.../data/datagrid/providers/state.provider.ts | 56 +++++++++++++++++--
.../binding-state.component.html | 7 ++-
.../binding-state/binding-state.component.ts | 43 ++++++++------
.../src/app/datagrid/utils/color-filter.ts | 34 ++++++++++-
6 files changed, 126 insertions(+), 28 deletions(-)
create mode 100644 src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts
diff --git a/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts
new file mode 100644
index 0000000000..7364adb25d
--- /dev/null
+++ b/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts
@@ -0,0 +1,11 @@
+import { FilterStateInterface } from "./filter.state.interface";
+
+/*
+ * Copyright (c) 2016-2018 VMware, Inc. All Rights Reserved.
+ * This software is released under MIT license.
+ * The full license information can be found in LICENSE in the root directory of this project.
+ */
+
+export interface ColorFilterStateInterface extends FilterStateInterface {
+ selectedColors: { [color: string]: boolean };
+}
diff --git a/src/clr-angular/data/datagrid/interfaces/state.interface.ts b/src/clr-angular/data/datagrid/interfaces/state.interface.ts
index 5377335282..3e017afe19 100644
--- a/src/clr-angular/data/datagrid/interfaces/state.interface.ts
+++ b/src/clr-angular/data/datagrid/interfaces/state.interface.ts
@@ -5,9 +5,10 @@
*/
import { ClrDatagridComparatorInterface } from './comparator.interface';
import { ClrDatagridFilterInterface } from './filter.interface';
+import { SerializableFilter } from './serializable.filter.interface';
export interface ClrDatagridStateInterface {
page?: { from?: number; to?: number; size?: number };
sort?: { by: string | ClrDatagridComparatorInterface; reverse: boolean };
- filters?: ({ property: string; value: string } | ClrDatagridFilterInterface)[];
+ filters?: ({ property: string; value: string } | ClrDatagridFilterInterface | any)[];
}
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index 39118a50c8..43ded0cf67 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -61,12 +61,14 @@ export class StateProvider {
if (activeFilters.length > 0) {
state.filters = [];
for (const filter of activeFilters) {
- if (filter.filterState.type === 'BuiltinStringFilter') {
+ if (filter.filterState && filter.filterState.type === 'BuiltinStringFilter') {
const stringFilterState = filter.filterState;
state.filters.push({
property: stringFilterState.property,
value: stringFilterState.value,
});
+ } else if (filter.filterState && filter.filterState.type) {
+ state.filters.push(filter);
}
}
}
@@ -76,6 +78,8 @@ export class StateProvider {
set state(state: ClrDatagridStateInterface) {
if (this.sameAsPreviousState(state)) {
return;
+ } else {
+ this._prevState = state;
}
this.debouncer.changeStart();
if (state.page) {
@@ -122,10 +126,52 @@ export class StateProvider {
}
sameAsPreviousState(state: ClrDatagridStateInterface) {
- const sameAsPreviousState = JSON.stringify(this._prevState) === JSON.stringify(state);
- if (!sameAsPreviousState) {
- this._prevState = state;
+ if (!this._prevState) {
+ return false;
+ }
+
+ if (!this.propertiesAreSame(state.page, this._prevState.page)) {
+ return false;
+ }
+
+ if (!this.propertiesAreSame(state.sort, this._prevState.sort)) {
+ return false;
+ }
+
+ if (!this.filtersCountAreSame(state)) {
+ return false;
+ }
+
+ if (state.filters && this._prevState.filters) {
+ for (let i; i < state.filters.length; i++) {
+ if (!this.propertiesAreSame(state.filters[i], this._prevState.filters[i])) {
+ return false;
+ }
+ }
}
- return sameAsPreviousState;
+
+ return true;
+ }
+
+ propertiesAreSame(object1: any, object2: any) {
+ for (let key in object1) {
+ if (object1[key] !== object2[key]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ filtersCountAreSame(state: ClrDatagridStateInterface) {
+ if (!state.filters && !this._prevState.filters) {
+ return true;
+ }
+
+ if (!state.filters && this._prevState.filters || state.filters && !this._prevState.filters) {
+ return false;
+ }
+
+ return state.filters.length === this._prevState.filters.length;
}
}
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.html b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
index 2bb1722ba9..050eb8b6de 100644
--- a/src/dev/src/app/datagrid/binding-state/binding-state.component.html
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
@@ -60,7 +60,12 @@ Server-driven datagrid
Name
Creation date
Pokemon
- Favorite color
+ Favorite color
+
+
+
+
+
{{user.id}}
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
index a5280fd9c5..480d7338b7 100644
--- a/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Inventory, FetchResult } from '../inventory/inventory';
import { User } from '../inventory/user';
import { ClrDatagridStateInterface } from '@clr/angular';
+import { ColorFilter } from '../utils/color-filter';
@Component({
selector: 'app-binding-state',
@@ -14,13 +15,19 @@ export class BindingStateComponent {
users: User[];
total: number;
loading: boolean = true;
- state: ClrDatagridStateInterface =
- {
- page: { from: 0, to: 8, size: 10 },
- sort: { by: "pokemon", reverse: false },
- filters: [{ property: "name", value: "sampleName" }, { property: "creation", value: "sampleDate" }]
- };
+ colorFilter = new ColorFilter();
+ state: ClrDatagridStateInterface;
constructor(private inventory: Inventory) {
+ this.state =
+ {
+ page: { from: 0, to: 8, size: 10 },
+ sort: { by: "pokemon", reverse: false },
+ filters: [
+ { property: "name", value: "sampleName" },
+ { property: "creation", value: "sampleDate" }]
+ };
+ this.colorFilter.toggleColor('Indigo');
+ this.state.filters.push(this.colorFilter);
inventory.size = 103;
this.inventory.latency = 500;
inventory.reset();
@@ -32,19 +39,19 @@ export class BindingStateComponent {
if (this.state.filters) {
for (const filter of this.state.filters) {
const { property, value } = <{ property: string; value: string }>filter;
- filters[property] = [value];
+ if (property && value) {
+ filters[property] = [value];
+ }
}
-
- this.inventory
- .filter(filters)
- .sort(<{ by: string; reverse: boolean }>this.state.sort)
- .fetch(this.state.page.from, this.state.page.size)
- .then((result: FetchResult) => {
- this.users = result.users;
- this.total = result.length;
- this.loading = false;
- });
}
+ this.inventory
+ .filter(filters)
+ .sort(<{ by: string; reverse: boolean }>this.state.sort)
+ .fetch(this.state.page.from, this.state.page.size)
+ .then((result: FetchResult) => {
+ this.users = result.users;
+ this.total = result.length;
+ this.loading = false;
+ });
}
-
}
diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts
index 6187e04fc3..a6f39bf283 100644
--- a/src/dev/src/app/datagrid/utils/color-filter.ts
+++ b/src/dev/src/app/datagrid/utils/color-filter.ts
@@ -4,9 +4,10 @@
* The full license information can be found in LICENSE in the root directory of this project.
*/
import { Component, EventEmitter } from '@angular/core';
-import { ClrDatagridFilterInterface } from '@clr/angular';
import { User } from '../inventory/user';
import { COLORS } from '../inventory/values';
+import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/interfaces/serializable.filter.interface';
+import { ColorFilterStateInterface } from '../../../../../clr-angular/data/datagrid/interfaces/color.filter.state.interface';
@Component({
selector: 'clr-datagrid-color-filter-demo',
@@ -17,12 +18,38 @@ import { COLORS } from '../inventory/values';
[class.color-selected]="selectedColors[color]">`,
styleUrls: ['../datagrid.demo.scss'],
})
-export class ColorFilter implements ClrDatagridFilterInterface {
+export class ColorFilter implements SerializableFilter {
allColors = COLORS;
selectedColors: { [color: string]: boolean } = {};
nbColors = 0;
-
changes: EventEmitter = new EventEmitter(false);
+ private _state: ColorFilterStateInterface;
+
+ constructor() {
+ this._state =
+ {
+ type: 'ColorFilter',
+ selectedColors: {}
+ };
+
+ }
+ public get filterState(): ColorFilterStateInterface {
+ return this._state;
+ }
+
+ public set filterState(state: ColorFilterStateInterface) {
+ this._state = state;
+ }
+
+ compatibleToState(state: ColorFilterStateInterface) {
+ for (let key in state.selectedColors) {
+ if (state[key] === this.filterState.selectedColors[key]) {
+ return false;
+ }
+
+ return true;
+ }
+ }
listSelected(): string[] {
const list: string[] = [];
@@ -37,6 +64,7 @@ export class ColorFilter implements ClrDatagridFilterInterface {
toggleColor(color: string) {
this.selectedColors[color] = !this.selectedColors[color];
this.selectedColors[color] ? this.nbColors++ : this.nbColors--;
+ this.filterState.selectedColors = this.selectedColors;
this.changes.emit(true);
}
From f55de8f67e7af8b9d808fff242c500728bbc8700 Mon Sep 17 00:00:00 2001
From: speti43
Date: Fri, 7 Dec 2018 17:31:46 +0100
Subject: [PATCH 10/14] Custom filter state binding fix
---
.../color.filter.state.interface.ts | 4 +-
.../data/datagrid/providers/filters.ts | 13 +++++
.../data/datagrid/providers/state.provider.ts | 8 +--
.../utils/datagrid-filter-registrar.ts | 7 +--
.../binding-state.component.html | 50 ++-----------------
.../binding-state/binding-state.component.ts | 3 +-
.../src/app/datagrid/utils/color-filter.ts | 20 ++++----
7 files changed, 40 insertions(+), 65 deletions(-)
diff --git a/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts
index 7364adb25d..794869d5ae 100644
--- a/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts
+++ b/src/clr-angular/data/datagrid/interfaces/color.filter.state.interface.ts
@@ -6,6 +6,8 @@ import { FilterStateInterface } from "./filter.state.interface";
* The full license information can be found in LICENSE in the root directory of this project.
*/
-export interface ColorFilterStateInterface extends FilterStateInterface {
+export interface ColorFilterStateInterface extends FilterStateInterface {
+ id: string;
+ allColors: string[];
selectedColors: { [color: string]: boolean };
}
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index 4998ae0ecd..f67300a972 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -57,6 +57,19 @@ export class FiltersProvider {
return ret;
}
+ /**
+ * Returns a list of all filters
+ */
+ public getFilters(): SerializableFilter[] {
+ const ret: SerializableFilter[] = [];
+ for (const { filter } of this._all) {
+ if (filter) {
+ ret.push(filter);
+ }
+ }
+ return ret;
+ }
+
/**
* Registers a filter, and returns a deregistration function
*/
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index 43ded0cf67..df684d6d71 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -100,7 +100,7 @@ export class StateProvider {
}
}
if (state.filters) {
- const activeFilters = this.filters.getActiveFilters();
+ const gridFilters = this.filters.getFilters();
for (const filter of state.filters) {
let filterObject: SerializableFilter;
if (filter.hasOwnProperty('property') && filter.hasOwnProperty('value')) {
@@ -112,14 +112,14 @@ export class StateProvider {
} else {
filterObject = filter as SerializableFilter;
}
- const existing = activeFilters.findIndex(value => value.compatibleToState(filterObject.filterState));
+ const existing = gridFilters.findIndex(value => value.compatibleToState(filterObject.filterState));
if (existing !== -1) {
- activeFilters.splice(existing, 1);
+ gridFilters.splice(existing, 1);
} else {
this.filters.add(filterObject);
}
}
- activeFilters.forEach(filter => this.filters.remove(filter));
+ gridFilters.forEach(filter => this.filters.remove(filter));
}
this.debouncer.changeDone();
diff --git a/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts b/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
index 19ecd87732..c9903f93b8 100644
--- a/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
+++ b/src/clr-angular/data/datagrid/utils/datagrid-filter-registrar.ts
@@ -25,10 +25,11 @@ export abstract class DatagridFilterRegistrar
} else if (filter) {
const existingFilter = this.filters.getRegisteredFilter(filter);
if (existingFilter) {
- this.registered = existingFilter;
- } else {
- this.registered = this.filters.add(filter);
+ filter.filterState = existingFilter.filter.filterState;
+ this.filters.remove(existingFilter.filter);
}
+ this.registered = this.filters.add(filter);
+
}
}
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.html b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
index 050eb8b6de..f1e3d8beb3 100644
--- a/src/dev/src/app/datagrid/binding-state/binding-state.component.html
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
@@ -4,55 +4,11 @@
~ The full license information can be found in LICENSE in the root directory of this project.
-->
-Server-driven datagrid
+Binding Grid state
- When dealing with large amounts of data or heavy processing, a datagrid often has access the currently displayed
- data only,
- requesting only the necessary pieces from the server. This is a very common case that we fully support.
-
-
- We expose events and hooks on all parts of the datagrid to make sure you can trigger any requests you need based on
- precise
- user actions. But an important thing to note is that when the server does handle pagination, it needs to also deal
- with
- sorting and filtering, because all three are tightly coupled. In light of this, we decided to expose a single
- global output
- (clrDgRefresh)
that emits the current "state" of the datagrid whenever it changes due
- to a user action or an external one. This state
- has the following format:
-
-
-
- It contains all the information you need to send to the server in order to get the slice of data currently
- displayed. It
- even contains redundant information (
- page.to
and
- page.size
for instance), to make sure you have access to the most practical for you
- without extra processing.
-
-
- One important thing to note is that since you don't have all the data available at once, you cannot use the smart
- iterator
- *clrDgItems
: it would sort, filter and paginate a subset of the data that has already
- gone through all that. So all you need to do
- is to go back to a simple
- *ngFor
, which we support.
-
-
- Finally, since server calls are involved, we need some way of notifying the user that his action has been
- acknowledged and
- that we are currently working on it. To this effect, we provide an input
- [clrDgLoading]
that you can use to display the datagrid in a loading state, while
- fetching data.
-
-
-
- Here is an example showcasing all this with a fake server latency of 500ms. It also demonstrates how to adapt the
- state we
- emit when it does not match exactly your server's API: in this example, the filters are re-formatted from an array
- to a
- map.
+ We expose the state field of the grid, which holds the paging, sorting, and filtering state.
+ This is a two-way binding, the user can preset all the settings via this field.
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
index 480d7338b7..29107c0834 100644
--- a/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts
@@ -25,8 +25,9 @@ export class BindingStateComponent {
filters: [
{ property: "name", value: "sampleName" },
{ property: "creation", value: "sampleDate" }]
- };
+ };
this.colorFilter.toggleColor('Indigo');
+ this.colorFilter.toggleColor('Red');
this.state.filters.push(this.colorFilter);
inventory.size = 103;
this.inventory.latency = 500;
diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts
index a6f39bf283..1270ddc3eb 100644
--- a/src/dev/src/app/datagrid/utils/color-filter.ts
+++ b/src/dev/src/app/datagrid/utils/color-filter.ts
@@ -3,7 +3,7 @@
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
-import { Component, EventEmitter } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, ElementRef } from '@angular/core';
import { User } from '../inventory/user';
import { COLORS } from '../inventory/values';
import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/interfaces/serializable.filter.interface';
@@ -18,7 +18,7 @@ import { ColorFilterStateInterface } from '../../../../../clr-angular/data/datag
[class.color-selected]="selectedColors[color]">`,
styleUrls: ['../datagrid.demo.scss'],
})
-export class ColorFilter implements SerializableFilter {
+export class ColorFilter implements SerializableFilter, OnInit {
allColors = COLORS;
selectedColors: { [color: string]: boolean } = {};
nbColors = 0;
@@ -28,27 +28,29 @@ export class ColorFilter implements SerializableFilter {
constructor() {
this._state =
{
+ id: null,
type: 'ColorFilter',
+ allColors: this.allColors,
selectedColors: {}
};
+ }
+ ngOnInit() {
}
+
public get filterState(): ColorFilterStateInterface {
return this._state;
}
public set filterState(state: ColorFilterStateInterface) {
this._state = state;
+ for (let color in state.selectedColors) {
+ this.toggleColor(color);
+ }
}
compatibleToState(state: ColorFilterStateInterface) {
- for (let key in state.selectedColors) {
- if (state[key] === this.filterState.selectedColors[key]) {
- return false;
- }
-
- return true;
- }
+ return state.type === this.filterState.type;
}
listSelected(): string[] {
From f26d7a6571b05144cfc1120b36dad1f1b7f9e035 Mon Sep 17 00:00:00 2001
From: speti43
Date: Thu, 13 Dec 2018 11:54:20 +0100
Subject: [PATCH 11/14] wallabeng's refactor
---
.../filters/datagrid-string-filter-impl.ts | 9 +--
.../serializable.filter.interface.ts | 2 +-
.../data/datagrid/providers/filters.ts | 8 +-
.../data/datagrid/providers/state.provider.ts | 11 ++-
.../binding-state.component.html | 76 +++++++++++--------
.../binding-state/binding-state.component.ts | 49 +++++++-----
.../src/app/datagrid/utils/color-filter.ts | 39 ++++++----
7 files changed, 112 insertions(+), 82 deletions(-)
diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
index 51c996f7f5..b46e8ae5bb 100644
--- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
+++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter-impl.ts
@@ -59,7 +59,7 @@ export class DatagridStringFilterImpl implements SerializableFilter
this._lowerCaseValue = value.toLowerCase().trim();
this._state.value = this.value;
this._changes.next(value);
- }
+ }
}
public get filterState(): StringFilterStateInterface {
@@ -90,10 +90,9 @@ export class DatagridStringFilterImpl implements SerializableFilter
/**
* Compare objects by properties
*/
- public compatibleToState(state: FilterStateInterface): boolean {
- if (state && state.type === this._state.type) {
- const stringState = state;
- return stringState.property === this._state.property;
+ public equals(other: DatagridStringFilterImpl): boolean {
+ if (other && other.filterState.type === this._state.type) {
+ return other.filterState.property === this._state.property;
} else {
return false;
}
diff --git a/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts b/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts
index 82b845bef7..377a368525 100644
--- a/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts
+++ b/src/clr-angular/data/datagrid/interfaces/serializable.filter.interface.ts
@@ -8,6 +8,6 @@ import { ClrDatagridFilterInterface } from './filter.interface';
import { FilterStateInterface } from './filter.state.interface';
export interface SerializableFilter extends ClrDatagridFilterInterface {
- compatibleToState(state: FilterStateInterface): boolean;
+ equals(state: SerializableFilter): boolean;
filterState: FilterStateInterface;
}
diff --git a/src/clr-angular/data/datagrid/providers/filters.ts b/src/clr-angular/data/datagrid/providers/filters.ts
index f67300a972..6f1a5d23a6 100644
--- a/src/clr-angular/data/datagrid/providers/filters.ts
+++ b/src/clr-angular/data/datagrid/providers/filters.ts
@@ -82,8 +82,8 @@ export class FiltersProvider {
}
subscription.unsubscribe();
const index = this._all.findIndex(value => {
- if (value.filter.compatibleToState) {
- return value.filter.compatibleToState(filter.filterState);
+ if (value.filter.equals) {
+ return value.filter.equals(filter);
} else {
return false;
}
@@ -116,14 +116,14 @@ export class FiltersProvider {
}
public remove>(filter: F) {
- const registeredFilter = this._all.find(value => value.filter.compatibleToState(filter.filterState));
+ const registeredFilter = this._all.find(value => value.filter.equals(filter));
if (registeredFilter) {
registeredFilter.unregister();
}
}
public getRegisteredFilter>(filter: F): RegisteredFilter {
- return >this._all.find(f => f.filter.compatibleToState(filter.filterState));
+ return >this._all.find(f => f.filter.equals(filter));
}
private resetPageAndEmitFilterChange(filters: ClrDatagridFilterInterface[]) {
diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts
index df684d6d71..f43e83528b 100644
--- a/src/clr-angular/data/datagrid/providers/state.provider.ts
+++ b/src/clr-angular/data/datagrid/providers/state.provider.ts
@@ -67,7 +67,7 @@ export class StateProvider {
property: stringFilterState.property,
value: stringFilterState.value,
});
- } else if (filter.filterState && filter.filterState.type) {
+ } else {
state.filters.push(filter);
}
}
@@ -112,14 +112,13 @@ export class StateProvider {
} else {
filterObject = filter as SerializableFilter;
}
- const existing = gridFilters.findIndex(value => value.compatibleToState(filterObject.filterState));
+ const existing = gridFilters.findIndex(value => value.equals(filterObject));
if (existing !== -1) {
- gridFilters.splice(existing, 1);
+ gridFilters[existing].filterState = filterObject.filterState;
} else {
this.filters.add(filterObject);
}
}
- gridFilters.forEach(filter => this.filters.remove(filter));
}
this.debouncer.changeDone();
@@ -130,11 +129,11 @@ export class StateProvider {
return false;
}
- if (!this.propertiesAreSame(state.page, this._prevState.page)) {
+ if (state.page && this._prevState.page && !this.propertiesAreSame(state.page, this._prevState.page)) {
return false;
}
- if (!this.propertiesAreSame(state.sort, this._prevState.sort)) {
+ if (state.sort && this._prevState.sort && !this.propertiesAreSame(state.sort, this._prevState.sort)) {
return false;
}
diff --git a/src/dev/src/app/datagrid/binding-state/binding-state.component.html b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
index f1e3d8beb3..58eac82cea 100644
--- a/src/dev/src/app/datagrid/binding-state/binding-state.component.html
+++ b/src/dev/src/app/datagrid/binding-state/binding-state.component.html
@@ -4,37 +4,47 @@
~ The full license information can be found in LICENSE in the root directory of this project.
-->
-