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. --> -

    Binding Grid state

    +

    Binding Grid state

    -

    - 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. -

    - - - 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 +

    + 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. +

    + + + User ID + Name + Creation date + Pokemon + Favorite color + + + + + + Favorite color2 + + + + + + + + {{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.ts b/src/dev/src/app/datagrid/binding-state/binding-state.component.ts index 29107c0834..4b1e9cef7c 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 @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { Inventory, FetchResult } from '../inventory/inventory'; import { User } from '../inventory/user'; import { ClrDatagridStateInterface } from '@clr/angular'; @@ -10,28 +10,43 @@ import { ColorFilter } from '../utils/color-filter'; templateUrl: './binding-state.component.html', styleUrls: ['./binding-state.component.css'] }) -export class BindingStateComponent { +export class BindingStateComponent implements OnInit { users: User[]; total: number; loading: boolean = true; - colorFilter = new ColorFilter(); state: ClrDatagridStateInterface; + + @ViewChild("colorFilter") filter: ColorFilter; + @ViewChild("colorFilter2") filter2: ColorFilter; + 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.colorFilter.toggleColor('Red'); - this.state.filters.push(this.colorFilter); - inventory.size = 103; - this.inventory.latency = 500; - inventory.reset(); + this.inventory.size = 103; + this.inventory.latency = 500; + this.inventory.reset(); + } + + ngOnInit() { + const colorFilter2 = new ColorFilter(); + colorFilter2.setId(this.filter2.id); + colorFilter2.toggleColor('White'); + colorFilter2.toggleColor('Black'); + + const colorFilter = new ColorFilter(); + colorFilter.setId(this.filter.id); + colorFilter.toggleColor('Indigo'); + colorFilter.toggleColor('Red'); + + this.state = + { + page: { from: 0, to: 8, size: 10 }, + sort: { by: "pokemon", reverse: false }, + filters: [ + { property: "name", value: "sampleName" }, + { property: "creation", value: "sampleDate" }, + colorFilter, + colorFilter2] + }; } refresh() { diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts index 1270ddc3eb..689658b496 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, Input, OnInit, ElementRef } from '@angular/core'; +import { Component, EventEmitter } from '@angular/core'; import { User } from '../inventory/user'; import { COLORS } from '../inventory/values'; import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/interfaces/serializable.filter.interface'; @@ -13,44 +13,51 @@ import { ColorFilterStateInterface } from '../../../../../clr-angular/data/datag selector: 'clr-datagrid-color-filter-demo', template: ` `, styleUrls: ['../datagrid.demo.scss'], }) -export class ColorFilter implements SerializableFilter, OnInit { +export class ColorFilter implements SerializableFilter { allColors = COLORS; selectedColors: { [color: string]: boolean } = {}; nbColors = 0; changes: EventEmitter = new EventEmitter(false); - private _state: ColorFilterStateInterface; + private _id: string; constructor() { - this._state = - { - id: null, - type: 'ColorFilter', - allColors: this.allColors, - selectedColors: {} - }; + this._id = Math.random().toString(); } - ngOnInit() { + setId(id: string) { + this._id = id; + } + + get id() { + return this._id; } public get filterState(): ColorFilterStateInterface { - return this._state; + return { + id: this.id, + type: 'ColorFilter', + allColors: this.allColors, + selectedColors: this.selectedColors, + }; } public set filterState(state: ColorFilterStateInterface) { - this._state = state; + this.selectedColors = {} + this.nbColors = 0; for (let color in state.selectedColors) { + if (state.selectedColors[color]) { this.toggleColor(color); + } } } - compatibleToState(state: ColorFilterStateInterface) { - return state.type === this.filterState.type; + equals(other: ColorFilter) { + return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; } listSelected(): string[] { From c4cb18d8d6370132b4f96a9898dfcce78237bd0c Mon Sep 17 00:00:00 2001 From: Daniel Szegvari Date: Tue, 15 Jan 2019 12:24:59 +0100 Subject: [PATCH 12/14] Filter state two-way binding, custom equals Date interval filter, Number interval filter, List filter Signed-off-by: Daniel Szegvari --- package-lock.json | 2597 +++++++++++++---- .../filters/datagrid-string-filter-impl.ts | 148 +- .../date.interval.filter.state.interface.ts | 13 + .../interfaces/list.filter.state.interface.ts | 13 + .../number.interval.filter.state.interface.ts | 13 + .../binding-state.component.html | 117 +- .../binding-state/binding-state.component.ts | 160 +- .../src/app/datagrid/datagrid.demo.module.ts | 6 + .../src/app/datagrid/utils/color-filter.ts | 120 +- .../datagrid/utils/date-interval-filter.ts | 92 + src/dev/src/app/datagrid/utils/list-filter.ts | 70 + .../datagrid/utils/number-interval-filter.ts | 73 + 12 files changed, 2541 insertions(+), 881 deletions(-) create mode 100644 src/clr-angular/data/datagrid/interfaces/date.interval.filter.state.interface.ts create mode 100644 src/clr-angular/data/datagrid/interfaces/list.filter.state.interface.ts create mode 100644 src/clr-angular/data/datagrid/interfaces/number.interval.filter.state.interface.ts create mode 100644 src/dev/src/app/datagrid/utils/date-interval-filter.ts create mode 100644 src/dev/src/app/datagrid/utils/list-filter.ts create mode 100644 src/dev/src/app/datagrid/utils/number-interval-filter.ts diff --git a/package-lock.json b/package-lock.json index a88d7cc31f..0de9b871b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,16 +15,16 @@ } }, "@angular-devkit/build-angular": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.10.3.tgz", - "integrity": "sha512-gV/mLAckS1jaXfuAEaO7p9LqcMrVSmwC2ad85J/unJMqkkxTk7S9TgMe8/A3LIOJ1oZyIKniz/Q30JL092EqRA==", + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.10.7.tgz", + "integrity": "sha512-wjhlMWWkGSSkdwd9elKfeeEgyig/eZGyF2wY5kZmWPBdeK/GfdBLyO15qh4ppRYI2SjyRvzl0tWDOA2Y0hKL0w==", "dev": true, "requires": { - "@angular-devkit/architect": "0.10.3", - "@angular-devkit/build-optimizer": "0.10.3", - "@angular-devkit/build-webpack": "0.10.3", - "@angular-devkit/core": "7.0.3", - "@ngtools/webpack": "7.0.3", + "@angular-devkit/architect": "0.10.7", + "@angular-devkit/build-optimizer": "0.10.7", + "@angular-devkit/build-webpack": "0.10.7", + "@angular-devkit/core": "7.0.7", + "@ngtools/webpack": "7.0.7", "ajv": "6.5.3", "autoprefixer": "9.1.5", "circular-dependency-plugin": "5.0.2", @@ -69,214 +69,49 @@ "webpack-subresource-integrity": "1.1.0-rc.6" }, "dependencies": { - "ajv": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", - "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "autoprefixer": { - "version": "9.1.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.1.5.tgz", - "integrity": "sha512-kk4Zb6RUc58ld7gdosERHMF3DzIYJc2fp5sX46qEsGXQQy5bXsu8qyLjoxuY1NuQ/cJuCYnx99BfjwnRggrYIw==", - "dev": true, - "requires": { - "browserslist": "^4.1.0", - "caniuse-lite": "^1.0.30000884", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.2", - "postcss-value-parser": "^3.2.3" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "postcss": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz", - "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.5.0" - } - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "@angular-devkit/architect": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.10.7.tgz", + "integrity": "sha512-S49LSslNRxIflHzrIrEgK7mGQ7HzETr/FU0fyTbB0vubcmfzMoYTsgYdK7SUz583lovc+UvASoUAhPJI3e35ng==", "dev": true, "requires": { - "punycode": "^2.1.0" - } - }, - "webpack-sources": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", - "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "@angular-devkit/core": "7.0.7", + "rxjs": "6.3.3" } - } - } - }, - "@angular-devkit/build-ng-packagr": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.10.3.tgz", - "integrity": "sha512-6KhavrMdUqplfpuI1kqoMnfOENXclEbFWIQ1Q0gi3Sz8HbyF8Ma7JXEHXlGzn6GvVqajtAjJUq5SLe1o45NSqg==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.10.3", - "@angular-devkit/core": "7.0.3", - "rxjs": "6.3.3", - "semver": "5.5.1" - }, - "dependencies": { - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", - "dev": true - } - } - }, - "@angular-devkit/build-optimizer": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.10.3.tgz", - "integrity": "sha512-NgsS0kdUh/Op9+Kzbq0X6AsTV/BgpVtiF5UxZjdWVQgPPOdur5V9PkpRn9odey+06S/wDTE/UzPmT3qKXTQVHw==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "source-map": "0.5.6", - "typescript": "3.1.3", - "webpack-sources": "1.2.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true }, - "webpack-sources": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", - "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", + "@angular-devkit/build-optimizer": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.10.7.tgz", + "integrity": "sha512-Ztj2U21B8zRO2csQS8mLv/+WKPPLePzaqJDk53Ou2r2HV+kh9GzYvgu1UFeGf/RyEeJi+9KnJGG2wPaeNqDNxg==", "dev": true, "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "loader-utils": "1.1.0", + "source-map": "0.5.6", + "typescript": "3.1.6", + "webpack-sources": "1.2.0" }, "dependencies": { "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true } } - } - } - }, - "@angular-devkit/build-webpack": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.10.3.tgz", - "integrity": "sha512-2uZselfqpxnPbV9d2dRCgl4lJjD1xemNpRijxFIuxeXvadqTPkHA0YuUkX7CTajtwSWy3Cs69StL87b9gYFLSA==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.10.3", - "@angular-devkit/core": "7.0.3", - "rxjs": "6.3.3" - } - }, - "@angular-devkit/core": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.3.tgz", - "integrity": "sha512-Yp0AhTuJbp0VwCHTmUOANrKZNQxTD/F49jPmSCBa/VMYMIoU/sUIiHVNdwzfcFnMoExGoXYah0kutBxgNIG3OA==", - "dev": true, - "requires": { - "ajv": "6.5.3", - "chokidar": "2.0.4", - "fast-json-stable-stringify": "2.0.0", - "rxjs": "6.3.3", - "source-map": "0.7.3" - }, - "dependencies": { + }, + "@angular-devkit/core": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.7.tgz", + "integrity": "sha512-M8tTT9r3nUtWI3YyiyynHIQn+lQQgeKkxVZ+rdxvyvgE3U9+wn0yep5HkFLQETTuJetu9ARRRD94sD2XL3F/3A==", + "dev": true, + "requires": { + "ajv": "6.5.3", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, "ajv": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", @@ -311,6 +146,20 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "autoprefixer": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.1.5.tgz", + "integrity": "sha512-kk4Zb6RUc58ld7gdosERHMF3DzIYJc2fp5sX46qEsGXQQy5bXsu8qyLjoxuY1NuQ/cJuCYnx99BfjwnRggrYIw==", + "dev": true, + "requires": { + "browserslist": "^4.1.0", + "caniuse-lite": "^1.0.30000884", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.2", + "postcss-value-parser": "^3.2.3" + } + }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", @@ -340,6 +189,17 @@ } } }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -518,6 +378,20 @@ } } }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -642,417 +516,1362 @@ "to-regex": "^3.0.2" } }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "postcss": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz", + "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==", "dev": true, "requires": { - "punycode": "^2.1.0" - } - } - } - }, - "@angular-devkit/schematics": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.0.3.tgz", - "integrity": "sha512-FhfPvn5hBCNSj39qLgVaSwa6Zl0qt3Uyw4B9M+Kv01/FFc9xrowldzNT4QRxBjjWbr6DssEP+tYQCPV3Ouwx5w==", - "dev": true, - "requires": { - "@angular-devkit/core": "7.0.3", - "rxjs": "6.3.3" - } - }, - "@angular/animations": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.0.0.tgz", - "integrity": "sha512-IYdryQXdYfPvhJpExLSAr0o9rlUeyVS++a6h/sjqN1dkUt/yJBHLRreuHx8Udvlj2nH70raHJgevk8FwhAkTdA==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/cli": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.0.3.tgz", - "integrity": "sha512-la1Jktl9qGqGLXDy43lTU1D8KkN0zAohD9mOq4ilgNigzpedXFbzsGXJdWv7xVBMG51M6uhw3HWuFprQmbwgIw==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.10.3", - "@angular-devkit/core": "7.0.3", - "@angular-devkit/schematics": "7.0.3", - "@schematics/angular": "7.0.3", - "@schematics/update": "0.10.3", - "inquirer": "6.2.0", - "opn": "5.3.0", - "rxjs": "6.3.3", - "semver": "5.5.1", - "symbol-observable": "1.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.0", - "figures": "^2.0.0", - "lodash": "^4.17.10", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.1.0", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "has-flag": "^3.0.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "typescript": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", + "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "punycode": "^2.1.0" } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "webpack-sources": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", + "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } } } }, - "@angular/common": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.0.0.tgz", - "integrity": "sha512-jp6MA6EOq/a1m+F0c1aZC345pAYYYFpN1m7GMM91JlqkjzJMhyYVk+Bod9xQOEWadcpY+RFudG+jRsPCMO8bvQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/compiler": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.0.0.tgz", - "integrity": "sha512-4fkohfGyG1BEpeYenOartuJmduyZ/R3XQx46hDDiR/9A8/Go4qLGkgr9Bd/JL/gPIR1XAHH9D5ii2sh+28ZEmA==", + "@angular-devkit/build-ng-packagr": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.10.3.tgz", + "integrity": "sha512-6KhavrMdUqplfpuI1kqoMnfOENXclEbFWIQ1Q0gi3Sz8HbyF8Ma7JXEHXlGzn6GvVqajtAjJUq5SLe1o45NSqg==", + "dev": true, "requires": { - "tslib": "^1.9.0" + "@angular-devkit/architect": "0.10.3", + "@angular-devkit/core": "7.0.3", + "rxjs": "6.3.3", + "semver": "5.5.1" + }, + "dependencies": { + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + } } }, - "@angular/compiler-cli": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.0.0.tgz", - "integrity": "sha512-fj5ixB4X3BsUnUukFx+dK5z2KkO7lCx5vlbUT2GOMbGCG43dIH6JKUfy5HbpCodLsJHG0gRgZZuY7/k+pbzS+g==", + "@angular-devkit/build-optimizer": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.10.3.tgz", + "integrity": "sha512-NgsS0kdUh/Op9+Kzbq0X6AsTV/BgpVtiF5UxZjdWVQgPPOdur5V9PkpRn9odey+06S/wDTE/UzPmT3qKXTQVHw==", "dev": true, "requires": { - "canonical-path": "0.0.2", - "chokidar": "^1.4.2", - "convert-source-map": "^1.5.1", - "dependency-graph": "^0.7.2", - "magic-string": "^0.25.0", - "minimist": "^1.2.0", - "reflect-metadata": "^0.1.2", - "shelljs": "^0.8.1", - "source-map": "^0.6.1", - "yargs": "9.0.1" + "loader-utils": "1.1.0", + "source-map": "0.5.6", + "typescript": "3.1.3", + "webpack-sources": "1.2.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "webpack-sources": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", + "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" }, "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } - }, - "dependency-graph": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", - "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", - "dev": true - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.10.7.tgz", + "integrity": "sha512-sUzgIhm5yWHvRo3GF6mc1J58PCuY5nJDF2vlE8Jhlwkq+/VbJ/NVfTDYRQCeqI1jLcdMaVrVQXnXAWc4KpFNig==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.10.7", + "@angular-devkit/core": "7.0.7", + "rxjs": "6.3.3" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.10.7.tgz", + "integrity": "sha512-S49LSslNRxIflHzrIrEgK7mGQ7HzETr/FU0fyTbB0vubcmfzMoYTsgYdK7SUz583lovc+UvASoUAhPJI3e35ng==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "@angular-devkit/core": "7.0.7", + "rxjs": "6.3.3" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "@angular-devkit/core": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.7.tgz", + "integrity": "sha512-M8tTT9r3nUtWI3YyiyynHIQn+lQQgeKkxVZ+rdxvyvgE3U9+wn0yep5HkFLQETTuJetu9ARRRD94sD2XL3F/3A==", + "dev": true, + "requires": { + "ajv": "6.5.3", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "path-type": { + "anymatch": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "pify": "^2.0.0" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "camelcase": "^4.1.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" } } } }, - "@angular/core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.0.0.tgz", - "integrity": "sha512-DjVyWNGBWKEeBvxeXy8FGBNlnr/W/tNygOZEd6/uCktcXTG4DNyNQrWuNZUKEpr7RuIT3YVMj+UNwgTq0jB/9g==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/forms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.0.0.tgz", - "integrity": "sha512-rTg1UHq9gHR6zY3Kkip1KCm/YTck/rlR8CvVFIMwF0bdQxUCT51SXVn58nXts9yDaieABcGaQHNkQn1mARslgw==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/language-service": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.0.0.tgz", - "integrity": "sha512-JlBAXvKrXCCdFc9AnQRaMDl9c0qmASagmmbEX+fuxJbcyqtsUF2y/15Hy5UUmmA+ldZ+mwAoX/naPZZrFSP2rw==", - "dev": true - }, - "@angular/platform-browser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.0.0.tgz", - "integrity": "sha512-XyvL30d6meJ+SXlOmdR+sxoLdSvkQdmVNvpdvUzAHC/EqwA/byg4V3bTe5lpZmypclgFCjkGoTsz6uOnnwlQhw==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/platform-browser-dynamic": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.0.0.tgz", - "integrity": "sha512-lH2KuH+Em1y/mTOE6yTJmsOxYkMbYKzKLP9gYzc9vZu3er1df6Jx6jxefeBmAr9v+kNCLnpnHWHz2y4GzAesJA==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/router": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.0.0.tgz", - "integrity": "sha512-BK6Ho/7ckldFKc724piuPuMX0HPYXD8SUfwNj6yc0wgzDxdWzSmZj/xPEYll2pGNIA9x8Tg1NQKCD+kp1WXngw==", + "@angular-devkit/core": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.3.tgz", + "integrity": "sha512-Yp0AhTuJbp0VwCHTmUOANrKZNQxTD/F49jPmSCBa/VMYMIoU/sUIiHVNdwzfcFnMoExGoXYah0kutBxgNIG3OA==", + "dev": true, "requires": { - "tslib": "^1.9.0" - } - }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", + "ajv": "6.5.3", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + } + } + }, + "@angular-devkit/schematics": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.0.3.tgz", + "integrity": "sha512-FhfPvn5hBCNSj39qLgVaSwa6Zl0qt3Uyw4B9M+Kv01/FFc9xrowldzNT4QRxBjjWbr6DssEP+tYQCPV3Ouwx5w==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.0.3", + "rxjs": "6.3.3" + } + }, + "@angular/animations": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.0.0.tgz", + "integrity": "sha512-IYdryQXdYfPvhJpExLSAr0o9rlUeyVS++a6h/sjqN1dkUt/yJBHLRreuHx8Udvlj2nH70raHJgevk8FwhAkTdA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/cli": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.0.3.tgz", + "integrity": "sha512-la1Jktl9qGqGLXDy43lTU1D8KkN0zAohD9mOq4ilgNigzpedXFbzsGXJdWv7xVBMG51M6uhw3HWuFprQmbwgIw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.10.3", + "@angular-devkit/core": "7.0.3", + "@angular-devkit/schematics": "7.0.3", + "@schematics/angular": "7.0.3", + "@schematics/update": "0.10.3", + "inquirer": "6.2.0", + "opn": "5.3.0", + "rxjs": "6.3.3", + "semver": "5.5.1", + "symbol-observable": "1.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inquirer": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "@angular/common": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.0.0.tgz", + "integrity": "sha512-jp6MA6EOq/a1m+F0c1aZC345pAYYYFpN1m7GMM91JlqkjzJMhyYVk+Bod9xQOEWadcpY+RFudG+jRsPCMO8bvQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.0.0.tgz", + "integrity": "sha512-4fkohfGyG1BEpeYenOartuJmduyZ/R3XQx46hDDiR/9A8/Go4qLGkgr9Bd/JL/gPIR1XAHH9D5ii2sh+28ZEmA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.0.0.tgz", + "integrity": "sha512-fj5ixB4X3BsUnUukFx+dK5z2KkO7lCx5vlbUT2GOMbGCG43dIH6JKUfy5HbpCodLsJHG0gRgZZuY7/k+pbzS+g==", + "dev": true, + "requires": { + "canonical-path": "0.0.2", + "chokidar": "^1.4.2", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "magic-string": "^0.25.0", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "shelljs": "^0.8.1", + "source-map": "^0.6.1", + "yargs": "9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "dependency-graph": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", + "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", + "dev": true + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "@angular/core": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.0.0.tgz", + "integrity": "sha512-DjVyWNGBWKEeBvxeXy8FGBNlnr/W/tNygOZEd6/uCktcXTG4DNyNQrWuNZUKEpr7RuIT3YVMj+UNwgTq0jB/9g==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/forms": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.0.0.tgz", + "integrity": "sha512-rTg1UHq9gHR6zY3Kkip1KCm/YTck/rlR8CvVFIMwF0bdQxUCT51SXVn58nXts9yDaieABcGaQHNkQn1mARslgw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.0.0.tgz", + "integrity": "sha512-JlBAXvKrXCCdFc9AnQRaMDl9c0qmASagmmbEX+fuxJbcyqtsUF2y/15Hy5UUmmA+ldZ+mwAoX/naPZZrFSP2rw==", + "dev": true + }, + "@angular/platform-browser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.0.0.tgz", + "integrity": "sha512-XyvL30d6meJ+SXlOmdR+sxoLdSvkQdmVNvpdvUzAHC/EqwA/byg4V3bTe5lpZmypclgFCjkGoTsz6uOnnwlQhw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.0.0.tgz", + "integrity": "sha512-lH2KuH+Em1y/mTOE6yTJmsOxYkMbYKzKLP9gYzc9vZu3er1df6Jx6jxefeBmAr9v+kNCLnpnHWHz2y4GzAesJA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.0.0.tgz", + "integrity": "sha512-BK6Ho/7ckldFKc724piuPuMX0HPYXD8SUfwNj6yc0wgzDxdWzSmZj/xPEYll2pGNIA9x8Tg1NQKCD+kp1WXngw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, @@ -1068,35 +1887,422 @@ "dev": true }, "@ngtools/webpack": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.0.3.tgz", - "integrity": "sha512-8vllt35aCARPF8LJ3YfVpqTG39/vDLtY8+8LZqUKnOIlX9F0uzuQl86pmKoIAyk6sLPkR0SpaMGwYDunVjzxRQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.0.7.tgz", + "integrity": "sha512-ukZv/8vhiVWLsEEWF1uena8GHRVUpwbPJ+8AupW25d2nNpwfsDtTIXKzTzRYeIQFFCnHJxr04lK18CVsn1lFaQ==", "dev": true, "requires": { - "@angular-devkit/core": "7.0.3", + "@angular-devkit/core": "7.0.7", "enhanced-resolve": "4.1.0", "rxjs": "6.3.3", "tree-kill": "1.2.0", "webpack-sources": "1.2.0" }, "dependencies": { + "@angular-devkit/core": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.0.7.tgz", + "integrity": "sha512-M8tTT9r3nUtWI3YyiyynHIQn+lQQgeKkxVZ+rdxvyvgE3U9+wn0yep5HkFLQETTuJetu9ARRRD94sD2XL3F/3A==", + "dev": true, + "requires": { + "ajv": "6.5.3", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, "enhanced-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "tapable": "^1.0.0" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "webpack-sources": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", @@ -1105,6 +2311,14 @@ "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } } } @@ -1499,9 +2713,9 @@ } }, "ajv-errors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", - "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", "dev": true }, "ajv-keywords": { @@ -1579,9 +2793,9 @@ } }, "ansi-colors": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.1.tgz", - "integrity": "sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, "ansi-escapes": { @@ -1727,9 +2941,9 @@ "dev": true }, "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, "array-map": { @@ -4477,26 +5691,26 @@ } }, "browserslist": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.3.tgz", - "integrity": "sha512-6h84UD1mmHeuQ9IucX6yzBc+KBYcBBTLYt2CXtY7GYCra6iE5kOm7oM+zuGw/0tjGtbJxjm58OvxSBmogEMCRQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.0.tgz", + "integrity": "sha512-tQkHS8VVxWbrjnNDXgt7/+SuPJ7qDvD0Y2e6bLtoQluR2SPvlmPUcfcU75L1KAalhqULlIFJlJ6BDfnYyJxJsw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000898", - "electron-to-chromium": "^1.3.81", - "node-releases": "^1.0.0-alpha.15" + "caniuse-lite": "^1.0.30000928", + "electron-to-chromium": "^1.3.100", + "node-releases": "^1.1.3" }, "dependencies": { "electron-to-chromium": { - "version": "1.3.82", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.82.tgz", - "integrity": "sha512-NI4nB2IWGcU4JVT1AE8kBb/dFor4zjLHMLsOROPahppeHrR0FG5uslxMmkp/thO1MvPjM2xhlKoY29/I60s0ew==", + "version": "1.3.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.103.tgz", + "integrity": "sha512-tObPqGmY9X8MUM8i3MEimYmbnLLf05/QV5gPlkR8MQ3Uj8G8B2govE1U4cQcBYtv3ymck9Y8cIOu4waoiykMZQ==", "dev": true }, "node-releases": { - "version": "1.0.0-alpha.15", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.15.tgz", - "integrity": "sha512-hKG6hd/g6a9OV/ARt2qrxbRhe/4WEMFohTLOB9PNyTYvvI59gICZFzt9/mMgpYUTts06qXlN8H6UjfbIRdnW8A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", + "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", "dev": true, "requires": { "semver": "^5.3.0" @@ -4715,9 +5929,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000899", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000899.tgz", - "integrity": "sha512-enC3zKfUCJxxwvUIsBkbHd54CtJw1KtIWvrK0JZxWD/fEN2knHaai45lndJ4xXAkyRAPyk60J3yagkKDWhfeMA==", + "version": "1.0.30000928", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000928.tgz", + "integrity": "sha512-aSpMWRXL6ZXNnzm8hgE4QDLibG5pVJ2Ujzsuj3icazlIkxXkPXtL+BWnMx6FBkWmkZgBHGUxPZQvrbRw2ZTxhg==", "dev": true }, "canonical-path": { @@ -5761,9 +6975,9 @@ } }, "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.4.tgz", + "integrity": "sha512-8I1sgZHfVwcSOY6mSGpVU3lw/GSIZvusg8dD2+OGehCJpOhQRLNcH0qb9upQnOH4XhgxxFJSg6E2kx95deb1Tw==", "dev": true, "requires": { "boom": "5.x.x" @@ -9746,7 +10960,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -12792,9 +14006,9 @@ "dev": true }, "map-age-cleaner": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", - "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { "p-defer": "^1.0.0" @@ -15106,9 +16320,9 @@ } }, "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", + "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", "dev": true }, "parallel-transform": { @@ -15921,25 +17135,36 @@ }, "dependencies": { "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz", - "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.11.tgz", + "integrity": "sha512-9AXb//5UcjeOEof9T+yPw3XTa5SL207ZOIC/lHYP4mbUTEh4M0rDAQekQpVANCZdwQwKhBtFZCk3i3h3h2hdWg==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.5.0" + "supports-color": "^6.1.0" } }, "source-map": { @@ -15949,9 +17174,9 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -16004,14 +17229,25 @@ }, "dependencies": { "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "cosmiconfig": { @@ -16037,14 +17273,14 @@ } }, "postcss": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz", - "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.11.tgz", + "integrity": "sha512-9AXb//5UcjeOEof9T+yPw3XTa5SL207ZOIC/lHYP4mbUTEh4M0rDAQekQpVANCZdwQwKhBtFZCk3i3h3h2hdWg==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.5.0" + "supports-color": "^6.1.0" } }, "postcss-load-config": { @@ -16081,9 +17317,9 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -18036,9 +19272,9 @@ } }, "spdy-transport": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", - "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.1.tgz", + "integrity": "sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q==", "dev": true, "requires": { "debug": "^2.6.8", @@ -18051,9 +19287,9 @@ } }, "speed-measure-webpack-plugin": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.3.tgz", - "integrity": "sha512-p+taQ69VkRUXYMoZOx2nxV/Tz8tt79ahctoZJyJDHWP7fEYvwFNf5Pd73k5kZ6auu0pTsPNLEUwWpM8mCk85Zw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.5.tgz", + "integrity": "sha512-S/guYjC4Izn5wY2d0+M4zowED/F77Lxh9yjkTZ+XAr244pr9c1MYNcXcRe9lx2hmAj0GPbOrBXgOF2YIp/CZ8A==", "dev": true, "requires": { "chalk": "^2.0.1" @@ -18540,9 +19776,9 @@ } }, "terser": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.10.3.tgz", - "integrity": "sha512-uyL5hwDICjnv49JANhZvQYLikt/HADWNbUFsKQpZ/i+JSOkL2T4V7WUpW7S/5QGZceVq2x0HRVhEQQuW2ZpX6g==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.1.tgz", + "integrity": "sha512-NSo3E99QDbYSMeJaEk9YW2lTg3qS9V0aKGlb+PlOrei1X02r1wSBHCNX/O+yeTRFSWPKPIGj6MqvvdqV4rnVGw==", "dev": true, "requires": { "commander": "~2.17.1", @@ -18563,9 +19799,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -18590,28 +19826,40 @@ "worker-farm": "^1.5.2" }, "dependencies": { + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "dev": true + }, "cacache": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.2.0.tgz", - "integrity": "sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", "y18n": "^4.0.0" } }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, "find-cache-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", @@ -18632,6 +19880,26 @@ "locate-path": "^3.0.0" } }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -18643,13 +19911,12 @@ } }, "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^3.0.2" } }, "mississippi": { @@ -18671,9 +19938,9 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -18738,6 +20005,21 @@ "requires": { "figgy-pudding": "^3.5.1" } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true } } }, @@ -19620,9 +20902,9 @@ "dev": true }, "url-parse": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", - "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", "dev": true, "requires": { "querystringify": "^2.0.0", @@ -20591,9 +21873,9 @@ "dev": true }, "tapable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", - "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", "dev": true }, "webpack-sources": { @@ -20965,9 +22247,9 @@ }, "dependencies": { "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true } } @@ -21154,13 +22436,13 @@ } }, "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -21337,6 +22619,15 @@ "locate-path": "^3.0.0" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -21508,26 +22799,26 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.10.0", + "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -21557,6 +22848,16 @@ "find-up": "^3.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", 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 b46e8ae5bb..ee59dadd1f 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 @@ -8,93 +8,91 @@ import { Subject } from 'rxjs'; 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'; import { StringFilterStateInterface } from '../../interfaces/string.filter.state.interface'; export class DatagridStringFilterImpl implements SerializableFilter { - constructor(public filterFn: ClrDatagridStringFilterInterface) { - const datagridPropertyStringFilter = filterFn; - this._state = - { - type: 'BuiltinStringFilter', - property: datagridPropertyStringFilter.prop, - value: '' - } - } + 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: StringFilterStateInterface; + /** + * The Observable required as part of the Filter interface + */ + private _changes = new Subject(); + private _state: StringFilterStateInterface; - // We do not want to expose the Subject itself, but the Observable which is read-only - public get changes(): Observable { - return this._changes.asObservable(); - } + // 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; + /** + * 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 = ''; } - /** - * 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._state.value = this.value; - this._changes.next(value); - } + if (value !== this._rawValue) { + this._rawValue = value; + this._lowerCaseValue = value.toLowerCase().trim(); + this._state.value = this.value; + this._changes.next(value); } + } - public get filterState(): StringFilterStateInterface { - return this._state; - } + public get filterState(): StringFilterStateInterface { + return this._state; + } - public set filterState(state: StringFilterStateInterface) { - this._state = state; - this._rawValue = state.value; - this._changes.next(); - } + public set filterState(state: StringFilterStateInterface) { + this._state = state; + this._rawValue = state.value; + this._changes.next(); + } - /** - * 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 properties - */ - public equals(other: DatagridStringFilterImpl): boolean { - if (other && other.filterState.type === this._state.type) { - return other.filterState.property === this._state.property; - } else { - return false; - } + /** + * Compare objects by properties + */ + 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/date.interval.filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/date.interval.filter.state.interface.ts new file mode 100644 index 0000000000..6fe4a32322 --- /dev/null +++ b/src/clr-angular/data/datagrid/interfaces/date.interval.filter.state.interface.ts @@ -0,0 +1,13 @@ +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 DateIntervalFilterStateInterface extends FilterStateInterface { + id: string; + from: Date; + to: Date; +} diff --git a/src/clr-angular/data/datagrid/interfaces/list.filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/list.filter.state.interface.ts new file mode 100644 index 0000000000..740490d143 --- /dev/null +++ b/src/clr-angular/data/datagrid/interfaces/list.filter.state.interface.ts @@ -0,0 +1,13 @@ +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 ListFilterStateInterface extends FilterStateInterface { + id: string; + values: string[]; + selectedValue: string; +} diff --git a/src/clr-angular/data/datagrid/interfaces/number.interval.filter.state.interface.ts b/src/clr-angular/data/datagrid/interfaces/number.interval.filter.state.interface.ts new file mode 100644 index 0000000000..5c4f77d389 --- /dev/null +++ b/src/clr-angular/data/datagrid/interfaces/number.interval.filter.state.interface.ts @@ -0,0 +1,13 @@ +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 NumberIntervalFilterStateInterface extends FilterStateInterface { + id: string; + from: number; + to: number; +} 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 58eac82cea..082081590b 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,47 +4,78 @@ ~ The full license information can be found in LICENSE in the root directory of this project. --> -

    Binding Grid state

    +

    Binding Grid state

    -

    - 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. -

    - - - User ID - Name - Creation date - Pokemon - Favorite color - - - - - - Favorite color2 - - - - - - - - {{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 +

    + 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. +

    + + + User ID + Name + Pokemon + Creation date + + + + + + Creation date 2 + + + + + + Favorite color + + + + + + Favorite color 2 + + + + + + Gender + + + + + Gender 2 + + + + + Age + + + + + Age 2 + + + + + + + {{user.id}} + {{user.name}} + {{user.pokemon.name}} + {{user.creation | date}} + {{user.creation | date}} + {{user.color1}} + {{user.color2}} + {{user.gender1}} + {{user.gender2}} + {{user.age1}} + {{user.age2}} + + + + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{total}} users + + + 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 4b1e9cef7c..1bd20ed98a 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 @@ -1,73 +1,123 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { Inventory, FetchResult } from '../inventory/inventory'; +import { FetchResult, Inventory } from '../inventory/inventory'; import { User } from '../inventory/user'; import { ClrDatagridStateInterface } from '@clr/angular'; import { ColorFilter } from '../utils/color-filter'; +import { ListFilter } from '../utils/list-filter'; +import { NumberIntervalFilter } from '../utils/number-interval-filter'; +import { DateIntervalFilter } from '../utils/date-interval-filter'; @Component({ - selector: 'app-binding-state', - providers: [Inventory], - templateUrl: './binding-state.component.html', - styleUrls: ['./binding-state.component.css'] + selector: 'app-binding-state', + providers: [Inventory], + templateUrl: './binding-state.component.html', + styleUrls: ['./binding-state.component.css'], }) export class BindingStateComponent implements OnInit { + users: User[]; + total: number; + loading: boolean = true; + state: ClrDatagridStateInterface; + genderList = ['FEMALE', 'MALE', 'OTHER']; - users: User[]; - total: number; - loading: boolean = true; - state: ClrDatagridStateInterface; + @ViewChild('creationFilter') dateFilter: DateIntervalFilter; + @ViewChild('creationFilter2') dateFilter2: DateIntervalFilter; + @ViewChild('colorFilter') filter: ColorFilter; + @ViewChild('colorFilter2') filter2: ColorFilter; + @ViewChild('genderFilter') listFilter: ListFilter; + @ViewChild('genderFilter2') listFilter2: ListFilter; + @ViewChild('ageFilter') numberIntervalFilter: ListFilter; + @ViewChild('ageFilter2') numberIntervalFilter2: ListFilter; - @ViewChild("colorFilter") filter: ColorFilter; - @ViewChild("colorFilter2") filter2: ColorFilter; + constructor(private inventory: Inventory) { + this.inventory.size = 103; + this.inventory.latency = 500; + this.inventory.reset(); + } - constructor(private inventory: Inventory) { - this.inventory.size = 103; - this.inventory.latency = 500; - this.inventory.reset(); - } + ngOnInit() { + const dateFilter = new DateIntervalFilter(); + dateFilter.setId(this.dateFilter.id); + dateFilter.from = new Date(); + dateFilter.to = new Date(); - ngOnInit() { - const colorFilter2 = new ColorFilter(); - colorFilter2.setId(this.filter2.id); - colorFilter2.toggleColor('White'); - colorFilter2.toggleColor('Black'); + const dateFilter2 = new DateIntervalFilter(); + dateFilter2.setId(this.dateFilter2.id); + dateFilter2.from = new Date(); + dateFilter2.to = new Date(); - const colorFilter = new ColorFilter(); - colorFilter.setId(this.filter.id); - colorFilter.toggleColor('Indigo'); - colorFilter.toggleColor('Red'); + const colorFilter = new ColorFilter(); + colorFilter.setId(this.filter.id); + colorFilter.toggleColor('Indigo'); + colorFilter.toggleColor('Red'); - this.state = - { - page: { from: 0, to: 8, size: 10 }, - sort: { by: "pokemon", reverse: false }, - filters: [ - { property: "name", value: "sampleName" }, - { property: "creation", value: "sampleDate" }, - colorFilter, - colorFilter2] - }; - } + const colorFilter2 = new ColorFilter(); + colorFilter2.setId(this.filter2.id); + colorFilter2.toggleColor('White'); + colorFilter2.toggleColor('Black'); + + const genderFilter = new ListFilter(); + genderFilter.setId(this.listFilter.id); + genderFilter.selectedValue = 'MALE'; + this.listFilter.values = this.genderList; + + const genderFilter2 = new ListFilter(); + genderFilter2.setId(this.listFilter2.id); + genderFilter2.selectedValue = 'FEMALE'; + this.listFilter2.values = this.genderList; + + const ageFilter = new NumberIntervalFilter(); + ageFilter.setId(this.numberIntervalFilter.id); + ageFilter.from = 3; + ageFilter.to = 36; + + const ageFilter2 = new NumberIntervalFilter(); + ageFilter2.setId(this.numberIntervalFilter2.id); + ageFilter2.from = 5; + ageFilter2.to = 25; + + this.state = { + page: { from: 0, to: 8, size: 10 }, + sort: { by: 'pokemon', reverse: false }, + filters: [ + { property: 'name', value: 'Alica' }, + dateFilter, + dateFilter2, + colorFilter, + colorFilter2, + genderFilter, + genderFilter2, + ageFilter, + ageFilter2, + ], + }; + } + + refresh(): void { + this.loading = true; + const filters: { [prop: string]: any[] } = this.extractFilters(); + + 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; + }); + } - 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; - if (property && value) { - filters[property] = [value]; - } - } + private extractFilters(): { [prop: string]: any[] } { + const filters: { [prop: string]: any[] } = {}; + if (this.state.filters) { + for (const filter of this.state.filters) { + const { property, value } = <{ property: string; value: string }>filter; + 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; - }); + } } + return filters; + } } diff --git a/src/dev/src/app/datagrid/datagrid.demo.module.ts b/src/dev/src/app/datagrid/datagrid.demo.module.ts index b7b6673724..2e8e6d0de1 100644 --- a/src/dev/src/app/datagrid/datagrid.demo.module.ts +++ b/src/dev/src/app/datagrid/datagrid.demo.module.ts @@ -40,6 +40,9 @@ 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'; +import { ListFilter } from './utils/list-filter'; +import { NumberIntervalFilter } from './utils/number-interval-filter'; +import { DateIntervalFilter } from './utils/date-interval-filter'; @NgModule({ imports: [CommonModule, FormsModule, ClarityModule, ROUTING, UtilsDemoModule], @@ -71,6 +74,9 @@ import { BindingStateComponent } from './binding-state/binding-state.component'; DatagridTestCasesAsyncDemo, DatagridKitchenSinkDemo, ColorFilter, + ListFilter, + NumberIntervalFilter, + DateIntervalFilter, DetailWrapper, BindingStateComponent, ], diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts index 689658b496..e3271191ae 100644 --- a/src/dev/src/app/datagrid/utils/color-filter.ts +++ b/src/dev/src/app/datagrid/utils/color-filter.ts @@ -10,78 +10,78 @@ import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/int import { ColorFilterStateInterface } from '../../../../../clr-angular/data/datagrid/interfaces/color.filter.state.interface'; @Component({ - selector: 'clr-datagrid-color-filter-demo', - template: ` - `, - styleUrls: ['../datagrid.demo.scss'], + selector: 'clr-datagrid-color-filter-demo', + template: ` + `, + styleUrls: ['../datagrid.demo.scss'], }) export class ColorFilter implements SerializableFilter { - allColors = COLORS; - selectedColors: { [color: string]: boolean } = {}; - nbColors = 0; - changes: EventEmitter = new EventEmitter(false); - private _id: string; + allColors = COLORS; + selectedColors: { [color: string]: boolean } = {}; + nbColors = 0; + changes: EventEmitter = new EventEmitter(false); + private _id: string; - constructor() { - this._id = Math.random().toString(); - } + constructor() { + this._id = Math.random().toString(); + } - setId(id: string) { - this._id = id; - } + get id() { + return this._id; + } - get id() { - return this._id; - } + public get filterState(): ColorFilterStateInterface { + return { + id: this.id, + type: 'ColorFilter', + allColors: this.allColors, + selectedColors: this.selectedColors, + }; + } - public get filterState(): ColorFilterStateInterface { - return { - id: this.id, - type: 'ColorFilter', - allColors: this.allColors, - selectedColors: this.selectedColors, - }; + public set filterState(state: ColorFilterStateInterface) { + this.selectedColors = {}; + this.nbColors = 0; + for (let color in state.selectedColors) { + if (state.selectedColors[color]) { + this.toggleColor(color); + } } + } - public set filterState(state: ColorFilterStateInterface) { - this.selectedColors = {} - this.nbColors = 0; - for (let color in state.selectedColors) { - if (state.selectedColors[color]) { - this.toggleColor(color); - } - } - } + setId(id: string) { + this._id = id; + } - equals(other: ColorFilter) { - return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; - } + equals(other: ColorFilter): boolean { + return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; + } - listSelected(): string[] { - const list: string[] = []; - for (const color in this.selectedColors) { - if (this.selectedColors[color]) { - list.push(color); - } - } - return list; + listSelected(): string[] { + const list: string[] = []; + for (const color in this.selectedColors) { + if (this.selectedColors[color]) { + list.push(color); + } } + return list; + } - 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); - } + toggleColor(color: string): void { + this.selectedColors[color] = !this.selectedColors[color]; + this.selectedColors[color] ? this.nbColors++ : this.nbColors--; + this.filterState.selectedColors = this.selectedColors; + this.changes.emit(true); + } - accepts(user: User) { - return this.nbColors === 0 || this.selectedColors[user.color]; - } + accepts(user: User): boolean { + return this.nbColors === 0 || this.selectedColors[user.color]; + } - isActive(): boolean { - return this.nbColors > 0; - } + isActive(): boolean { + return this.nbColors > 0; + } } diff --git a/src/dev/src/app/datagrid/utils/date-interval-filter.ts b/src/dev/src/app/datagrid/utils/date-interval-filter.ts new file mode 100644 index 0000000000..eaf0c52d9f --- /dev/null +++ b/src/dev/src/app/datagrid/utils/date-interval-filter.ts @@ -0,0 +1,92 @@ +/* + * 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 { Component, EventEmitter } from '@angular/core'; +import { User } from '../inventory/user'; +import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/interfaces/serializable.filter.interface'; +import { DateIntervalFilterStateInterface } from '../../../../../clr-angular/data/datagrid/interfaces/date.interval.filter.state.interface'; +import { from } from 'rxjs/internal/observable/from'; + +@Component({ + selector: 'clr-datagrid-date-interval-filter-demo', + template: ` +
    + + From: + + To: + +
    `, + styleUrls: ['../datagrid.demo.scss'], +}) +export class DateIntervalFilter implements SerializableFilter { + from: Date; + to: Date; + visible = true; + changes: EventEmitter = new EventEmitter(false); + + constructor() { + this._id = Math.random().toString(); + } + + private _id: string; + + get id() { + return this._id; + } + + public get filterState(): DateIntervalFilterStateInterface { + return { + id: this.id, + type: 'DateIntervalFilter', + from: this.from, + to: this.to, + }; + } + + public set filterState(state: DateIntervalFilterStateInterface) { + this.from = state.from; + this.to = state.to; + } + + setId(id: string): void { + this._id = id; + } + + equals(other: DateIntervalFilter): boolean { + return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; + } + + setFilters(): void { + this.changes.emit(true); + } + + setToDate(): void { + if (this.from) { + this.to = this.from; + this.changes.emit(true); + } + } + + resetFilter(): void { + this.from = undefined; + this.to = undefined; + + this.changes.emit(true); + + this.visible = false; + setTimeout(() => { + this.visible = true; + }); + } + + accepts(user: User): boolean { + return this.from <= user.creation && this.to >= user.creation; + } + + isActive(): boolean { + return !!this.from || !!this.to; + } +} diff --git a/src/dev/src/app/datagrid/utils/list-filter.ts b/src/dev/src/app/datagrid/utils/list-filter.ts new file mode 100644 index 0000000000..e4a8808927 --- /dev/null +++ b/src/dev/src/app/datagrid/utils/list-filter.ts @@ -0,0 +1,70 @@ +/* + * 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 { Component, EventEmitter } from '@angular/core'; +import { User } from '../inventory/user'; +import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/interfaces/serializable.filter.interface'; +import { ListFilterStateInterface } from '../../../../../clr-angular/data/datagrid/interfaces/list.filter.state.interface'; + +@Component({ + selector: 'clr-datagrid-list-filter-demo', + template: ` +
    + +
    `, + styleUrls: ['../datagrid.demo.scss'], +}) +export class ListFilter implements SerializableFilter { + values: string[] = []; + selectedValue: string; + changes: EventEmitter = new EventEmitter(false); + private _id: string; + + constructor() { + this._id = Math.random().toString(); + } + + get id() { + return this._id; + } + + public get filterState(): ListFilterStateInterface { + return { + id: this.id, + type: 'ListFilter', + values: this.values, + selectedValue: this.selectedValue, + }; + } + + public set filterState(state: ListFilterStateInterface) { + this.selectedValue = state.selectedValue; + } + + setId(id: string) { + this._id = id; + } + + equals(other: ListFilter): boolean { + return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; + } + + selectionChanged(): void { + this.changes.emit(true); + } + + accepts(user: User): boolean { + return this.selectedValue === user.gender; + } + + isActive(): boolean { + return !!this.selectedValue; + } +} diff --git a/src/dev/src/app/datagrid/utils/number-interval-filter.ts b/src/dev/src/app/datagrid/utils/number-interval-filter.ts new file mode 100644 index 0000000000..48babd7cd7 --- /dev/null +++ b/src/dev/src/app/datagrid/utils/number-interval-filter.ts @@ -0,0 +1,73 @@ +/* + * 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 { Component, EventEmitter } from '@angular/core'; +import { User } from '../inventory/user'; +import { SerializableFilter } from '../../../../../clr-angular/data/datagrid/interfaces/serializable.filter.interface'; +import { NumberIntervalFilterStateInterface } from '../../../../../clr-angular/data/datagrid/interfaces/number.interval.filter.state.interface'; + +@Component({ + selector: 'clr-datagrid-number-interval-filter-demo', + template: ` + From: + To: + `, + styleUrls: ['../datagrid.demo.scss'], +}) +export class NumberIntervalFilter implements SerializableFilter { + from: number; + to: number; + changes: EventEmitter = new EventEmitter(false); + + constructor() { + this._id = Math.random().toString(); + } + + private _id: string; + + get id() { + return this._id; + } + + public get filterState(): NumberIntervalFilterStateInterface { + return { + id: this.id, + type: 'NumberIntervalFilter', + from: this.from, + to: this.to, + }; + } + + public set filterState(state: NumberIntervalFilterStateInterface) { + this.from = state.from; + this.to = state.to; + } + + setId(id: string) { + this._id = id; + } + + equals(other: NumberIntervalFilter): boolean { + return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; + } + + valueChanged(): void { + this.changes.emit(true); + } + + resetFilter(): void { + this.from = null; + this.to = null; + this.valueChanged(); + } + + accepts(user: User): boolean { + return this.from <= user.age && this.to >= user.age; + } + + isActive(): boolean { + return !!this.from || !!this.to; + } +} From 60e250d3ac1f5062676167af5f69f88e54513cbd Mon Sep 17 00:00:00 2001 From: Daniel Szegvari Date: Tue, 5 Feb 2019 12:38:17 +0100 Subject: [PATCH 13/14] Filter state two-way binding, custom equals Minor refactors, Write new tests, Fix failing tests Signed-off-by: Daniel Szegvari --- package-lock.json | 16 +- .../filters/datagrid-string-filter.spec.ts | 23 +- .../data/datagrid/datagrid-column.spec.ts | 33 +- .../data/datagrid/datagrid-filter.spec.ts | 18 +- .../data/datagrid/datagrid-row.spec.ts | 2 +- .../data/datagrid/datagrid.spec.ts | 381 +++++++++++--- src/clr-angular/data/datagrid/datagrid.ts | 477 +++++++++--------- .../data/datagrid/providers/filters.spec.ts | 15 +- .../data/datagrid/providers/selection.spec.ts | 17 +- .../data/datagrid/providers/state.provider.ts | 260 +++++----- .../binding-state.component.html | 3 +- .../binding-state/binding-state.component.ts | 22 +- .../src/app/datagrid/utils/color-filter.ts | 12 +- .../datagrid/utils/date-interval-filter.ts | 13 +- src/dev/src/app/datagrid/utils/list-filter.ts | 12 +- .../datagrid/utils/number-interval-filter.ts | 13 +- 16 files changed, 793 insertions(+), 524 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0de9b871b2..43b79f025a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "dependencies": { "source-map": { "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true } @@ -256,7 +256,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -276,7 +276,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -820,7 +820,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -840,7 +840,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -2042,7 +2042,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -2062,7 +2062,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -10960,7 +10960,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { diff --git a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter.spec.ts b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter.spec.ts index aca72d1cc1..3b0df20db1 100644 --- a/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter.spec.ts +++ b/src/clr-angular/data/datagrid/built-in/filters/datagrid-string-filter.spec.ts @@ -17,6 +17,10 @@ import { DomAdapter } from '../../../../utils/dom-adapter/dom-adapter'; import { DatagridStringFilter } from './datagrid-string-filter'; import { DatagridStringFilterImpl } from './datagrid-string-filter-impl'; +import { SerializableFilter } from '../../interfaces/serializable.filter.interface'; +import { Observable } from 'rxjs'; +import { FilterStateInterface } from '../../interfaces/filter.state.interface'; +import { DatagridPropertyStringFilter } from '@clr/angular'; const PROVIDERS = [FiltersProvider, DomAdapter, Page, StateDebouncer]; @@ -106,9 +110,20 @@ export default function(): void { }); } -class TestFilter implements ClrDatagridStringFilterInterface { - accepts(item: string, search: string) { - return item.toLowerCase() === search; +class TestFilter implements SerializableFilter { + changes: Observable; + filterState: FilterStateInterface; + + accepts(item: string): boolean { + return false; + } + + equals(state: SerializableFilter): boolean { + return false; + } + + isActive(): boolean { + return false; } } @@ -119,6 +134,6 @@ class TestFilter implements ClrDatagridStringFilterInterface { class FullTest { @ViewChild(CustomFilter) customFilter: CustomFilter; - filter: ClrDatagridStringFilterInterface; + filter: ClrDatagridStringFilterInterface = new DatagridPropertyStringFilter('test'); filterValue: string; } diff --git a/src/clr-angular/data/datagrid/datagrid-column.spec.ts b/src/clr-angular/data/datagrid/datagrid-column.spec.ts index 652a0d1847..9cbc00b70c 100644 --- a/src/clr-angular/data/datagrid/datagrid-column.spec.ts +++ b/src/clr-angular/data/datagrid/datagrid-column.spec.ts @@ -6,7 +6,7 @@ import { Component, Renderer2, ViewChild } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { Subject } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { DatagridPropertyComparator } from './built-in/comparators/datagrid-property-comparator'; import { DatagridStringFilter } from './built-in/filters/datagrid-string-filter'; @@ -16,8 +16,6 @@ import { DatagridHideableColumnModel } from './datagrid-hideable-column.model'; import { ClrDatagridSortOrder } from './enums/sort-order.enum'; import { TestContext } from './helpers.spec'; import { ClrDatagridComparatorInterface } from './interfaces/comparator.interface'; -import { ClrDatagridFilterInterface } from './interfaces/filter.interface'; -import { ClrDatagridStringFilterInterface } from './interfaces/string-filter.interface'; import { DragDispatcher } from './providers/drag-dispatcher'; import { FiltersProvider } from './providers/filters'; import { Page } from './providers/page'; @@ -26,6 +24,8 @@ import { StateDebouncer } from './providers/state-debouncer.provider'; import { TableSizeService } from './providers/table-size.service'; import { DomAdapter } from '../../utils/dom-adapter/dom-adapter'; import { DatagridRenderOrganizer } from './render/render-organizer'; +import { SerializableFilter } from './interfaces/serializable.filter.interface'; +import { FilterStateInterface } from './interfaces/filter.state.interface'; const PROVIDERS_NEEDED = [ Sort, @@ -436,7 +436,7 @@ class TestComparator implements ClrDatagridComparatorInterface { } } -class TestFilter implements ClrDatagridFilterInterface { +class TestFilter implements SerializableFilter { isActive(): boolean { return true; } @@ -446,12 +446,33 @@ class TestFilter implements ClrDatagridFilterInterface { } changes = new Subject(); + filterState: FilterStateInterface; + + equals(state: SerializableFilter): boolean { + return false; + } } -class TestStringFilter implements ClrDatagridStringFilterInterface { - accepts(n: number, search: string): boolean { +class TestStringFilter implements SerializableFilter { + id: string; + changes: Observable; + filterState: FilterStateInterface; + + constructor() { + this.id = Math.random().toString(); + } + + accepts(item: number): boolean { return true; } + + equals(state: TestStringFilter): boolean { + return state.filterState.type === state.filterState.type && this.id === state.id; + } + + isActive(): boolean { + return false; + } } @Component({ diff --git a/src/clr-angular/data/datagrid/datagrid-filter.spec.ts b/src/clr-angular/data/datagrid/datagrid-filter.spec.ts index d896576fe7..ceb32b0b14 100644 --- a/src/clr-angular/data/datagrid/datagrid-filter.spec.ts +++ b/src/clr-angular/data/datagrid/datagrid-filter.spec.ts @@ -13,6 +13,8 @@ import { CustomFilter } from './providers/custom-filter'; import { FiltersProvider } from './providers/filters'; import { Page } from './providers/page'; import { StateDebouncer } from './providers/state-debouncer.provider'; +import { SerializableFilter } from './interfaces/serializable.filter.interface'; +import { FilterStateInterface } from './interfaces/filter.state.interface'; export default function(): void { describe('ClrDatagridFilter component', function() { @@ -76,7 +78,7 @@ export default function(): void { expect(context.clarityDirective.filter).toBe(filter); }); - it('offers two-way binding on he open state of the filter dropdown', function() { + it('offers two-way binding on the open state of the filter dropdown', function() { context.testComponent.filter = filter; context.testComponent.open = true; context.detectChanges(); @@ -119,9 +121,15 @@ export default function(): void { }); } -class TestFilter implements ClrDatagridFilterInterface { +class TestFilter implements SerializableFilter { + public id; public active = true; + constructor() { + this.id = Math.random().toString(); + this.filterState = { type: 'TestFilter' }; + } + isActive(): boolean { return this.active; } @@ -131,6 +139,12 @@ class TestFilter implements ClrDatagridFilterInterface { } changes = new Subject(); + + filterState: FilterStateInterface; + + equals(state: TestFilter): boolean { + return this.filterState.type === state.filterState.type && this.id === state.id; + } } @Component({ template: `Hello world` }) diff --git a/src/clr-angular/data/datagrid/datagrid-row.spec.ts b/src/clr-angular/data/datagrid/datagrid-row.spec.ts index 744c2bec5c..9058f3f934 100644 --- a/src/clr-angular/data/datagrid/datagrid-row.spec.ts +++ b/src/clr-angular/data/datagrid/datagrid-row.spec.ts @@ -62,7 +62,7 @@ export default function(): void { renderer.resize(); }); - it('initialzes in display mode', function() { + it('initializes in display mode', function() { expect(context.clarityDirective.displayCells).toBe(true); }); diff --git a/src/clr-angular/data/datagrid/datagrid.spec.ts b/src/clr-angular/data/datagrid/datagrid.spec.ts index 5b8e0c633b..83aef9b5ba 100644 --- a/src/clr-angular/data/datagrid/datagrid.spec.ts +++ b/src/clr-angular/data/datagrid/datagrid.spec.ts @@ -4,7 +4,7 @@ * The full license information can be found in LICENSE in the root directory of this project. */ import { ChangeDetectionStrategy, Component, Input, Renderer2 } from '@angular/core'; -import { Subject } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { DatagridPropertyStringFilter } from './built-in/filters/datagrid-property-string-filter'; import { DatagridStringFilterImpl } from './built-in/filters/datagrid-string-filter-impl'; @@ -12,9 +12,7 @@ import { ClrDatagrid } from './datagrid'; import { DatagridDisplayMode } from './enums/display-mode.enum'; import { TestContext } from './helpers.spec'; import { ClrDatagridComparatorInterface } from './interfaces/comparator.interface'; -import { ClrDatagridFilterInterface } from './interfaces/filter.interface'; import { ClrDatagridStateInterface } from './interfaces/state.interface'; -import { ClrDatagridStringFilterInterface } from './interfaces/string-filter.interface'; import { ColumnToggleButtonsService } from './providers/column-toggle-buttons.service'; import { MockDisplayModeService } from './providers/display-mode.mock'; import { DisplayModeService } from './providers/display-mode.service'; @@ -30,6 +28,12 @@ import { StateDebouncer } from './providers/state-debouncer.provider'; import { StateProvider } from './providers/state.provider'; import { TableSizeService } from './providers/table-size.service'; import { DatagridRenderOrganizer } from './render/render-organizer'; +import { SerializableFilter } from './interfaces/serializable.filter.interface'; +import { FilterStateInterface } from './interfaces/filter.state.interface'; +import { ListFilter } from '../../../dev/src/app/datagrid/utils/list-filter'; +import { ColorFilter } from '../../../dev/src/app/datagrid/utils/color-filter'; +import { DateIntervalFilter } from '../../../dev/src/app/datagrid/utils/date-interval-filter'; +import { NumberIntervalFilter } from '../../../dev/src/app/datagrid/utils/number-interval-filter'; @Component({ template: ` @@ -263,7 +267,14 @@ class TestComparator implements ClrDatagridComparatorInterface { } } -class TestFilter implements ClrDatagridFilterInterface { +class TestFilter implements SerializableFilter { + public id; + + constructor() { + this.id = Math.random().toString(); + this.filterState = { type: 'TestFilter' }; + } + isActive(): boolean { return true; } @@ -273,10 +284,33 @@ class TestFilter implements ClrDatagridFilterInterface { } changes = new Subject(); + + filterState: FilterStateInterface; + + equals(state: TestFilter): boolean { + return this.filterState.type === state.filterState.type && this.id === state.id; + } } -class TestStringFilter implements ClrDatagridStringFilterInterface { - accepts(item: number, search: string) { +class TestStringFilter implements SerializableFilter { + changes: Observable; + filterState: FilterStateInterface; + id: string; + + constructor() { + this.id = Math.random().toString(); + this.filterState = { type: 'TestStringFilter' }; + } + + accepts(item: number): boolean { + return true; + } + + equals(state: TestStringFilter): boolean { + return this.filterState.type === state.filterState.type && this.id === state.id; + } + + isActive(): boolean { return true; } } @@ -489,24 +523,6 @@ export default function(): void { }); }); - it('emits the correct data for all filter types', function() { - const filters = context.getClarityProvider(FiltersProvider); - const customFilter = new TestFilter(); - const testStringFilter = new DatagridStringFilterImpl(new TestStringFilter()); - testStringFilter.value = 'whatever'; - const builtinStringFilter = new DatagridStringFilterImpl(new DatagridPropertyStringFilter('test')); - builtinStringFilter.value = '1234'; - filters.add(customFilter); // custom filter - filters.add(testStringFilter); // custom ClrDatagridStringFilterInterface ?? - filters.add(builtinStringFilter); - context.detectChanges(); - expect(context.testComponent.latestState.filters).toEqual([ - customFilter, - testStringFilter, - { property: 'test', value: '1234' }, - ]); - }); - it('emits early enough to avoid chocolate errors on the loading input', function() { context.testComponent.fakeLoad = true; const page: Page = context.getClarityProvider(Page); @@ -576,72 +592,273 @@ export default function(): void { expect(stateProvider.state.sort).toEqual(sortState); }); - it('sets the correct filters for all filter types', function() { + it('adds and removes the correct data for BuiltinStringFilters', function() { const filters = context.getClarityProvider(FiltersProvider); - const customFilter = new TestFilter(); - const testStringFilter = new DatagridStringFilterImpl(new TestStringFilter()); - testStringFilter.value = 'whatever'; - const builtinStringFilter = new DatagridStringFilterImpl(new DatagridPropertyStringFilter('test')); - builtinStringFilter.value = '1234'; - const filterState = [customFilter, testStringFilter, { property: 'test', value: '1234' }]; - context.testComponent.state = { - filters: filterState, - }; + const stringFilter1 = new DatagridStringFilterImpl(new DatagridPropertyStringFilter('test')); + stringFilter1.value = '1234'; + const stringFilter2 = new DatagridStringFilterImpl(new DatagridPropertyStringFilter('otherProperty')); + stringFilter2.value = 'otherValue'; + + filters.add(stringFilter1); + filters.add(stringFilter2); context.detectChanges(); - expect(context.testComponent.nbRefreshed).toEqual(2); - expect(filters.getActiveFilters()).toContain(customFilter); - expect(filters.getActiveFilters()).toContain(testStringFilter); + + expect(filters.getActiveFilters()).toBeArrayOfSize(2); expect( - filters - .getActiveFilters() - .filter( - filter => - filter instanceof DatagridStringFilterImpl && - filter.value === '1234' && - filter.filterFn instanceof DatagridPropertyStringFilter && - filter.filterFn.prop === 'test' - ) - ).toBeArrayOfSize(1); - expect(stateProvider.state.filters).toEqual(filterState); - }); - - it('disables and adds the correct filters for all filter types', function() { + filters.getActiveFilters().every(filter => filter.filterState.type === 'BuiltinStringFilter') + ).toBeTrue('Not every active filter is BuiltinStringFilter'); + expect((filters.getActiveFilters()[0]).filterState.property).toContain('test'); + expect((filters.getActiveFilters()[0]).filterState.value).toContain('1234'); + expect((filters.getActiveFilters()[1]).filterState.property).toContain( + 'otherProperty' + ); + expect((filters.getActiveFilters()[1]).filterState.value).toContain('otherValue'); + expect(context.testComponent.latestState.filters).toEqual([ + { property: 'test', value: '1234' }, + { property: 'otherProperty', value: 'otherValue' }, + ]); + + filters.remove(stringFilter1); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(1); + expect( + filters.getActiveFilters().every(filter => filter.filterState.type === 'BuiltinStringFilter') + ).toBeTrue('Not every active filter is BuiltinStringFilter'); + expect((filters.getActiveFilters()[0]).filterState.property).toContain( + 'otherProperty' + ); + expect((filters.getActiveFilters()[0]).filterState.value).toContain('otherValue'); + expect(context.testComponent.latestState.filters).toEqual([ + { property: 'otherProperty', value: 'otherValue' }, + ]); + }); + + it('adds and removes the correct data for Color filters', function() { const filters = context.getClarityProvider(FiltersProvider); - const customFilter = new TestFilter(); - const testStringFilter = new DatagridStringFilterImpl(new TestStringFilter()); - testStringFilter.value = 'whatever'; + const colorFilter1 = new ColorFilter(); + colorFilter1.toggleColor('Blue'); + colorFilter1.toggleColor('Green'); + const colorFilter2 = new ColorFilter(); + colorFilter2.toggleColor('Red'); + colorFilter2.toggleColor('White'); + + filters.add(colorFilter1); + filters.add(colorFilter2); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(2); + expect(filters.getActiveFilters().every(filter => filter.filterState.type === 'ColorFilter')).toBeTrue( + 'Not every active filter is ColorFilter' + ); + expect((filters.getActiveFilters()[0]).listSelected()).toContain('Green'); + expect((filters.getActiveFilters()[0]).listSelected()).toContain('Blue'); + expect((filters.getActiveFilters()[0]).listSelected()).not.toContain('White'); + expect((filters.getActiveFilters()[0]).listSelected()).not.toContain('Brown'); + expect((filters.getActiveFilters()[1]).listSelected()).toContain('White'); + expect((filters.getActiveFilters()[1]).listSelected()).toContain('Red'); + expect((filters.getActiveFilters()[1]).listSelected()).not.toContain('Green'); + expect((filters.getActiveFilters()[1]).listSelected()).not.toContain('Yellow'); + expect(context.testComponent.latestState.filters).toEqual([colorFilter1, colorFilter2]); + + filters.remove(colorFilter1); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(1); + expect(filters.getActiveFilters().every(filter => filter.filterState.type === 'ColorFilter')).toBeTrue( + 'Not every active filter is ColorFilter' + ); + expect((filters.getActiveFilters()[0]).listSelected()).toContain('White'); + expect((filters.getActiveFilters()[0]).listSelected()).toContain('Red'); + expect((filters.getActiveFilters()[0]).listSelected()).not.toContain('Green'); + expect((filters.getActiveFilters()[0]).listSelected()).not.toContain('Yellow'); + expect(context.testComponent.latestState.filters).toEqual([colorFilter2]); + }); + + it('adds and removes the correct data for List filters', function() { + const filters = context.getClarityProvider(FiltersProvider); + const listFilter1 = new ListFilter(); + listFilter1.values = ['FEMALE', 'MALE']; + listFilter1.selectedValue = 'MALE'; + const listFilter2 = new ListFilter(); + listFilter2.values = ['YES', 'NO', 'MAYBE']; + listFilter2.selectedValue = 'MAYBE'; + + filters.add(listFilter1); + filters.add(listFilter2); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(2); + expect(filters.getActiveFilters().every(filter => filter.filterState.type === 'ListFilter')).toBeTrue( + 'Not every active filter is ListFilter' + ); + expect((filters.getActiveFilters()[0]).selectedValue).toEqual('MALE'); + expect((filters.getActiveFilters()[1]).selectedValue).toEqual('MAYBE'); + expect(context.testComponent.latestState.filters).toEqual([listFilter1, listFilter2]); + + filters.remove(listFilter2); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(1); + expect(filters.getActiveFilters().every(filter => filter.filterState.type === 'ListFilter')).toBeTrue( + 'Not every active filter is ListFilter' + ); + expect((filters.getActiveFilters()[0]).selectedValue).toEqual('MALE'); + expect(context.testComponent.latestState.filters).toEqual([listFilter1]); + }); + + it('adds and removes the correct data for Date interval filters', function() { + const filters = context.getClarityProvider(FiltersProvider); + const dateIntervalFilter1 = new DateIntervalFilter(); + const today = new Date(); + dateIntervalFilter1.from = today; + let to1 = new Date(); + to1.setDate(to1.getDate() + 1); + dateIntervalFilter1.to = to1; + const dateIntervalFilter2 = new DateIntervalFilter(); + dateIntervalFilter2.from = today; + let to2 = new Date(); + to2.setDate(to2.getDate() + 3); + dateIntervalFilter2.to = to2; + + filters.add(dateIntervalFilter1); + filters.add(dateIntervalFilter2); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(2); + expect(filters.getActiveFilters().every(filter => filter.filterState.type === 'DateIntervalFilter')).toBeTrue( + 'Not every active filter is DateIntervalFilter' + ); + expect((filters.getActiveFilters()[0]).from.toString()).toEqual(today.toString()); + expect((filters.getActiveFilters()[0]).to.toString()).toEqual(to1.toString()); + expect((filters.getActiveFilters()[1]).from.toString()).toEqual(today.toString()); + expect((filters.getActiveFilters()[1]).to.toString()).toEqual(to2.toString()); + expect(context.testComponent.latestState.filters).toEqual([dateIntervalFilter1, dateIntervalFilter2]); + + filters.remove(dateIntervalFilter2); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(1); + expect(filters.getActiveFilters().every(filter => filter.filterState.type === 'DateIntervalFilter')).toBeTrue( + 'Not every active filter is DateIntervalFilter' + ); + expect((filters.getActiveFilters()[0]).from.toString()).toEqual(today.toString()); + expect((filters.getActiveFilters()[0]).to.toString()).toEqual(to1.toString()); + expect(context.testComponent.latestState.filters).toEqual([dateIntervalFilter1]); + }); + + it('adds and removes the correct data for Number interval filters', function() { + const filters = context.getClarityProvider(FiltersProvider); + const numberIntervalFilter1 = new NumberIntervalFilter(); + numberIntervalFilter1.from = 2; + numberIntervalFilter1.to = 7; + const numberIntervalFilter2 = new NumberIntervalFilter(); + numberIntervalFilter2.from = 13; + numberIntervalFilter2.to = 78; + + filters.add(numberIntervalFilter1); + filters.add(numberIntervalFilter2); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(2); + expect( + filters.getActiveFilters().every(filter => filter.filterState.type === 'NumberIntervalFilter') + ).toBeTrue('Not every active filter is NumberIntervalFilter'); + expect((filters.getActiveFilters()[0]).from).toEqual(2); + expect((filters.getActiveFilters()[0]).to).toEqual(7); + expect((filters.getActiveFilters()[1]).from).toEqual(13); + expect((filters.getActiveFilters()[1]).to).toEqual(78); + expect(context.testComponent.latestState.filters).toEqual([numberIntervalFilter1, numberIntervalFilter2]); + + filters.remove(numberIntervalFilter1); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeArrayOfSize(1); + expect( + filters.getActiveFilters().every(filter => filter.filterState.type === 'NumberIntervalFilter') + ).toBeTrue('Not every active filter is NumberIntervalFilter'); + expect((filters.getActiveFilters()[0]).from).toEqual(13); + expect((filters.getActiveFilters()[0]).to).toEqual(78); + expect(context.testComponent.latestState.filters).toEqual([numberIntervalFilter2]); + }); + + it('adds and removes the correct data for all filter types', function() { + const filters = context.getClarityProvider(FiltersProvider); + const listFilter = new ListFilter(); + listFilter.values = ['FEMALE', 'MALE']; + listFilter.selectedValue = 'MALE'; + const colorFilter = new ColorFilter(); + colorFilter.toggleColor('Blue'); + colorFilter.toggleColor('Green'); + const dateIntervalFilter = new DateIntervalFilter(); + const from = new Date(); + dateIntervalFilter.from = from; + let to = new Date(); + to.setDate(to.getDate() + 1); + dateIntervalFilter.to = to; + const numberIntervalFilter = new NumberIntervalFilter(); + numberIntervalFilter.from = 13; + numberIntervalFilter.to = 78; const builtinStringFilter = new DatagridStringFilterImpl(new DatagridPropertyStringFilter('test')); builtinStringFilter.value = '1234'; - const filterState = [customFilter, { property: 'test', value: '1234' }]; - context.testComponent.state = { - filters: filterState, - }; + + filters.add(listFilter); + filters.add(colorFilter); + filters.add(dateIntervalFilter); + filters.add(numberIntervalFilter); + filters.add(builtinStringFilter); context.detectChanges(); - expect(context.testComponent.nbRefreshed).toEqual(2); - expect(filters.getActiveFilters()).toContain(customFilter); - expect( - filters - .getActiveFilters() - .filter( - filter => - filter instanceof DatagridStringFilterImpl && - filter.value === '1234' && - filter.filterFn instanceof DatagridPropertyStringFilter && - filter.filterFn.prop === 'test' - ) - ).toBeArrayOfSize(1); - expect(stateProvider.state.filters).toEqual(filterState); - const newFilterState = [customFilter, testStringFilter]; - context.testComponent.state = { - filters: newFilterState, - }; + + expect(filters.getActiveFilters()).toBeArrayOfSize(5); + expect(context.testComponent.latestState.filters[0].filterState.type).toEqual('ListFilter'); + expect((context.testComponent.latestState.filters[0]).selectedValue).toEqual('MALE'); + expect(context.testComponent.latestState.filters[1].filterState.type).toEqual('ColorFilter'); + const selectedColors = (context.testComponent.latestState.filters[1]).listSelected(); + expect(selectedColors).toContain('Blue'); + expect(selectedColors).toContain('Green'); + expect(selectedColors).not.toContain('Red'); + expect(context.testComponent.latestState.filters[2].filterState.type).toEqual('DateIntervalFilter'); + expect((context.testComponent.latestState.filters[2]).from).toEqual(from); + expect((context.testComponent.latestState.filters[2]).to).toEqual(to); + expect(context.testComponent.latestState.filters[3].filterState.type).toEqual('NumberIntervalFilter'); + expect((context.testComponent.latestState.filters[3]).from).toEqual(13); + expect((context.testComponent.latestState.filters[3]).to).toEqual(78); + console.log(context.testComponent.latestState.filters[4]); + expect(context.testComponent.latestState.filters[4].property).toEqual('test'); + expect(context.testComponent.latestState.filters[4].value).toEqual('1234'); + expect(context.testComponent.latestState.filters).toEqual([ + listFilter, + colorFilter, + dateIntervalFilter, + numberIntervalFilter, + { property: 'test', value: '1234' }, + ]); + + filters.remove(colorFilter); + filters.remove(numberIntervalFilter); context.detectChanges(); - expect(context.testComponent.nbRefreshed).toEqual(3); - expect(filters.getActiveFilters()).toContain(customFilter); - expect(filters.getActiveFilters()).toContain(testStringFilter); - expect(filters.getActiveFilters()).not.toContain(builtinStringFilter); - expect(filters.getActiveFilters()).toBeArrayOfSize(2); - expect(stateProvider.state.filters).toEqual(newFilterState); + + expect(filters.getActiveFilters()).toBeArrayOfSize(3); + expect(context.testComponent.latestState.filters[0].filterState.type).toEqual('ListFilter'); + expect((context.testComponent.latestState.filters[0]).selectedValue).toEqual('MALE'); + expect(context.testComponent.latestState.filters[1].filterState.type).toEqual('DateIntervalFilter'); + expect((context.testComponent.latestState.filters[1]).from).toEqual(from); + expect((context.testComponent.latestState.filters[1]).to).toEqual(to); + expect(context.testComponent.latestState.filters[2].property).toEqual('test'); + expect(context.testComponent.latestState.filters[2].value).toEqual('1234'); + expect(context.testComponent.latestState.filters).toEqual([ + listFilter, + dateIntervalFilter, + { property: 'test', value: '1234' }, + ]); + + filters.remove(listFilter); + filters.remove(dateIntervalFilter); + filters.remove(builtinStringFilter); + context.detectChanges(); + + expect(filters.getActiveFilters()).toBeEmptyArray(); + expect(context.testComponent.latestState.filters).toBeUndefined(); }); }); }); diff --git a/src/clr-angular/data/datagrid/datagrid.ts b/src/clr-angular/data/datagrid/datagrid.ts index 3e7693b0bf..95efcc4af1 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,270 +44,265 @@ 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; - } - this.selection.updateCurrent(value, false); + /** + * 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); + } - @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; - } + /** + * 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(); - } + /** + * Indicates if all currently displayed items are selected + */ + public get allSelected() { + return this.selection.isAllSelected(); + } - /** - * Selects/deselects all currently displayed items - * @param value - */ - public set allSelected(value: boolean) { - /* + /** + * 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(); - } + this.selection.toggleAll(); + } - /** - * Custom placeholder detection - */ - @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder; + /** + * Custom placeholder detection + */ + @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder; - /** - * Hideable Column data source / detection. - */ - @ContentChildren(ClrDatagridColumn) public columns: QueryList>; + /** + * Hideable Column data source / detection. + */ + @ContentChildren(ClrDatagridColumn) public columns: QueryList>; - /** - * 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. - */ + /** + * 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. + */ - @ContentChildren(ClrDatagridRow) rows: QueryList>; - @ViewChild('scrollableColumns', { read: ViewContainerRef }) - scrollableColumns: ViewContainerRef; + @ContentChildren(ClrDatagridRow) rows: QueryList>; + @ViewChild('scrollableColumns', { read: ViewContainerRef }) + scrollableColumns: ViewContainerRef; - ngAfterContentInit() { + ngAfterContentInit() { + if (!this.items.smart) { + this.items.all = this.rows.map((row: ClrDatagridRow) => row.item); + } + + this._subscriptions.push( + this.rows.changes.subscribe(() => { 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.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); - }); - }) - ); - - this._subscriptions.push( - this.columns.changes.subscribe((columns: ClrDatagridColumn[]) => { - this.columnService.updateColumnList(this.columns.map(col => col.hideable)); - }) - ); - - // Get ColumnService ready for HideableColumns. + 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 => { - 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); - }); - } + // 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 => 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.spec.ts b/src/clr-angular/data/datagrid/providers/filters.spec.ts index 2217f34b9e..bff547d200 100644 --- a/src/clr-angular/data/datagrid/providers/filters.spec.ts +++ b/src/clr-angular/data/datagrid/providers/filters.spec.ts @@ -10,6 +10,8 @@ import { ClrDatagridFilterInterface } from '../interfaces/filter.interface'; import { FiltersProvider } from './filters'; import { Page } from './page'; import { StateDebouncer } from './state-debouncer.provider'; +import { SerializableFilter } from '../interfaces/serializable.filter.interface'; +import { FilterStateInterface } from '../interfaces/filter.state.interface'; export default function(): void { describe('FiltersProvider provider', function() { @@ -146,8 +148,13 @@ export default function(): void { }); } -abstract class TestFilter implements ClrDatagridFilterInterface { +abstract class TestFilter implements SerializableFilter { private active = false; + id: string; + + constructor() { + this.id = Math.random().toString(); + } toggle() { this.active = !this.active; @@ -161,6 +168,12 @@ abstract class TestFilter implements ClrDatagridFilterInterface { changes = new Subject(); abstract accepts(n: number): boolean; + + filterState: FilterStateInterface; + + equals(state: TestFilter): boolean { + return this.id === state.id; + } } class EvenFilter extends TestFilter { diff --git a/src/clr-angular/data/datagrid/providers/selection.spec.ts b/src/clr-angular/data/datagrid/providers/selection.spec.ts index 35f4fd9a74..dfa59d1e07 100644 --- a/src/clr-angular/data/datagrid/providers/selection.spec.ts +++ b/src/clr-angular/data/datagrid/providers/selection.spec.ts @@ -7,14 +7,14 @@ import { TrackByFunction } from '@angular/core'; import { fakeAsync, tick } from '@angular/core/testing'; import { Subject } from 'rxjs'; -import { ClrDatagridFilterInterface } from '../interfaces/filter.interface'; - import { FiltersProvider } from './filters'; import { Items } from './items'; import { Page } from './page'; import { Selection, SelectionType } from './selection'; import { Sort } from './sort'; import { StateDebouncer } from './state-debouncer.provider'; +import { SerializableFilter } from '../interfaces/serializable.filter.interface'; +import { FilterStateInterface } from '../interfaces/filter.state.interface'; const numberSort = (a: number, b: number) => a - b; @@ -225,7 +225,7 @@ export default function(): void { expect(nbChanges).toBe(1); const evenFilter: EvenFilter = new EvenFilter(); - filtersInstance.add(>evenFilter); + filtersInstance.add(evenFilter); evenFilter.toggle(); // current is set to [] because filter is applied, and nbChanges is 3. @@ -241,7 +241,7 @@ export default function(): void { const evenFilter: EvenFilter = new EvenFilter(); - filtersInstance.add(>evenFilter); + filtersInstance.add(evenFilter); evenFilter.toggle(); @@ -590,7 +590,8 @@ export default function(): void { }); } -abstract class TestFilter implements ClrDatagridFilterInterface { +abstract class TestFilter implements SerializableFilter { + public id; private active = false; toggle() { @@ -605,6 +606,12 @@ abstract class TestFilter implements ClrDatagridFilterInterface { changes = new Subject(); abstract accepts(n: number): boolean; + + filterState: FilterStateInterface; + + equals(state: TestFilter): boolean { + return this.filterState.type === state.filterState.type && this.id === state.id; + } } class EvenFilter extends TestFilter { diff --git a/src/clr-angular/data/datagrid/providers/state.provider.ts b/src/clr-angular/data/datagrid/providers/state.provider.ts index f43e83528b..389fbd7431 100644 --- a/src/clr-angular/data/datagrid/providers/state.provider.ts +++ b/src/clr-angular/data/datagrid/providers/state.provider.ts @@ -24,153 +24,169 @@ import { StringFilterStateInterface } from '../interfaces/string.filter.state.in */ @Injectable() export class StateProvider { - 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)); - _prevState: ClrDatagridStateInterface; - /* - * 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) { - /* + 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)); + _prevState: ClrDatagridStateInterface; + + 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 }; + } else { + state.page = { from: 0 }; + } + 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 }; - } - } + state.sort = { by: (>this.sort.comparator).prop, reverse: this.sort.reverse }; + } else { + state.sort = { by: this.sort.comparator, reverse: this.sort.reverse }; + } + } - const activeFilters = this.filters.getActiveFilters(); - if (activeFilters.length > 0) { - state.filters = []; - for (const filter of activeFilters) { - if (filter.filterState && filter.filterState.type === 'BuiltinStringFilter') { - const stringFilterState = filter.filterState; - state.filters.push({ - property: stringFilterState.property, - value: stringFilterState.value, - }); - } else { - state.filters.push(filter); - } - } + const activeFilters = this.filters.getActiveFilters(); + if (activeFilters.length > 0) { + state.filters = []; + for (const filter of activeFilters) { + if (filter.filterState && filter.filterState.type === 'BuiltinStringFilter') { + const stringFilterState = filter.filterState; + state.filters.push({ + property: stringFilterState.property, + value: stringFilterState.value, + }); + } else { + state.filters.push(filter); } - return state; + } } + return state; + } - set state(state: ClrDatagridStateInterface) { - if (this.sameAsPreviousState(state)) { - return; - } else { - this._prevState = state; + set state(state: ClrDatagridStateInterface) { + if (this.sameAsPreviousState(state)) { + return; + } + + 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); } - 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 (this.sort.reverse !== state.sort.reverse) { + this.sort.toggle(this.sort.comparator); } - 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; - } + } else { + this.sort.comparator = state.sort.by; + this.sort.reverse = state.sort.reverse; + } + } + + if (!this.filtersAreSame(state) && state.filters) { + const gridFilters = this.filters.getFilters(); + 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; } - if (state.filters) { - const gridFilters = this.filters.getFilters(); - 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 = gridFilters.findIndex(value => value.equals(filterObject)); - if (existing !== -1) { - gridFilters[existing].filterState = filterObject.filterState; - } else { - this.filters.add(filterObject); - } - } + const existing = gridFilters.findIndex(value => value.equals(filterObject)); + if (existing !== -1) { + gridFilters[existing].filterState = filterObject.filterState; + } else { + this.filters.add(filterObject); } - - this.debouncer.changeDone(); + } } - sameAsPreviousState(state: ClrDatagridStateInterface) { - if (!this._prevState) { - return false; - } + this._prevState = state; + this.debouncer.changeDone(); + } - if (state.page && this._prevState.page && !this.propertiesAreSame(state.page, this._prevState.page)) { - return false; - } + sameAsPreviousState(state: ClrDatagridStateInterface) { + if (!this._prevState) { + return false; + } - if (state.sort && this._prevState.sort && !this.propertiesAreSame(state.sort, this._prevState.sort)) { - return false; - } + if (state.page && this._prevState.page && !this.propertiesAreSame(state.page, this._prevState.page)) { + return false; + } - if (!this.filtersCountAreSame(state)) { - return false; - } + if (state.sort && this._prevState.sort && !this.propertiesAreSame(state.sort, this._prevState.sort)) { + 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 this.filtersAreSame(state); + } + + filtersAreSame(state: ClrDatagridStateInterface): boolean { + if (!this._prevState) { + return false; + } - return true; + if (!this.filtersCountAreSame(state)) { + return false; } - propertiesAreSame(object1: any, object2: any) { - for (let key in object1) { - if (object1[key] !== object2[key]) { - 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 true; + } - return true; + propertiesAreSame(object1: any, object2: any): boolean { + for (let key in object1) { + if (object1[key] !== object2[key]) { + return false; + } } - filtersCountAreSame(state: ClrDatagridStateInterface) { - if (!state.filters && !this._prevState.filters) { - return true; - } + for (let key in object2) { + if (object1[key] !== object2[key]) { + return false; + } + } - if (!state.filters && this._prevState.filters || state.filters && !this._prevState.filters) { - return false; - } + return true; + } + + filtersCountAreSame(state: ClrDatagridStateInterface): boolean { + if (!state.filters && !this._prevState.filters) { + return true; + } - return state.filters.length === this._prevState.filters.length; + 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 082081590b..ee5057a6a4 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 @@ -76,6 +76,7 @@

    Binding Grid state

    {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{total}} users - +
    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 1bd20ed98a..891b252c7d 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 @@ -18,6 +18,7 @@ export class BindingStateComponent implements OnInit { total: number; loading: boolean = true; state: ClrDatagridStateInterface; + pageInfo: { size: number; current: number }; genderList = ['FEMALE', 'MALE', 'OTHER']; @ViewChild('creationFilter') dateFilter: DateIntervalFilter; @@ -37,47 +38,46 @@ export class BindingStateComponent implements OnInit { ngOnInit() { const dateFilter = new DateIntervalFilter(); - dateFilter.setId(this.dateFilter.id); + dateFilter.id = this.dateFilter.id; dateFilter.from = new Date(); dateFilter.to = new Date(); const dateFilter2 = new DateIntervalFilter(); - dateFilter2.setId(this.dateFilter2.id); + dateFilter2.id = this.dateFilter2.id; dateFilter2.from = new Date(); dateFilter2.to = new Date(); const colorFilter = new ColorFilter(); - colorFilter.setId(this.filter.id); + colorFilter.id = this.filter.id; colorFilter.toggleColor('Indigo'); colorFilter.toggleColor('Red'); const colorFilter2 = new ColorFilter(); - colorFilter2.setId(this.filter2.id); + colorFilter2.id = this.filter2.id; colorFilter2.toggleColor('White'); colorFilter2.toggleColor('Black'); const genderFilter = new ListFilter(); - genderFilter.setId(this.listFilter.id); + genderFilter.id = this.listFilter.id; genderFilter.selectedValue = 'MALE'; this.listFilter.values = this.genderList; const genderFilter2 = new ListFilter(); - genderFilter2.setId(this.listFilter2.id); + genderFilter2.id = this.listFilter2.id; genderFilter2.selectedValue = 'FEMALE'; this.listFilter2.values = this.genderList; const ageFilter = new NumberIntervalFilter(); - ageFilter.setId(this.numberIntervalFilter.id); + ageFilter.id = this.numberIntervalFilter.id; ageFilter.from = 3; ageFilter.to = 36; const ageFilter2 = new NumberIntervalFilter(); - ageFilter2.setId(this.numberIntervalFilter2.id); + ageFilter2.id = this.numberIntervalFilter2.id; ageFilter2.from = 5; ageFilter2.to = 25; this.state = { - page: { from: 0, to: 8, size: 10 }, sort: { by: 'pokemon', reverse: false }, filters: [ { property: 'name', value: 'Alica' }, @@ -91,6 +91,10 @@ export class BindingStateComponent implements OnInit { ageFilter2, ], }; + this.pageInfo = { + size: 3, + current: 2, + }; } refresh(): void { diff --git a/src/dev/src/app/datagrid/utils/color-filter.ts b/src/dev/src/app/datagrid/utils/color-filter.ts index e3271191ae..753582c139 100644 --- a/src/dev/src/app/datagrid/utils/color-filter.ts +++ b/src/dev/src/app/datagrid/utils/color-filter.ts @@ -19,18 +19,14 @@ import { ColorFilterStateInterface } from '../../../../../clr-angular/data/datag styleUrls: ['../datagrid.demo.scss'], }) export class ColorFilter implements SerializableFilter { + id: string; allColors = COLORS; selectedColors: { [color: string]: boolean } = {}; nbColors = 0; changes: EventEmitter = new EventEmitter(false); - private _id: string; constructor() { - this._id = Math.random().toString(); - } - - get id() { - return this._id; + this.id = Math.random().toString(); } public get filterState(): ColorFilterStateInterface { @@ -52,10 +48,6 @@ export class ColorFilter implements SerializableFilter { } } - setId(id: string) { - this._id = id; - } - equals(other: ColorFilter): boolean { return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; } diff --git a/src/dev/src/app/datagrid/utils/date-interval-filter.ts b/src/dev/src/app/datagrid/utils/date-interval-filter.ts index eaf0c52d9f..86f5912b4c 100644 --- a/src/dev/src/app/datagrid/utils/date-interval-filter.ts +++ b/src/dev/src/app/datagrid/utils/date-interval-filter.ts @@ -22,19 +22,14 @@ import { from } from 'rxjs/internal/observable/from'; styleUrls: ['../datagrid.demo.scss'], }) export class DateIntervalFilter implements SerializableFilter { + id: string; from: Date; to: Date; visible = true; changes: EventEmitter = new EventEmitter(false); constructor() { - this._id = Math.random().toString(); - } - - private _id: string; - - get id() { - return this._id; + this.id = Math.random().toString(); } public get filterState(): DateIntervalFilterStateInterface { @@ -51,10 +46,6 @@ export class DateIntervalFilter implements SerializableFilter { this.to = state.to; } - setId(id: string): void { - this._id = id; - } - equals(other: DateIntervalFilter): boolean { return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; } diff --git a/src/dev/src/app/datagrid/utils/list-filter.ts b/src/dev/src/app/datagrid/utils/list-filter.ts index e4a8808927..6b91694469 100644 --- a/src/dev/src/app/datagrid/utils/list-filter.ts +++ b/src/dev/src/app/datagrid/utils/list-filter.ts @@ -22,17 +22,13 @@ import { ListFilterStateInterface } from '../../../../../clr-angular/data/datagr styleUrls: ['../datagrid.demo.scss'], }) export class ListFilter implements SerializableFilter { + id: string; values: string[] = []; selectedValue: string; changes: EventEmitter = new EventEmitter(false); - private _id: string; constructor() { - this._id = Math.random().toString(); - } - - get id() { - return this._id; + this.id = Math.random().toString(); } public get filterState(): ListFilterStateInterface { @@ -48,10 +44,6 @@ export class ListFilter implements SerializableFilter { this.selectedValue = state.selectedValue; } - setId(id: string) { - this._id = id; - } - equals(other: ListFilter): boolean { return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; } diff --git a/src/dev/src/app/datagrid/utils/number-interval-filter.ts b/src/dev/src/app/datagrid/utils/number-interval-filter.ts index 48babd7cd7..3fda66496d 100644 --- a/src/dev/src/app/datagrid/utils/number-interval-filter.ts +++ b/src/dev/src/app/datagrid/utils/number-interval-filter.ts @@ -17,18 +17,13 @@ import { NumberIntervalFilterStateInterface } from '../../../../../clr-angular/d styleUrls: ['../datagrid.demo.scss'], }) export class NumberIntervalFilter implements SerializableFilter { + id: string; from: number; to: number; changes: EventEmitter = new EventEmitter(false); constructor() { - this._id = Math.random().toString(); - } - - private _id: string; - - get id() { - return this._id; + this.id = Math.random().toString(); } public get filterState(): NumberIntervalFilterStateInterface { @@ -45,10 +40,6 @@ export class NumberIntervalFilter implements SerializableFilter { this.to = state.to; } - setId(id: string) { - this._id = id; - } - equals(other: NumberIntervalFilter): boolean { return other.filterState.type === this.filterState.type && other.filterState.id === this.filterState.id; } From 77f3925fb02dffa2c00ff4fd429438cf37598760 Mon Sep 17 00:00:00 2001 From: Daniel Szegvari Date: Thu, 7 Feb 2019 12:53:14 +0100 Subject: [PATCH 14/14] Filter state two-way binding, custom equals Minor refactors Signed-off-by: Daniel Szegvari --- package-lock.json | 16 +- src/clr-angular/data/datagrid/datagrid.ts | 472 +++++++++++----------- 2 files changed, 244 insertions(+), 244 deletions(-) diff --git a/package-lock.json b/package-lock.json index 43b79f025a..0de9b871b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "dependencies": { "source-map": { "version": "0.5.6", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true } @@ -256,7 +256,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -276,7 +276,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -820,7 +820,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -840,7 +840,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -2042,7 +2042,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -2062,7 +2062,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -10960,7 +10960,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { diff --git a/src/clr-angular/data/datagrid/datagrid.ts b/src/clr-angular/data/datagrid/datagrid.ts index 95efcc4af1..15d18d4df2 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,265 @@ 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; - } + /** + * 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(); - } + /** + * Indicates if all currently displayed items are selected + */ + public get allSelected() { + return this.selection.isAllSelected(); + } - /** - * Selects/deselects all currently displayed items - * @param value - */ - public set allSelected(value: boolean) { - /* + /** + * 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(); - } - - /** - * Custom placeholder detection - */ - @ContentChild(ClrDatagridPlaceholder) public placeholder: ClrDatagridPlaceholder; + 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 => 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; }