From 52d602c83eed2c19dd8dcc0d50102d2fb589dbc2 Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Mon, 3 Dec 2018 19:42:39 +0200 Subject: [PATCH 001/114] docs(readme): Updating the missing components and directives. --- README.md | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6c87fb4422d..25b95185c69 100644 --- a/README.md +++ b/README.md @@ -16,39 +16,43 @@ You can find source files under the [`src`](https://github.com/IgniteUI/igniteui ![](https://dl.infragistics.com/tools/extensions/angular-tooltips/tooltip_preview.gif) **IMPORTANT** The repository has been renamed from `igniteui-js-blocks` to `igniteui-angular`. Read more on our new [naming convention](https://www.infragistics.com/community/blogs/b/infragistics/posts/ignite-ui-github-repo-name-changes). - + Current list of controls include: | *Components* | Status | | | | *Directives* | Status | | | | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | | **avatar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/avatar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/avatar.html) | | **button** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/button/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/button.html) | -| **badge** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/badge/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/badge.html) | | **dragdrop** | InProgress | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/dragdrop/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/dragdrop.html) | -| **buttonGroup** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/buttonGroup/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/buttongroup.html) | | **filter** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/filter/README-FILTER.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list.html) | -| **calendar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/calendar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/calendar.html) | | **forOf** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/for-of/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/for_of.html) | +| **badge** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/badge/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/badge.html) | | **dragdrop** | InProgress | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/dragdrop/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/drag_drop.html) | +| **banner** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/banner/README.md) | | | **filter** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/filter/README-FILTER.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list.html) | +| **buttonGroup** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/buttonGroup/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/buttongroup.html) | | **focus** | Available | | | +| **calendar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/calendar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/calendar.html) | | **forOf** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/for-of/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/for_of.html) | | **card** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/card/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/card.html) | | **hint** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | | **carousel** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/carousel/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/carousel.html) | | **input** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/input/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | | **checkbox** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/checkbox/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/checkbox.html) | | **label** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/label/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | -| **chips** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/chips/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/chip.html) | | **layout** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/layout/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/layout.html) | +| **chips** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/chips/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/chip.html) | | **layout** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/layout/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/layout.html) | | **circular progress** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/progressbar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/circular_progress.html) | | **mask** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/mask/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/mask.html) | -| **combo** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/combo/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/combo.html) | | **prefix** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | -| **datePicker** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/date-picker/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date_picker.html) | | **ripple** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/ripple/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/button.html) | -| **dialog** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/dialog/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/dialog.html) | | **suffix** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | -| **drop down** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/drop-down/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/drop_down.html) | | **text-highlight** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/text-highlight/README.md) | | -| **grid** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grid/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid.html) | | **text-selection** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/text-selection/README.md) | | -| **icon** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/icon/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/icon.html) | | **toggle** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/toggle/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/toggle.html) | -| **input group** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | | *Others* | Status | Docs | | -| **list** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/list/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list.html) | | **Animations** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/animations/README.md) | | -| **navbar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navbar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navbar.html) | | **dataUtil** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATAUTIL.md) | | -| **navigation drawer** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navigation-drawer/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navdrawer.html) | | **dataContainer** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATACONTAINER.md) | | -| **radio** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/radio/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio_button.html) | | | | | | -| **slider** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/slider/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/slider.html) | | | | | | +| **combo** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/combo/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/combo.html) | | **prefix** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | +| **datePicker** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/date-picker/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/date_picker.html) | | **radio-group** | Available | | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio_button.html#radio-group)| +| **dialog** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/dialog/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/dialog.html) | | **ripple** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/ripple/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/button.html) | +| **drop down** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/drop-down/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/drop_down.html) | | **scroll-inertia** | Available | | | +| **expansion-panel** | Available | | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/expansion_panel.html) | | **suffix** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | +| **grid** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid.html) | | **template-outlet** | Available | | | +| **icon** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/icon/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/icon.html) | | **text-highlight** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/text-highlight/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/texthighlight.html) | +| **input group** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/input-group/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/input_group.html) | | **text-selection** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/directives/text-selection/README.md) | | | +| **linear progress** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/progressbar) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/linear_progress.html) | | **toggle** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/toggle/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/toggle.html) | +| **list** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/list/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/list.html) | | **tooltip** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/tooltip/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip.html) | +| **navbar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navbar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navbar.html) | | *Others* | Status | Docs | | +| **navigation drawer** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/navigation-drawer/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/navdrawer.html) | | **Animations** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/animations/README.md) | | +| **radio** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/radio/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio_button.html) | | **dataUtil** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATAUTIL.md) | | +| **slider** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/slider/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/slider.html) | | **dataContainer** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/data-operations/README-DATACONTAINER.md) | | | **snackbar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/snackbar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/snackbar.html) | | | | | | | **switch** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/switch/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/switch.html) | | | | | | | **tabbar** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/tabbar/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tabbar.html) | | | | | | | **tabs** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/tabs/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tabs.html) | | | | | | | **time picker** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/time-picker/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/time_picker.html) | | | | | | | **toast** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/toast/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/toast.html) | | | | | | +| **tree grid** | Available | [Readme](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/grids/tree-grid/README.md) | [Docs](https://www.infragistics.com/products/ignite-ui-angular/angular/components/treegrid.html) | | | | | | ## Setup From 69271280cb7521786d2ed00be1914908e69de8e6 Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Tue, 4 Dec 2018 12:00:37 +0200 Subject: [PATCH 002/114] docs(readme): Removing source changes. --- .../src/lib/combo/combo.component.spec.ts | 16 ---------------- .../src/lib/drop-down/drop-down.base.ts | 6 ------ .../src/lib/drop-down/drop-down.component.ts | 11 ++--------- 3 files changed, 2 insertions(+), 31 deletions(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index e40eece4108..08e875a0206 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -882,22 +882,6 @@ describe('igxCombo', () => { }); }); }); - - it('Should properly get the first focusable item when focusing the component list', fakeAsync(() => { - const fixture = TestBed.createComponent(IgxComboInputTestComponent); - fixture.detectChanges(); - const combo = fixture.componentInstance.combo; - spyOn(combo.dropdown, 'getFirstSelectableItem').and.callThrough(); - combo.toggle(); - tick(); - fixture.detectChanges(); - combo.searchInput.nativeElement.dispatchEvent(new KeyboardEvent('keypress', { key: 'Tab'})); - (document.getElementsByClassName('igx-combo__content')[0]).dispatchEvent(new Event('focus')); - tick(); - fixture.detectChanges(); - expect(combo.dropdown.getFirstSelectableItem).toHaveBeenCalledTimes(1); - expect((combo.dropdown.focusedItem.element.nativeElement).textContent.trim()).toEqual('Michigan'); - })); }); diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts index cefdd64da0a..1f61391c386 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.base.ts @@ -552,12 +552,6 @@ export abstract class IgxDropDownBase implements OnInit, IToggleView { this._focusedItem.isFocused = true; } } - /** - * @hidden - */ - public getFirstSelectableItem() { - return this.children.find(child => !child.isHeader && !child.disabled); - } } /** diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts index 92afaff2b4c..150c0c98fab 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts @@ -43,13 +43,6 @@ export class IgxDropDownItemNavigationDirective { set target(target: IgxDropDownBase) { this._target = target ? target : this.dropdown; } - @HostListener('focus') - handleFocus() { - if ((this.target).combo) { - this.target.focusedItem = this.target.getFirstSelectableItem(); - this.target.focusedItem.isFocused = true; - } - } /** * @hidden @@ -59,7 +52,7 @@ export class IgxDropDownItemNavigationDirective { if (event) { const key = event.key.toLowerCase(); if (!this.target.collapsed) { // If dropdown is opened - const navKeys = ['esc', 'escape', 'enter', 'space', 'spacebar', ' ', + const navKeys = ['esc', 'escape', 'enter', 'tab', 'space', 'spacebar', ' ', 'arrowup', 'up', 'arrowdown', 'down', 'home', 'end']; if (navKeys.indexOf(key) === -1) { // If key has appropriate function in DD return; @@ -72,7 +65,7 @@ export class IgxDropDownItemNavigationDirective { switch (key) { case 'esc': case 'escape': - // case 'tab': + case 'tab': this.onEscapeKeyDown(event); break; case 'enter': From 795918084d65697aeb9aedaebaa6e6bd253c5d30 Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Tue, 4 Dec 2018 12:02:54 +0200 Subject: [PATCH 003/114] docs(redme): Removing Redundant source code. --- .../igniteui-angular/src/lib/drop-down/drop-down.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts index 150c0c98fab..50e6332a26c 100644 --- a/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts +++ b/projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts @@ -65,10 +65,10 @@ export class IgxDropDownItemNavigationDirective { switch (key) { case 'esc': case 'escape': - case 'tab': this.onEscapeKeyDown(event); break; case 'enter': + case 'tab': this.onEnterKeyDown(event); break; case 'space': From 9edcf11af2bcb2df09f7089e8b02abf56b548f7e Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Tue, 4 Dec 2018 16:52:04 +0200 Subject: [PATCH 004/114] docs(readme): Removing the QuickStart App section. --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 25b95185c69..e766f03b7ee 100644 --- a/README.md +++ b/README.md @@ -130,15 +130,6 @@ You can include Ignite UI for Angular in your project as a dependency using the [General Naming Guidelines](../../wiki//General-Naming-Guidelines-for-Ignite-UI-for-Angular) - -## Quickstart App -[Ignite UI for Angular Quickstart app](https://github.com/IgniteUI/igniteui-angular-quickstart) -This repository is a fork of the Angular QuickStart Source and has been updated to demonstrate how to include and use components from Ignite UI for Angular. It basically follows the shortest path to bootstrap writing the application with Ignite UI for Angular: - -- Fork the [the angular quickstart](https://github.com/angular/quickstart) -- Install Ignite UI for Angular from npm using `npm install igniteui-angular --save-dev` -- Update the views with sample Ignite UI for Angular controls. - ## Demo Apps & Documentation The [Warehouse Picklist App](https://github.com/IgniteUI/warehouse-js-blocks) demonstrates using several Ignite UI for Angular widgets together to build a modern, mobile app. From 6fa110f4f86a264902b2b47b1b74b5f1765eca5a Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Wed, 5 Dec 2018 13:49:30 +0200 Subject: [PATCH 005/114] docs(readme): Update tp Ignite Cli section. Adding sections for ng add/update. --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e766f03b7ee..ede1a0ef0ad 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ From the root folder run: npm install ``` -## Getting Started with CLI +## Create new Project with IgniteUI CLI To get started with the Ignite UI CLI and Ignite UI for Angular: ``` @@ -73,6 +73,28 @@ ig add grid ig start ``` +## Adding IgniteUI for Angular to Existing Project + +Including the `igniteui-angular` package to your project: + +``` +ng add igniteui-angular +``` + +## Updating Existing Project + +Analyze your project for possible migrations: + +``` +ng update +``` + +If there are new versions available, update your packages: + +``` +ng update +``` + ## Building the Library ``` // build the code From de62f5869f654e8452046ab947c305671454eada Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Wed, 5 Dec 2018 13:52:48 +0200 Subject: [PATCH 006/114] docs(readme): Changes to ng add desctiption. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ede1a0ef0ad..16281efaac6 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ From the root folder run: npm install ``` -## Create new Project with IgniteUI CLI +## Create new Project with Ignite UI CLI To get started with the Ignite UI CLI and Ignite UI for Angular: ``` @@ -75,7 +75,7 @@ ig start ## Adding IgniteUI for Angular to Existing Project -Including the `igniteui-angular` package to your project: +Including the `igniteui-angular` and `igniteui-cli` packages to your project: ``` ng add igniteui-angular From 79c995e6f0db2770282ea07d214fb2a1cd6419fe Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 08:57:53 +0200 Subject: [PATCH 007/114] refactor(tree-grid): remove unused methods and classes, #2921 --- .../igniteui-angular/src/lib/grids/grid-base.component.ts | 3 --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 4 ---- .../services/transaction/igx-hierarchical-transaction.ts | 2 +- .../src/lib/services/transaction/transaction.ts | 7 ------- 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index a37eb5ede07..f681aae2cac 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -4477,9 +4477,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements return rowChanges ? Object.keys(rowChanges).length : 0; } - protected writeToData(rowIndex: number, value: any) { - mergeObjects(this.data[rowIndex], value); - } /** * TODO: Refactor * @hidden diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index f770e5c43ae..3c0dbe741e6 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -506,8 +506,4 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { templateID: 'dataRow' }; } - - protected writeToData(rowIndex: number, value: any) { - mergeObjects(this.flatData[rowIndex], value); - } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 9fbd8f14ad6..f5ab175a59d 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -1,4 +1,4 @@ -import { HierarchicalTransaction, HierarchicalState, TransactionType, HierarchicalTransactionNode } from './transaction'; +import { HierarchicalTransaction, HierarchicalState, TransactionType } from './transaction'; import { Injectable } from '@angular/core'; import { IgxTransactionService } from './igx-transaction'; diff --git a/projects/igniteui-angular/src/lib/services/transaction/transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/transaction.ts index 22dba1606d6..09164322d56 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/transaction.ts @@ -28,13 +28,6 @@ export interface HierarchicalState extends State { parentId: any; } -/** @experimental @hidden */ -export interface HierarchicalTransactionNode { - id: any; - parentId?: any; - childNodes: HierarchicalTransactionNode[]; -} - export interface TransactionService { /** * Returns whether transaction is enabled for this service From cc9e33d6b184ed94301afde806ccf7bbea4df0e2 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 008/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/core/utils.ts | 6 +- .../src/lib/data-operations/data-util.ts | 74 ++++++++++++------- .../src/lib/grids/api.service.ts | 32 ++++---- .../grids/tree-grid/tree-grid-api.service.ts | 20 +++++ .../grids/tree-grid/tree-grid.component.ts | 14 ++-- .../lib/grids/tree-grid/tree-grid.pipes.ts | 9 ++- .../igx-hierarchical-transaction.ts | 6 +- .../lib/services/transaction/transaction.ts | 4 +- src/app/app.module.ts | 2 + src/app/tree-grid/tree-grid.sample.html | 24 +++--- 10 files changed, 125 insertions(+), 66 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/utils.ts b/projects/igniteui-angular/src/lib/core/utils.ts index c6209cde679..cb22db31cdb 100644 --- a/projects/igniteui-angular/src/lib/core/utils.ts +++ b/projects/igniteui-angular/src/lib/core/utils.ts @@ -24,13 +24,11 @@ export function cloneHierarchicalArray(array: any[], childDataKey: any): any[] { } for (const item of array) { + const clonedItem = cloneValue(item); if (Array.isArray(item[childDataKey])) { - const clonedItem = cloneValue(item); clonedItem[childDataKey] = cloneHierarchicalArray(clonedItem[childDataKey], childDataKey); - result.push(clonedItem); - } else { - result.push(item); } + result.push(clonedItem); } return result; } diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index ac90bab6871..4c437a13b39 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -206,35 +206,59 @@ export class DataUtil { data: any[], transactions: HierarchicalTransaction[], childDataKey: any, - primaryKey?: any, - parentKey?: any): any[] { - - for (let index = 0; index < data.length; index++) { - const dataItem = data[index]; - const rowId = primaryKey ? dataItem[primaryKey] : dataItem; - const updateTransaction = transactions.filter(t => t.type === TransactionType.UPDATE).find(t => t.id === rowId); - const addedTransactions = transactions.filter(t => t.type === TransactionType.ADD).filter(t => t.parentId === rowId); - if (updateTransaction || addedTransactions.length > 0) { - data[index] = mergeObjects(cloneValue(dataItem), updateTransaction && updateTransaction.newValue); - } - if (addedTransactions.length > 0) { - if (!data[index][childDataKey]) { - data[index][childDataKey] = []; + primaryKey?: any): any[] { + + for (let i = 0; i < transactions.length; i++) { + const transaction = transactions[i]; + const path = transaction.path; + + if (path) { + // We need to get parent data row. If there is a path and path contains this row id, + // this is the case for UPDATE and DELETE transactions type, remove the last id from + // the path + if (path.find(id => id === transaction.id)) { + path.splice(-1, 1); } - for (const addedTransaction of addedTransactions) { - data[index][childDataKey].push(addedTransaction.newValue); + const dataRow = this.getDataRowFromPath(data, primaryKey, childDataKey, path); + switch (transaction.type) { + case TransactionType.ADD: + // if there is no dataRow, but there is a path this is ADD row added to + // DELETED ADD row - we just skip this + if (dataRow) { + if (!dataRow[childDataKey]) { + dataRow[childDataKey] = []; + } + if (!dataRow[childDataKey].find(r => r[primaryKey] === transaction.id)) { + dataRow[childDataKey].push(transaction.newValue); + } + } + break; + case TransactionType.UPDATE: + const index = dataRow[childDataKey].findIndex(r => r[primaryKey] === transaction.id); + const dataItem = dataRow[childDataKey][index]; + dataRow[childDataKey][index] = mergeObjects(cloneValue(dataItem), transaction.newValue); + break; } - } - if (data[index][childDataKey]) { - data[index][childDataKey] = this.mergeHierarchicalTransactions( - data[index][childDataKey], - transactions, - childDataKey, - primaryKey, - rowId - ); + } else { + // if there is no path this is ADD row in root. Push the newValue to data + data.push(transaction.newValue); } } return data; } + + private static getDataRowFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any { + let collection: any[] = data; + let result: any; + for (let i = 0; i < path.length; i++) { + const rowIndex = collection ? collection.findIndex(r => r[primaryKey] === path[i]) : undefined; + result = collection ? collection[rowIndex] : undefined; + if (!result) { + break; + } + collection = result[childDataKey]; + } + + return result; + } } diff --git a/projects/igniteui-angular/src/lib/grids/api.service.ts b/projects/igniteui-angular/src/lib/grids/api.service.ts index c0c6fa98fde..8a5d6eaeafe 100644 --- a/projects/igniteui-angular/src/lib/grids/api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/api.service.ts @@ -313,9 +313,9 @@ export class GridBaseAPIService { } const args = { rowID, - oldValue: oldValue, - newValue: editValue, - cancel: false + oldValue: oldValue, + newValue: editValue, + cancel: false }; if (cellObj) { Object.assign(args, { @@ -357,25 +357,31 @@ export class GridBaseAPIService { // if edit (new) value is same as old value do nothing here if (emittedArgs.oldValue !== undefined && isEqual(emittedArgs.oldValue, emittedArgs.newValue)) { return; } - const transaction: Transaction = { - id: rowID, type: TransactionType.UPDATE, newValue: { [column.field]: emittedArgs.newValue } - }; - if (grid.transactions.enabled) { - grid.transactions.add(transaction, currentGridEditState.rowData); - } else { - const rowValue = this.get_all_data(id)[rowIndex]; - mergeObjects(rowValue, {[column.field]: emittedArgs.newValue }); - } + const rowValue = this.get_all_data(id)[rowIndex]; + this.updateCellData(grid, rowID, rowValue, currentGridEditState.rowData, { [column.field]: emittedArgs.newValue }); if (grid.primaryKey === column.field && currentGridEditState.isRowSelected) { grid.selection.deselect_item(id, rowID); grid.selection.select_item(id, emittedArgs.newValue); } - if (!grid.rowEditable || !grid.rowInEditMode || grid.rowInEditMode.rowID !== rowID) { + if (!grid.rowEditable || !grid.rowInEditMode || grid.rowInEditMode.rowID !== rowID || !grid.transactions.enabled) { (grid as any)._pipeTrigger++; } } } + protected updateCellData(grid, rowID, rowValue: any, rowData: any, newValue: {[x: string]: any}) { + if (grid.transactions.enabled) { + const transaction: Transaction = { + id: rowID, + type: TransactionType.UPDATE, + newValue + }; + grid.transactions.add(transaction, rowData); + } else { + mergeObjects(rowValue, newValue); + } + } + public update_row(value: any, id: string, rowID: any, gridState?: { args: IGridEditEventArgs, isRowSelected: boolean, diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index d7c56066018..8cbe21d6978 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -5,6 +5,8 @@ import { ITreeGridRecord } from './tree-grid.interfaces'; import { IRowToggleEventArgs } from './tree-grid.interfaces'; import { IgxColumnComponent } from '../column.component'; import { first } from 'rxjs/operators'; +import { HierarchicalTransaction, TransactionType } from '../../services'; +import { mergeObjects } from '../../core/utils'; export class IgxTreeGridAPIService extends GridBaseAPIService { public get_all_data(id: string, transactions?: boolean): any[] { @@ -123,4 +125,22 @@ export class IgxTreeGridAPIService extends GridBaseAPIService c[this.primaryKey]).indexOf(rowID) : childData.indexOf(rowID); if (this.transactions.enabled) { + const path = [...record.path]; + path.push(rowID); this.transactions.add({ id: rowID, type: TransactionType.DELETE, newValue: null, - parentId: record.parent ? record.parent.rowID : undefined + path: path }, - this.data); + childData[index]); } else { childData.splice(index, 1); } diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 459b3f91f5b..74de241b817 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,8 +268,10 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); - if (collection && grid.transactions.enabled) { + const aggregatedChanges = grid.transactions.getAggregatedChanges(true); + if (collection && aggregatedChanges.length > 0) { const primaryKey = grid.primaryKey; if (!primaryKey) { return collection; @@ -284,9 +286,10 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { grid.transactions.getAggregatedChanges(true), grid.primaryKey); } else if (childDataKey) { + const clone = cloneHierarchicalArray(collection, childDataKey); return DataUtil.mergeHierarchicalTransactions( - cloneHierarchicalArray(collection, childDataKey), - grid.transactions.getAggregatedChanges(true), + clone, + aggregatedChanges, childDataKey, grid.primaryKey ); diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index f5ab175a59d..d7afa6f8ae9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -12,7 +12,7 @@ export class IgxHierarchicalTransactionService { const value = mergeChanges ? this.mergeValues(state.recordRef, state.value) : state.value; this.clearArraysFromObject(value); - result.push({ id: key, parentId: state.parentId, newValue: value, type: state.type } as T); + result.push({ id: key, path: state.path, newValue: value, type: state.type } as T); }); return result; } @@ -20,8 +20,8 @@ export class IgxHierarchicalTransactionService, transaction: T, recordRef?: any): void { super.updateState(states, transaction, recordRef); const currentState = states.get(transaction.id); - if (currentState && transaction.type === TransactionType.ADD) { - currentState.parentId = transaction.parentId; + if (currentState) { + currentState.path = transaction.path; } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/transaction.ts index 09164322d56..5bc55a04357 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/transaction.ts @@ -14,7 +14,7 @@ export interface Transaction { /** @experimental @hidden */ export interface HierarchicalTransaction extends Transaction { - parentId: any; + path: any[]; } export interface State { @@ -25,7 +25,7 @@ export interface State { /** @experimental @hidden */ export interface HierarchicalState extends State { - parentId: any; + path: any[]; } export interface TransactionService { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d3fd72dcfe0..b56707482dc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -76,6 +76,7 @@ import { TreeGridSampleComponent } from './tree-grid/tree-grid.sample'; import { TreeGridFlatDataSampleComponent } from './tree-grid-flat-data/tree-grid-flat-data.sample'; import { GridColumnPercentageWidthsSampleComponent } from './grid-percentage-columns/grid-percantge-widths.sample'; import { BannerSampleComponent } from './banner/banner.sample'; +import { TreeGridWithTransactionsComponent } from './tree-grid/tree-grid-with-transactions.component'; const components = [ AppComponent, @@ -134,6 +135,7 @@ const components = [ GridWithTransactionsComponent, TreeGridSampleComponent, TreeGridFlatDataSampleComponent, + TreeGridWithTransactionsComponent, CustomContentComponent, ColorsSampleComponent, ShadowsSampleComponent, diff --git a/src/app/tree-grid/tree-grid.sample.html b/src/app/tree-grid/tree-grid.sample.html index edd5d50e4a4..a1418693e32 100644 --- a/src/app/tree-grid/tree-grid.sample.html +++ b/src/app/tree-grid/tree-grid.sample.html @@ -10,16 +10,18 @@
- - - - + + + + + +
Enable Paging @@ -32,4 +34,4 @@
- + \ No newline at end of file From 03db45979c7fa6e9cc8b6fc92748a8080b0ad9bc Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:46:13 +0200 Subject: [PATCH 009/114] chore(tree-grid): add missed sample component --- .../tree-grid/tree-grid-with-transactions.component.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/app/tree-grid/tree-grid-with-transactions.component.ts diff --git a/src/app/tree-grid/tree-grid-with-transactions.component.ts b/src/app/tree-grid/tree-grid-with-transactions.component.ts new file mode 100644 index 00000000000..ad83afcca24 --- /dev/null +++ b/src/app/tree-grid/tree-grid-with-transactions.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; +import { IgxGridTransaction, IgxHierarchicalTransactionService } from 'igniteui-angular'; + +@Component({ + selector: 'app-tree-grid-with-transactions', + template: '', + providers: [{ provide: IgxGridTransaction, useClass: IgxHierarchicalTransactionService }] +}) +export class TreeGridWithTransactionsComponent { } From 5781a744a303ac6ea13262b16187bb93b00e4873 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 010/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 74de241b817..20bf2678d1a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); const aggregatedChanges = grid.transactions.getAggregatedChanges(true); if (collection && aggregatedChanges.length > 0) { From 48cbb619f51ae6d8e176f092bfd1b7660f56a6c1 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 011/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../grids/tree-grid/tree-grid.component.ts | 49 ++++++++++++------- .../igx-hierarchical-transaction.ts | 7 +++ .../services/transaction/igx-transaction.ts | 2 +- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 2b3cf8372ce..f75c5c5f438 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -368,8 +369,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { const childKey = this.childDataKey; if (this.transactions.enabled) { const rowId = this.primaryKey ? data[this.primaryKey] : data; - const path = [...parentRecord.path]; + const path: any[] = []; path.push(parentRowID); + path.push(...this.getPath(rowId)); this.transactions.add({ id: rowId, path: path, @@ -395,34 +397,32 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } - /** - * @hidden - */ - public deleteRowById(rowId: any) { - if (this.transactions.enabled && this.foreignKey && this.cascadeOnDelete) { - this.transactions.startPending(); - } - - super.deleteRowById(rowId); - - if (this.transactions.enabled && this.foreignKey && this.cascadeOnDelete) { - this.transactions.endPending(true); - } - } - /** * @hidden */ protected deleteRowFromData(rowID: any, index: number) { if (this.primaryKey && this.foreignKey) { - super.deleteRowFromData(rowID, index); + if (this.transactions.enabled) { + const path = this.getPath(rowID); + const transaction: HierarchicalTransaction = { id: rowID, type: TransactionType.DELETE, newValue: null, path }; + let recordRef = this.data[index]; + if (!recordRef) { + const state: HierarchicalState = this.transactions.getState(rowID); + recordRef = state && state.recordRef; + } + this.transactions.add(transaction, recordRef, this._useInUndoStack); + } else { + super.deleteRowFromData(rowID, index); + } if (this.cascadeOnDelete) { const treeRecord = this.records.get(rowID); if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } @@ -432,8 +432,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { index = this.primaryKey ? childData.map(c => c[this.primaryKey]).indexOf(rowID) : childData.indexOf(rowID); if (this.transactions.enabled) { - const path = [...record.path]; - path.push(rowID); + const path = this.getPath(rowID); this.transactions.add({ id: rowID, type: TransactionType.DELETE, @@ -447,6 +446,18 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } + private getPath(rowId: any): any[] { + const path: any[] = []; + let record = this.records.get(rowId); + + while (record.parent) { + path.push(record.parent.rowID); + record = record.parent; + } + + return path; + } + /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index d7afa6f8ae9..e2df2864f8a 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -7,6 +7,13 @@ import { IgxTransactionService } from './igx-transaction'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 9363470f940..410f1cd48c2 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -26,7 +26,7 @@ export class IgxTransactionService exten this.addTransaction(transaction, states, recordRef); } - private addTransaction(transaction: T, states: Map, recordRef?: any, useInUndo: boolean = true) { + protected addTransaction(transaction: T, states: Map, recordRef?: any, useInUndo: boolean = true) { this.updateState(states, transaction, recordRef); const transactions = this._isPending ? this._pendingTransactions : this._transactions; From 7bd14dec09557da0326619b325d54b14605becbc Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 012/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/data-operations/data-util.ts | 1 + .../grids/tree-grid/tree-grid.component.ts | 56 ++++++++++-------- .../igx-hierarchical-transaction.ts | 7 --- .../services/transaction/igx-transaction.ts | 58 +++++++++---------- 4 files changed, 60 insertions(+), 62 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 4c437a13b39..7327311c745 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -231,6 +231,7 @@ export class DataUtil { if (!dataRow[childDataKey].find(r => r[primaryKey] === transaction.id)) { dataRow[childDataKey].push(transaction.newValue); } + } else { } break; case TransactionType.UPDATE: diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index f75c5c5f438..a22aa74c18f 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -52,12 +52,11 @@ let NEXT_ID = 0; preserveWhitespaces: false, selector: 'igx-tree-grid', templateUrl: 'tree-grid.component.html', - providers: [ IgxGridNavigationService, { provide: GridBaseAPIService, useClass: IgxTreeGridAPIService }, + providers: [IgxGridNavigationService, { provide: GridBaseAPIService, useClass: IgxTreeGridAPIService }, { provide: IgxGridBaseComponent, useExisting: forwardRef(() => IgxTreeGridComponent) }, IgxFilteringService] }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -184,7 +183,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { this.cdr.markForCheck(); } - private _expansionStates: Map = new Map(); + private _expansionStates: Map = new Map(); /** * Returns a list of key-value pairs [row ID, expansion state]. Includes only states that differ from the default one. @@ -267,12 +266,12 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { return false; } - private cloneMap(mapIn: Map): Map { + private cloneMap(mapIn: Map): Map { const mapCloned: Map = new Map(); mapIn.forEach((value: boolean, key: any, mapObj: Map) => { - mapCloned.set(key, value); + mapCloned.set(key, value); }); return mapCloned; @@ -371,7 +370,8 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { const rowId = this.primaryKey ? data[this.primaryKey] : data; const path: any[] = []; path.push(parentRowID); - path.push(...this.getPath(rowId)); + path.push(...this.getPath(parentRowID)); + path.reverse(); this.transactions.add({ id: rowId, path: path, @@ -397,36 +397,44 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } + /** + * Removes the `IgxGridRowComponent` and the corresponding data record by primary key. + * Requires that the `primaryKey` property is set. + * The method accept rowSelector as a parameter, which is the rowID. + * ```typescript + * this.grid1.deleteRow(0); + * ``` + * @param rowSelector + * @memberof IgxGridBaseComponent + */ + public deleteRowById(rowId: any) { + if (this.primaryKey && this.foreignKey && this.cascadeOnDelete && this.transactions.enabled) { + this.transactions.startPending(); + } + super.deleteRowById(rowId); + + if (this.primaryKey && this.foreignKey && this.cascadeOnDelete && this.transactions.enabled) { + this.transactions.endPending(true); + } + } + /** * @hidden */ protected deleteRowFromData(rowID: any, index: number) { - if (this.primaryKey && this.foreignKey) { - if (this.transactions.enabled) { - const path = this.getPath(rowID); - const transaction: HierarchicalTransaction = { id: rowID, type: TransactionType.DELETE, newValue: null, path }; - let recordRef = this.data[index]; - if (!recordRef) { - const state: HierarchicalState = this.transactions.getState(rowID); - recordRef = state && state.recordRef; - } - this.transactions.add(transaction, recordRef, this._useInUndoStack); - } else { - super.deleteRowFromData(rowID, index); - } + if (this.primaryKey && this.foreignKey) { + super.deleteRowFromData(rowID, index); if (this.cascadeOnDelete) { const treeRecord = this.records.get(rowID); if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } - } else { + } else { const record = this.records.get(rowID); const childData = record.parent ? record.parent.data[this.childDataKey] : this.data; index = this.primaryKey ? childData.map(c => c[this.primaryKey]).indexOf(rowID) : @@ -439,7 +447,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { newValue: null, path: path }, - childData[index]); + childData[index]); } else { childData.splice(index, 1); } @@ -515,7 +523,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { /** * @hidden */ - public getContext(rowData): any { + public getContext(rowData): any { return { $implicit: rowData, templateID: 'dataRow' diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index e2df2864f8a..d7afa6f8ae9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -7,13 +7,6 @@ import { IgxTransactionService } from './igx-transaction'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 410f1cd48c2..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -6,8 +6,8 @@ import { isObject, mergeObjects, cloneValue } from '../../core/utils'; @Injectable() export class IgxTransactionService extends IgxBaseTransactionService { protected _transactions: T[] = []; - protected _redoStack: { transaction: T, recordRef: any, useInUndo?: boolean }[] = []; - protected _undoStack: { transaction: T, recordRef: any, useInUndo?: boolean }[] = []; + protected _redoStack: { transaction: T, recordRef: any }[][] = []; + protected _undoStack: { transaction: T, recordRef: any }[][] = []; protected _states: Map = new Map(); get canUndo(): boolean { @@ -26,14 +26,14 @@ export class IgxTransactionService exten this.addTransaction(transaction, states, recordRef); } - protected addTransaction(transaction: T, states: Map, recordRef?: any, useInUndo: boolean = true) { + protected addTransaction(transaction: T, states: Map, recordRef?: any) { this.updateState(states, transaction, recordRef); const transactions = this._isPending ? this._pendingTransactions : this._transactions; transactions.push(transaction); if (!this._isPending) { - this._undoStack.push({ transaction, recordRef, useInUndo }); + this._undoStack.push([{ transaction, recordRef }]); this._redoStack = []; this.onStateUpdate.emit(); } @@ -85,13 +85,20 @@ export class IgxTransactionService exten public endPending(commit: boolean): void { this._isPending = false; if (commit) { - let i = 0; - this._pendingStates.forEach((s: S, k: any) => { - this.addTransaction({ id: k, newValue: s.value, type: s.type } as T, this._states, s.recordRef, i === 0); - i++; - }); + const actions: { transaction: T, recordRef: any }[] = []; + for (let i = 0; i < this._pendingTransactions.length; i++) { + const transaction: T = this._pendingTransactions[i]; + const pendingState = this._pendingStates.get(transaction.id); + this._transactions.push(transaction); + this.updateState(this._states, transaction, pendingState.recordRef); + actions.push({ transaction, recordRef: pendingState.recordRef }); + } + + this._undoStack.push(actions); + this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { @@ -129,35 +136,24 @@ export class IgxTransactionService exten return; } - let action: { transaction: T, recordRef: any, useInUndo?: boolean }; - do { - action = this._undoStack.pop(); - this._transactions.pop(); - this._redoStack.push(action); - } while (!action.useInUndo); + const actions: { transaction: T, recordRef: any }[] = this._undoStack.pop(); + this._transactions.splice(this._transactions.length - actions.length); + this._redoStack.push(actions); this._states.clear(); - this._undoStack.map(a => this.updateState(this._states, a.transaction, a.recordRef)); + this._undoStack.map(a => a.map(t => this.updateState(this._states, t.transaction, t.recordRef))); this.onStateUpdate.emit(); } public redo(): void { if (this._redoStack.length > 0) { - // remove first item from redo stack (it should always has useInUndo === true) - // and then all next items until there are items and useInUndo === false. - // If there are no more items, or next item's useInUndo === true leave. - let undoItem: { transaction: T, recordRef: any, useInUndo?: boolean }; - undoItem = this._redoStack.pop(); - this.updateState(this._states, undoItem.transaction, undoItem.recordRef); - this._transactions.push(undoItem.transaction); - this._undoStack.push(undoItem); - - while (this._redoStack[this._redoStack.length - 1] && !this._redoStack[this._redoStack.length - 1].useInUndo) { - undoItem = this._redoStack.pop(); - this.updateState(this._states, undoItem.transaction, undoItem.recordRef); - this._transactions.push(undoItem.transaction); - this._undoStack.push(undoItem); - } + let actions: { transaction: T, recordRef: any, useInUndo?: boolean }[]; + actions = this._redoStack.pop(); + actions.map(a => { + this.updateState(this._states, a.transaction, a.recordRef); + this._transactions.push(a.transaction); + }); + this._undoStack.push(actions); this.onStateUpdate.emit(); } } From a75f94729d20d0725a2fbd0a934062d5d9c0d821 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 013/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From 1b479be895996948b0cc86b2608be1dde3ffb39f Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 014/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 20bf2678d1a..74de241b817 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,6 +268,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); const aggregatedChanges = grid.transactions.getAggregatedChanges(true); if (collection && aggregatedChanges.length > 0) { From 9f40364c87231d39f62c6af228ccc66cc71b3425 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 015/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 74de241b817..20bf2678d1a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); const aggregatedChanges = grid.transactions.getAggregatedChanges(true); if (collection && aggregatedChanges.length > 0) { From 92901bf83e20f9b09d27a7c3980ee5c5e619bacf Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 016/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index a22aa74c18f..376a6e376fd 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index d7afa6f8ae9..e2df2864f8a 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -7,6 +7,13 @@ import { IgxTransactionService } from './igx-transaction'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From baba9d8979f9385e076d945bea7aee8a6fa98df3 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 017/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 376a6e376fd..a22aa74c18f 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index e2df2864f8a..d7afa6f8ae9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -7,13 +7,6 @@ import { IgxTransactionService } from './igx-transaction'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From ba91d22348662c5f4a321837cea22ac5e75ada1b Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 018/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From 6a19081b634655e4b09f9abad3ff5967fb9f391d Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 16:05:34 +0200 Subject: [PATCH 019/114] test(tree-grid): fix failing test in transactions, #2921 --- .../src/lib/services/transaction/igx-transaction.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts index 4044f203e41..fd527da29d7 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts @@ -571,7 +571,11 @@ describe('IgxTransaction', () => { [ { id: 'Key1', - newValue: { key: 'Key1', value1: 10, value3: 30 }, + newValue: { key: 'Key1', value1: 10 }, + type: 'update' + }, { + id: 'Key1', + newValue: { key: 'Key1', value3: 30 }, type: 'update' } ]); From 0a0dcfec948908aad82196422008b449aaffd530 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 17:52:33 +0200 Subject: [PATCH 020/114] feat(tree-grid): update states in hierarchical data, #2921 If deleted transaction is added we are checking all current states. If any of them is for row which is child of the deleted row we are updating its state. --- .../tree-grid/tree-grid-row.component.ts | 22 ++++++++++--------- .../igx-hierarchical-transaction.ts | 15 +++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts index 6b73256892c..f96f54714ad 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts @@ -89,22 +89,24 @@ export class IgxTreeGridRowComponent extends IgxRowComponent { + if (v.path.indexOf(transaction.id) !== -1) { + switch (v.type) { + case TransactionType.ADD: + states.delete(k); + break; + case TransactionType.UPDATE: + states.get(k).type = TransactionType.DELETE; + states.get(k).value = null; + } + } + }); + } } // TODO: remove this method. Force cloning to strip child arrays when needed instead From 1302816f639aa718639a1ad512d5cb2aab958e32 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 14 Nov 2018 12:04:54 +0200 Subject: [PATCH 021/114] feat(tree-grid): hierarchical transaction are in stable state now, #2921 ADD, UPDATE, DELETE, UNDO, REDO and COMMIT should working now for both flat and hierarchical data source --- .../src/lib/data-operations/data-util.ts | 31 +++++++++--- .../grids/tree-grid/tree-grid-api.service.ts | 4 +- .../grids/tree-grid/tree-grid.component.ts | 7 +-- .../lib/grids/tree-grid/tree-grid.pipes.ts | 50 ++++++++++--------- .../igx-hierarchical-transaction.ts | 6 +++ .../tree-grid-flat-data.sample.html | 3 ++ .../tree-grid-flat-data.sample.ts | 16 +++++- src/app/tree-grid/tree-grid.sample.html | 3 ++ src/app/tree-grid/tree-grid.sample.ts | 12 +++++ 9 files changed, 94 insertions(+), 38 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 7327311c745..aa547106e58 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -206,7 +206,8 @@ export class DataUtil { data: any[], transactions: HierarchicalTransaction[], childDataKey: any, - primaryKey?: any): any[] { + primaryKey?: any, + deleteRows: boolean = false): any[] { for (let i = 0; i < transactions.length; i++) { const transaction = transactions[i]; @@ -219,7 +220,7 @@ export class DataUtil { if (path.find(id => id === transaction.id)) { path.splice(-1, 1); } - const dataRow = this.getDataRowFromPath(data, primaryKey, childDataKey, path); + const dataRow = this.findDataRowFromPath(data, primaryKey, childDataKey, path); switch (transaction.type) { case TransactionType.ADD: // if there is no dataRow, but there is a path this is ADD row added to @@ -235,9 +236,16 @@ export class DataUtil { } break; case TransactionType.UPDATE: - const index = dataRow[childDataKey].findIndex(r => r[primaryKey] === transaction.id); - const dataItem = dataRow[childDataKey][index]; - dataRow[childDataKey][index] = mergeObjects(cloneValue(dataItem), transaction.newValue); + const collectionToUpdate: any[] = this.findCollectionToManipulate(childDataKey, dataRow, data); + const updateIndex: number = collectionToUpdate.findIndex(r => r[primaryKey] === transaction.id); + collectionToUpdate[updateIndex] = mergeObjects(cloneValue(collectionToUpdate[updateIndex]), transaction.newValue); + break; + case TransactionType.DELETE: + if (deleteRows) { + const collectionToDelete: any[] = this.findCollectionToManipulate(childDataKey, dataRow, data); + const deleteIndex: number = collectionToDelete.findIndex(r => r[primaryKey] === transaction.id); + collectionToDelete.splice(deleteIndex, 1); + } break; } } else { @@ -248,7 +256,7 @@ export class DataUtil { return data; } - private static getDataRowFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any { + private static findDataRowFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any { let collection: any[] = data; let result: any; for (let i = 0; i < path.length; i++) { @@ -262,4 +270,15 @@ export class DataUtil { return result; } + + private static findCollectionToManipulate(childDataKey: any, dataRow: any, data: any[]): any[] { + let result: any[]; + if (dataRow && dataRow[childDataKey]) { + result = dataRow[childDataKey]; + } else { + result = data; + } + + return result; + } } diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index 8cbe21d6978..47ed57ad942 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -128,9 +128,7 @@ export class IgxTreeGridAPIService extends GridBaseAPIService c[this.primaryKey]).indexOf(rowID) : childData.indexOf(rowID); if (this.transactions.enabled) { - const path = this.getPath(rowID); + const path = this.generateRowPath(rowID); this.transactions.add({ id: rowID, type: TransactionType.DELETE, @@ -454,7 +454,8 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } - private getPath(rowId: any): any[] { + /** @hidden */ + public generateRowPath(rowId: any): any[] { const path: any[] = []; let record = this.records.get(rowId); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 20bf2678d1a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -269,31 +269,33 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { transform(collection: any[], id: string, pipeTrigger: number): any[] { const grid: IgxTreeGridComponent = this.gridAPI.get(id); - const aggregatedChanges = grid.transactions.getAggregatedChanges(true); - if (collection && aggregatedChanges.length > 0) { - const primaryKey = grid.primaryKey; - if (!primaryKey) { - return collection; - } + if (collection && collection.length > 0 && grid.transactions.enabled) { + const aggregatedChanges = grid.transactions.getAggregatedChanges(true); + if (aggregatedChanges.length > 0) { + const primaryKey = grid.primaryKey; + if (!primaryKey) { + return collection; + } - const foreignKey = grid.foreignKey; - const childDataKey = grid.childDataKey; - - if (foreignKey) { - return DataUtil.mergeTransactions( - cloneArray(collection), - grid.transactions.getAggregatedChanges(true), - grid.primaryKey); - } else if (childDataKey) { - const clone = cloneHierarchicalArray(collection, childDataKey); - return DataUtil.mergeHierarchicalTransactions( - clone, - aggregatedChanges, - childDataKey, - grid.primaryKey - ); - } - } + const foreignKey = grid.foreignKey; + const childDataKey = grid.childDataKey; + + if (foreignKey) { + return DataUtil.mergeTransactions( + cloneArray(collection), + grid.transactions.getAggregatedChanges(true), + grid.primaryKey); + } else if (childDataKey) { + const clone = cloneHierarchicalArray(collection, childDataKey); + return DataUtil.mergeHierarchicalTransactions( + clone, + aggregatedChanges, + childDataKey, + grid.primaryKey + ); + } + } + } return collection; } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index f27df2edfdb..a0f6bc3769a 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -1,6 +1,7 @@ import { HierarchicalTransaction, HierarchicalState, TransactionType } from './transaction'; import { Injectable } from '@angular/core'; import { IgxTransactionService } from './igx-transaction'; +import { DataUtil } from 'igniteui-angular'; /** @experimental @hidden */ @Injectable() @@ -40,6 +41,11 @@ export class IgxHierarchicalTransactionServiceAdd row + + + diff --git a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts index ac00af58e41..7bba3140c44 100644 --- a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts +++ b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts @@ -1,9 +1,9 @@ import { Component, Injectable, ViewChild, OnInit } from '@angular/core'; import { Http } from '@angular/http'; -import { IgxTreeGridComponent } from 'igniteui-angular'; +import { IgxTreeGridComponent, IgxHierarchicalTransactionService, IgxGridTransaction } from 'igniteui-angular'; @Component({ - providers: [], + providers: [{provide: IgxGridTransaction, useClass: IgxHierarchicalTransactionService}], selector: 'app-tree-grid-flat-data-sample', styleUrls: ['tree-grid-flat-data.sample.css'], templateUrl: 'tree-grid-flat-data.sample.html' @@ -90,4 +90,16 @@ export class TreeGridFlatDataSampleComponent implements OnInit { public selectDensity(event) { this.density = this.displayDensities[event.index].label; } + + public undo() { + this.grid1.transactions.undo(); + } + + public redo() { + this.grid1.transactions.redo(); + } + + public commit() { + this.grid1.transactions.commit(this.data); + } } diff --git a/src/app/tree-grid/tree-grid.sample.html b/src/app/tree-grid/tree-grid.sample.html index a1418693e32..a6655054fe3 100644 --- a/src/app/tree-grid/tree-grid.sample.html +++ b/src/app/tree-grid/tree-grid.sample.html @@ -30,6 +30,9 @@ + + + diff --git a/src/app/tree-grid/tree-grid.sample.ts b/src/app/tree-grid/tree-grid.sample.ts index 487aab838f5..6f1c508e006 100644 --- a/src/app/tree-grid/tree-grid.sample.ts +++ b/src/app/tree-grid/tree-grid.sample.ts @@ -449,4 +449,16 @@ export class TreeGridSampleComponent implements OnInit { public deleteRow() { this.grid1.deleteRowById(this.grid1.selectedRows()[0]); } + + public undo() { + this.grid1.transactions.undo(); + } + + public redo() { + this.grid1.transactions.redo(); + } + + public commit() { + this.grid1.transactions.commit(this.data, this.grid1.childDataKey, this.grid1.primaryKey); + } } From 4c10e24c9bcb463a504cf35b316b0eda4d1ef84c Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 022/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index b64604ee4e7..af9041bf34a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,6 +268,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From eccd29dba76c2300e910282316fad7c34bd8d07d Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 023/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index af9041bf34a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 3097fada2c2300c09063b4f10540706eab675c58 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 024/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..38fdf68fb1b 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index a0f6bc3769a..ef481d6395f 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,6 +8,13 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From cd4f61d053ea3052fe1ec893db832eb5e8da6816 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 025/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 38fdf68fb1b..3365d8b6c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index ef481d6395f..a0f6bc3769a 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,13 +8,6 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From 5c02658100b6a54e4a744784b1d98ef929341622 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 026/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From 6330529ce2893287c395e1664504efedceeda822 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 027/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../src/lib/grids/tree-grid/tree-grid-api.service.ts | 1 - .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index 47ed57ad942..8d4baec71c7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -140,5 +140,4 @@ export class IgxTreeGridAPIService extends GridBaseAPIService 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 377a13afae93cc65d27d0e84cc0676b1c1293c73 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 028/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index af9041bf34a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From d61d2532e9224f571a00613c6f60b64f19cc0bfa Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 029/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..38fdf68fb1b 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index a0f6bc3769a..ef481d6395f 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,6 +8,13 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From dfc3a116709a9e99fe785a8002c6535c282bae37 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 030/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 38fdf68fb1b..3365d8b6c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index ef481d6395f..a0f6bc3769a 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,13 +8,6 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From 2cf27433eea354561ecf39d573ff039a3709eda3 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 031/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From e501142866ec8deeec0f1825b3db588671b67ce7 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 17:52:33 +0200 Subject: [PATCH 032/114] feat(tree-grid): update states in hierarchical data, #2921 If deleted transaction is added we are checking all current states. If any of them is for row which is child of the deleted row we are updating its state. --- .../src/lib/services/transaction/igx-hierarchical-transaction.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index a0f6bc3769a..93343b7725b 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -55,3 +55,4 @@ export class IgxHierarchicalTransactionService Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 033/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index b64604ee4e7..af9041bf34a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,6 +268,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From fa88cb07a365d586707dc0decdd5d81f88f79433 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 034/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index af9041bf34a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 432b928a1c06c0a2951c3446d0173b80692492fe Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 035/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..38fdf68fb1b 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 93343b7725b..38c58f9f155 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,6 +8,13 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From 4a1be5393b034f727699d3a051cbaff0a4eba7b9 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 036/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 38fdf68fb1b..3365d8b6c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 38c58f9f155..93343b7725b 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,13 +8,6 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From d7e790bca6a49db27deebb19dd3e746256cff3d5 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 037/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From c955cc39a8a9d120f49e7e7cdf26f500ed5a8da8 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 038/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index b64604ee4e7..af9041bf34a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,6 +268,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 4e4994ad2c0fab05be71234fc6cc82dd0cc6bffc Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 039/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index af9041bf34a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 4477dcb8766b68fb97f8ea4222fe728380649d72 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 040/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..38fdf68fb1b 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 93343b7725b..38c58f9f155 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,6 +8,13 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From 71cc6d6f69914788d644f34ea3aa2ed61ff74148 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 041/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 38fdf68fb1b..3365d8b6c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 38c58f9f155..93343b7725b 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,13 +8,6 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From dce642b32122325ad796d7b15937d623c7bdd589 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 042/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From 5c5ce80f3252bd976557bcc7ebb0933a84ba0aa4 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 043/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index b64604ee4e7..af9041bf34a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,6 +268,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From a5c5349606f2e2745904f7f5ec1c2cbd9c4e361c Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 044/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index af9041bf34a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 9375ed7803109a41ab4acfa664829bbdafadcee1 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 045/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..38fdf68fb1b 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 93343b7725b..38c58f9f155 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,6 +8,13 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From 60d4bb86d1060b22af6fa6159ae9cf1f12383a1b Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 046/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 38fdf68fb1b..3365d8b6c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 38c58f9f155..93343b7725b 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,13 +8,6 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From 8b7f90ec772f602e8a6e0f650f729602a1093efa Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 047/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From 24b06871f743088f953126a2bd973cbd053ef556 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:34:35 +0200 Subject: [PATCH 048/114] refactor(tree-grid): apply transaction in one iteration, #2921 By the help of transaction.path we are able to going trough all the changes now and update the cloned data source without need of recursion. --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index b64604ee4e7..af9041bf34a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,6 +268,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { + console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From c35720772efd4c16e9e6640d43505afa2c73e804 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 7 Nov 2018 16:54:44 +0200 Subject: [PATCH 049/114] chore(tree-grid): remove console.log from tree-grid.pipes --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index af9041bf34a..b64604ee4e7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -268,7 +268,6 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { } transform(collection: any[], id: string, pipeTrigger: number): any[] { - console.log(collection); const grid: IgxTreeGridComponent = this.gridAPI.get(id); if (collection && collection.length > 0 && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); From 27c36fc57e06b6d6f9e05cb45c0fa232ce44d45b Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 10:34:49 +0200 Subject: [PATCH 050/114] feat(tree-grid): update flat data transaction, #2921 _useInUndo flag added to IgxTreeGrid. When cascading delete rows in flat data set this flag to false and push add it this way in transaction. This allow undo to skip this transactions. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 +++ .../services/transaction/igx-hierarchical-transaction.ts | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..38fdf68fb1b 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,6 +57,7 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; + private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -430,7 +431,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; + this._useInUndoStack = false; super.deleteRowById(child.rowID); + this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 93343b7725b..38c58f9f155 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,6 +8,13 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { + + public add(transaction: T, recordRef?: any, useInUndo = true): void { + const states = this._isPending ? this._pendingStates : this._states; + this.verifyAddedTransaction(states, transaction, recordRef); + super.addTransaction(transaction, states, recordRef, useInUndo); + } + public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { From d5bbcaad64c179e5200b4daba974e6513c624190 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 14:48:10 +0200 Subject: [PATCH 051/114] feat(tree-grid): update flat data transaction, #2921 We are returning to startPending/endPending approach. This time on each startPending we are pushing array of transactions to undo stack. If undo gets called we remove the last array of transactions, splice the transactions array, update the necessary states and push the array of transactions to redo stack. --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 ------- .../src/lib/services/transaction/igx-transaction.ts | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 38fdf68fb1b..3365d8b6c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -57,7 +57,6 @@ let NEXT_ID = 0; }) export class IgxTreeGridComponent extends IgxGridBaseComponent { private _id = `igx-tree-grid-${NEXT_ID++}`; - private _useInUndoStack = true; /** * An @Input property that sets the value of the `id` attribute. If not provided it will be automatically generated. @@ -431,9 +430,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { if (treeRecord && treeRecord.children && treeRecord.children.length > 0) { for (let i = 0; i < treeRecord.children.length; i++) { const child = treeRecord.children[i]; - this._useInUndoStack = false; super.deleteRowById(child.rowID); - this._useInUndoStack = true; } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 38c58f9f155..93343b7725b 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -8,13 +8,6 @@ import { DataUtil } from 'igniteui-angular'; export class IgxHierarchicalTransactionService extends IgxTransactionService { - - public add(transaction: T, recordRef?: any, useInUndo = true): void { - const states = this._isPending ? this._pendingStates : this._states; - this.verifyAddedTransaction(states, transaction, recordRef); - super.addTransaction(transaction, states, recordRef, useInUndo); - } - public getAggregatedChanges(mergeChanges: boolean): T[] { const result: T[] = []; this._states.forEach((state: S, key: any) => { diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..30c788982d9 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,6 +98,7 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); + this._useInUndo = true; } public commit(data: any[]): void { From 5d89ef65dcdd467ced2df542b0c56e871026c8ba Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 13 Nov 2018 15:16:00 +0200 Subject: [PATCH 052/114] chore(tree-grid): remove call to missing field in igx-transaction --- .../src/lib/services/transaction/igx-transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 30c788982d9..6a8e3caeb95 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -98,7 +98,6 @@ export class IgxTransactionService exten this._redoStack = []; } super.endPending(commit); - this._useInUndo = true; } public commit(data: any[]): void { From 388762d4588d10d0fc25e551a3988880922d7877 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 14 Nov 2018 16:30:02 +0200 Subject: [PATCH 053/114] fix(tree-grid): fix circular reference exception. #2921 --- .../services/transaction/igx-hierarchical-transaction.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 93343b7725b..66dcaf079a1 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -1,7 +1,7 @@ import { HierarchicalTransaction, HierarchicalState, TransactionType } from './transaction'; import { Injectable } from '@angular/core'; import { IgxTransactionService } from './igx-transaction'; -import { DataUtil } from 'igniteui-angular'; +import { DataUtil } from '../../data-operations/data-util'; /** @experimental @hidden */ @Injectable() @@ -41,11 +41,6 @@ export class IgxHierarchicalTransactionService Date: Mon, 19 Nov 2018 09:46:44 +0200 Subject: [PATCH 054/114] refactor(tree-grid): update mergeTransactions static method --- .../src/lib/data-operations/data-util.ts | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index aa547106e58..ade08203dfd 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -181,27 +181,45 @@ export class DataUtil { * @param data Collection to merge * @param transactions Transactions to merge into data * @param primaryKey Primary key of the collection, if any + * @param deleteRows Should delete rows with DELETE transaction type from data + * @returns Provided data collections updated with all provided transactions */ - public static mergeTransactions(data: T[], transactions: Transaction[], primaryKey?: any): T[] { + public static mergeTransactions(data: T[], transactions: Transaction[], primaryKey?: any, deleteRows: boolean = false): T[] { data.forEach((item: any, index: number) => { const rowId = primaryKey ? item[primaryKey] : item; const transaction = transactions.find(t => t.id === rowId); - if (Array.isArray(item.children)) { - this.mergeTransactions(item.children, transactions, primaryKey); - } if (transaction && transaction.type === TransactionType.UPDATE) { data[index] = transaction.newValue; } }); + if (deleteRows) { + transactions + .filter(t => t.type === TransactionType.DELETE) + .forEach(t => { + const index = primaryKey ? data.findIndex(d => d[primaryKey] === t.id) : data.findIndex(d => d === t.id); + if (0 <= index && index < data.length) { + data.splice(index, 1); + } + }); + } + data.push(...transactions .filter(t => t.type === TransactionType.ADD) .map(t => t.newValue)); - return data; + + return data; } - // TODO: optimize addition of added rows. Should not filter transaction in each recursion!!! - /** @experimental @hidden */ + /** + * Merges all changes from provided transactions into provided hierarchical data collection + * @param data Collection to merge + * @param transactions Transactions to merge into data + * @param childDataKey Data key of child collections + * @param primaryKey Primary key of the collection, if any + * @param deleteRows Should delete rows with DELETE transaction type from data + * @returns Provided data collections updated with all provided transactions + */ public static mergeHierarchicalTransactions( data: any[], transactions: HierarchicalTransaction[], From 825d54afa41d649765c96ea82cc97c8630b99983 Mon Sep 17 00:00:00 2001 From: wnvko Date: Mon, 19 Nov 2018 10:58:24 +0200 Subject: [PATCH 055/114] fix(tree-grid): delete row in flat data source, #2921 --- .../transaction/igx-hierarchical-transaction.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 66dcaf079a1..f89999296f7 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -25,19 +25,9 @@ export class IgxHierarchicalTransactionService { - if (v.path.indexOf(transaction.id) !== -1) { - switch (v.type) { - case TransactionType.ADD: - states.delete(k); - break; - case TransactionType.UPDATE: - states.get(k).type = TransactionType.DELETE; - states.get(k).value = null; - } - } - }); + // if transaction has no path, e.g. flat data source, get out + if (!transaction.path) { + return; } } From c044d4a84574b2b0b0770ec5dc968f47f39b9de5 Mon Sep 17 00:00:00 2001 From: wnvko Date: Mon, 19 Nov 2018 12:18:47 +0200 Subject: [PATCH 056/114] fix(tree-grid): update cell in hierarchical grid with row edit, #2921 --- .../src/lib/data-operations/data-util.ts | 9 +++--- .../src/lib/grids/api.service.ts | 29 ++++++++++++------- .../grids/tree-grid/tree-grid-api.service.ts | 21 +++++++++++--- .../lib/grids/tree-grid/tree-grid.pipes.ts | 24 +++++++-------- .../igx-hierarchical-transaction.ts | 4 --- 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index ade08203dfd..30d648f7b86 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -28,13 +28,13 @@ export enum DataType { * @hidden */ export class DataUtil { - public static sort(data: T[], expressions: ISortingExpression [], sorting: IgxSorting = new IgxSorting()): T[] { + public static sort(data: T[], expressions: ISortingExpression[], sorting: IgxSorting = new IgxSorting()): T[] { return sorting.sort(data, expressions); } public static treeGridSort(hierarchicalData: ITreeGridRecord[], - expressions: ISortingExpression [], - parent?: ITreeGridRecord): ITreeGridRecord[] { + expressions: ISortingExpression[], + parent?: ITreeGridRecord): ITreeGridRecord[] { let res: ITreeGridRecord[] = []; hierarchicalData.forEach((hr: ITreeGridRecord) => { @@ -208,7 +208,7 @@ export class DataUtil { .filter(t => t.type === TransactionType.ADD) .map(t => t.newValue)); - return data; + return data; } /** @@ -250,7 +250,6 @@ export class DataUtil { if (!dataRow[childDataKey].find(r => r[primaryKey] === transaction.id)) { dataRow[childDataKey].push(transaction.newValue); } - } else { } break; case TransactionType.UPDATE: diff --git a/projects/igniteui-angular/src/lib/grids/api.service.ts b/projects/igniteui-angular/src/lib/grids/api.service.ts index 8a5d6eaeafe..80e742135cd 100644 --- a/projects/igniteui-angular/src/lib/grids/api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/api.service.ts @@ -358,7 +358,7 @@ export class GridBaseAPIService { if (emittedArgs.oldValue !== undefined && isEqual(emittedArgs.oldValue, emittedArgs.newValue)) { return; } const rowValue = this.get_all_data(id)[rowIndex]; - this.updateCellData(grid, rowID, rowValue, currentGridEditState.rowData, { [column.field]: emittedArgs.newValue }); + this.updateData(grid, rowID, rowValue, currentGridEditState.rowData, { [column.field]: emittedArgs.newValue }); if (grid.primaryKey === column.field && currentGridEditState.isRowSelected) { grid.selection.deselect_item(id, rowID); grid.selection.select_item(id, emittedArgs.newValue); @@ -369,16 +369,24 @@ export class GridBaseAPIService { } } - protected updateCellData(grid, rowID, rowValue: any, rowData: any, newValue: {[x: string]: any}) { + /** + * Updates related row of provided grid's data source with provided new row value + * @param grid Grid to update data for + * @param rowID ID of the row to update + * @param rowValueInDataSource Initial value of the row as it is in data source + * @param rowCurrentValue Current value of the row as it is with applied previous transactions + * @param rowNewValue New value of the row + */ + protected updateData(grid, rowID, rowValueInDataSource: any, rowCurrentValue: any, rowNewValue: {[x: string]: any}) { if (grid.transactions.enabled) { const transaction: Transaction = { id: rowID, type: TransactionType.UPDATE, - newValue + newValue: rowNewValue }; - grid.transactions.add(transaction, rowData); + grid.transactions.add(transaction, rowCurrentValue); } else { - mergeObjects(rowValue, newValue); + mergeObjects(rowValueInDataSource, rowNewValue); } } @@ -412,11 +420,12 @@ export class GridBaseAPIService { if (currentRowInEditMode) { grid.transactions.endPending(false); } - if (grid.transactions.enabled && emitArgs.newValue !== null) { - grid.transactions.add({id: rowID, newValue: emitArgs.newValue, type: TransactionType.UPDATE}, emitArgs.oldValue); - } else if (emitArgs.newValue !== null && emitArgs.newValue !== undefined) { - Object.assign(data[index], emitArgs.newValue); - } + this.updateData(grid, rowID, data[index], emitArgs.oldValue, emitArgs.newValue); + // if (grid.transactions.enabled && emitArgs.newValue !== null) { + // grid.transactions.add({id: rowID, newValue: emitArgs.newValue, type: TransactionType.UPDATE}, emitArgs.oldValue); + // } else if (emitArgs.newValue !== null && emitArgs.newValue !== undefined) { + // Object.assign(data[index], emitArgs.newValue); + // } if (currentGridState.isRowSelected) { grid.selection.deselect_item(id, rowID); const newRowID = (grid.primaryKey) ? emitArgs.newValue[grid.primaryKey] : emitArgs.newValue; diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index 8d4baec71c7..b8982df5b19 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -126,18 +126,31 @@ export class IgxTreeGridAPIService extends GridBaseAPIService, transaction: T, recordRef?: any): void { super.updateState(states, transaction, recordRef); - const currentState = states.get(transaction.id); - if (currentState) { - currentState.path = transaction.path; - } // if transaction has no path, e.g. flat data source, get out if (!transaction.path) { From dcd8a18ee14352e41671c43008c3f374cb604e84 Mon Sep 17 00:00:00 2001 From: wnvko Date: Mon, 19 Nov 2018 14:32:33 +0200 Subject: [PATCH 057/114] refactor(tree-grid): change endRowTransaction to call endPending once, #2921 --- projects/igniteui-angular/src/lib/grids/grid-base.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index f681aae2cac..0bd1b98b75b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -4507,6 +4507,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements }); if (!commit) { this.onRowEditCancel.emit(emitArgs); + this.transactions.endPending(commit); } else { this.gridAPI.update_row(emitArgs.newValue, this.id, rowID, currentGridState); } @@ -4514,7 +4515,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.transactions.startPending(); return; } - this.transactions.endPending(commit); this.closeRowEditingOverlay(); } From 77e5f91695ffe8f081816472cd08ece59292cd23 Mon Sep 17 00:00:00 2001 From: wnvko Date: Mon, 19 Nov 2018 16:34:31 +0200 Subject: [PATCH 058/114] fix(tree-grid): allow deletion of add root row, #2921 --- .../src/lib/grids/tree-grid/tree-grid.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 3365d8b6c6e..24ce1bf021a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -436,7 +436,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } else { const record = this.records.get(rowID); - const childData = record.parent ? record.parent.data[this.childDataKey] : this.data; + const childData = record.parent ? record.parent.data[this.childDataKey] : this._gridAPI.get_all_data(this.id); index = this.primaryKey ? childData.map(c => c[this.primaryKey]).indexOf(rowID) : childData.indexOf(rowID); if (this.transactions.enabled) { From e5faf609355d10d152bc29e058ba1365b73d9101 Mon Sep 17 00:00:00 2001 From: AMarinov Date: Mon, 19 Nov 2018 15:15:59 +0200 Subject: [PATCH 059/114] test(row-editing): Adding test scenarios #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 517bb099940..30dd0800541 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -511,6 +511,38 @@ describe('IgxTreeGrid - Integration', () => { const editedParentCell = parentRow.cells.filter(c => c.column.field === 'Age')[0]; expect(editedParentCell.value).toEqual(80); }); + + it('Children are transformed into parent nodes after their parent is deleted', () => { + // TODO: + // 1. Set 'Cascade On Delete' to false on a grid with Flat DS + // 2. Delete a parent node + // 3. Verify the correct style is applied before committing + // 4. Commit changes + // 5. Verify the correct style is applied after committing + // 6. Verify its children are transformed into parent nodes + // and are placed at the correct place in the grid + // 7. Verify the undo stack is empty + }); + + it('Children are deleted along with their parent', () => { + // TODO: + // 1. Set 'Cascade On Delete' to true on a grid with Flat DS + // 2. Delete a parent node + // 3. Verify the correct style is applied before committing to all nodes + // 4. Commit changes + // 5. Verify the parent node and its children are deleted + // 6. Verify the undo stack is empty + }); + + it('Editing a cell is posible with Hierarchical DS', () => { + // TODO: + // 1. Enter row edit mode in a gridwith Hierarchical DS + // 2. Update a cell + // 3. Press ENTER or click Done + // 4. Verify the value is updated and the correct style is applied before committing + // 5. Commit + // 6. Verify the correct value is set + }); }); describe('Multi-column header', () => { From 2630d01f65cf7002047ae51dc099c68b11c74c9a Mon Sep 17 00:00:00 2001 From: AMarinov Date: Mon, 19 Nov 2018 15:59:08 +0200 Subject: [PATCH 060/114] test(row-editing): Adding more scenarios ##2921 --- .../tree-grid/tree-grid-integration.spec.ts | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 30dd0800541..24faa2d387c 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -543,6 +543,103 @@ describe('IgxTreeGrid - Integration', () => { // 5. Commit // 6. Verify the correct value is set }); + + it('Undo/Redo keeps the correct number of steps with Hierarchical DS', () => { + // TODO: + // 1. Update a cell in three different rows + // 2. Execute "Undo" three times + // 3. Verify the initial state is shown + // 4. Execute "Redo" three times + // 5. Verify all the updates are shown with correct styles + // 6. Press "Commit" + // 7. Verify the changes are comitted + }); + + it('Add parent node to a Flat DS tree grid', () => { + // TODO: + // 1. Add a row at level 0 to the grid + // 2. Verify the new row is pending with the correct styles + // 3. Commit + // 4. verify the row is committed, the styles are OK and the Undo stack is empty + // 5. Add another row at level 0 + // 6. verify the pending styles is applied only to the newly added row + // and not to the previously added row + }); + + it('Add parent node to a Hierarchical DS tree grid', () => { + // TODO: + // 1. Add a row at level 0 to the grid + // 2. Verify the new row is pending with the correct styles + // 3. Commit + // 4. Verify the row is committed, the styles are OK and the Undo stack is empty + // 5. Add another row at level 0 + // 6. verify the pending styles is applied only to the newly added row + // and not to the previously added row + }); + + it('Add a child node to a previously added parent node - Flat DS', () => { + // TODO: + // 1. Add a row at level 0 to the grid + // 2. Add a child row to that parent + // 3. Verify the new rows are pending with the correct styles + // 4. Commit + // 5. verify the rows are committed, the styles are OK + // 6. Add another child row at level 2 (grand-child of the first row) + // 7. verify the pending styles is applied only to the newly added row + // and not to the previously added rows + }); + + it('Add a child node to a previously added parent node - Hierarchical DS', () => { + // TODO: + // 1. Add a row at level 0 to the grid + // 2. Add a child row to that parent + // 3. Verify the new rows are pending with the correct styles + // 4. Commit + // 5. verify the rows are committed, the styles are OK + // 6. Add another child row at level 2 (grand-child of the first row) + // 7. verify the pending styles is applied only to the newly added row + // and not to the previously added rows + }); + + it('Delete a pending parent node - Flat DS', () => { + // TODO: + // 1. Add a row at level 0 to the grid + // 2. Select it + // 3. Delete the selected row + // 4. Verify the row is deleted + // 5. Undo + // 6. Verify the row is visible and pending again + }); + + it('Delete a pending parent node - Hierarchical DS', () => { + // TODO: + // 1. Add a row at level 0 to the grid + // 2. Select it + // 3. Delete the selected row + // 4. Verify the row is deleted + // 5. Undo + // 6. Verify the row is visible and pending again + }); + + it('Delete a pending child node - Flat DS', () => { + // TODO: + // 1. Add a row at level 1 to the grid + // 2. Select it + // 3. Delete the selected row + // 4. Verify the row is deleted + // 5. Undo + // 6. Verify the row is visible and pending again + }); + + it('Delete a pending child node - Hierarchical DS', () => { + // TODO: + // 1. Add a row at level 1 to the grid + // 2. Select it + // 3. Delete the selected row + // 4. Verify the row is deleted + // 5. Undo + // 6. Verify the row is visible and pending again + }); }); describe('Multi-column header', () => { From 6e274bea06a60ecbd3d0eb1bf20bc4f9f768f754 Mon Sep 17 00:00:00 2001 From: wnvko Date: Mon, 19 Nov 2018 16:40:49 +0200 Subject: [PATCH 061/114] refactor(tree-grid): sample add root row may add several rows now --- .../tree-grid-flat-data.sample.ts | 12 +++++++++--- src/app/tree-grid/tree-grid.sample.ts | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts index 7bba3140c44..e87e7165c53 100644 --- a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts +++ b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.ts @@ -3,7 +3,7 @@ import { Http } from '@angular/http'; import { IgxTreeGridComponent, IgxHierarchicalTransactionService, IgxGridTransaction } from 'igniteui-angular'; @Component({ - providers: [{provide: IgxGridTransaction, useClass: IgxHierarchicalTransactionService}], + providers: [{ provide: IgxGridTransaction, useClass: IgxHierarchicalTransactionService }], selector: 'app-tree-grid-flat-data-sample', styleUrls: ['tree-grid-flat-data.sample.css'], templateUrl: 'tree-grid-flat-data.sample.html' @@ -68,12 +68,18 @@ export class TreeGridFlatDataSampleComponent implements OnInit { } public addRow() { - this.grid1.addRow({ 'employeeID': 24, 'PID': -1, 'firstName': 'John', 'lastName': 'Doe', 'Title': 'Junior Sales Representative' }); + this.grid1.addRow({ + 'employeeID': this.data.length + this.nextRow++, + 'PID': -1, + 'firstName': 'John', + 'lastName': 'Doe', + 'Title': 'Junior Sales Representative' + }); } public addChildRow() { const selectedRowId = this.grid1.selectedRows()[0]; - this.grid1.addRow ( + this.grid1.addRow( { 'employeeID': this.data.length + this.nextRow++, 'firstName': `Added `, diff --git a/src/app/tree-grid/tree-grid.sample.ts b/src/app/tree-grid/tree-grid.sample.ts index 6f1c508e006..52ccc860b4e 100644 --- a/src/app/tree-grid/tree-grid.sample.ts +++ b/src/app/tree-grid/tree-grid.sample.ts @@ -409,7 +409,7 @@ export class TreeGridSampleComponent implements OnInit { public addRow() { this.grid1.addRow({ - 'ID': 'ASDFG', + 'ID': `ADD${this.nextRow++}`, 'CompanyName': 'Around the Horn', 'ContactName': 'Thomas Hardy', 'ContactTitle': 'Sales Representative', From b8494f5d4ed322ba0ff20da94a91d26d2544a8ac Mon Sep 17 00:00:00 2001 From: plamenamiteva Date: Tue, 20 Nov 2018 14:34:09 +0200 Subject: [PATCH 062/114] test(IgxTreeGrid): Adding flat data delete row tests. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 99 ++++++++++++++++--- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 24faa2d387c..10c4a1edc7c 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -15,6 +15,8 @@ import { configureTestSuite } from '../../test-utils/configure-suite'; import { IgxToggleModule } from '../../directives/toggle/toggle.directive'; import { IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; +import { IgxHierarchicalTransactionService } from '../../services/transaction/igx-hierarchical-transaction'; +import { IgxGridTransaction } from '../grid-base.component'; const CSS_CLASS_BANNER = 'igx-banner'; @@ -34,7 +36,10 @@ describe('IgxTreeGrid - Integration', () => { IgxTreeGridRowEditingComponent, IgxTreeGridMultiColHeadersComponent ], - imports: [NoopAnimationsModule, IgxToggleModule, IgxTreeGridModule] + imports: [NoopAnimationsModule, IgxToggleModule, IgxTreeGridModule], + providers: [ + { provide: IgxGridTransaction, useClass: IgxHierarchicalTransactionService } + ] }) .compileComponents(); })); @@ -602,13 +607,45 @@ describe('IgxTreeGrid - Integration', () => { }); it('Delete a pending parent node - Flat DS', () => { - // TODO: - // 1. Add a row at level 0 to the grid - // 2. Select it - // 3. Delete the selected row - // 4. Verify the row is deleted - // 5. Undo - // 6. Verify the row is visible and pending again + fix = TestBed.createComponent(IgxTreeGridPrimaryForeignKeyComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + treeGrid.foreignKey = 'ParentID'; + + const addedRowId = treeGrid.data.length; + const newRow = { + ID: addedRowId, + ParentID: 1, + Name: 'John Dow', + JobTitle: 'Copywriter', + Age: 22 + }; + treeGrid.addRow(newRow, 0); + fix.detectChanges(); + + const addedRow = treeGrid.rowList.filter(r => r.rowID === addedRowId)[0] as IgxTreeGridRowComponent; + treeGrid.selectRows([treeGrid.getRowByIndex(addedRow.index).rowID], true); + fix.detectChanges(); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); + expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(0); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(2); + expect(treeGrid.transactions.getTransactionLog()[1].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[1].type).toEqual('delete'); + expect(treeGrid.transactions.getTransactionLog()[1].newValue).toBeNull(); + + treeGrid.transactions.undo(); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); + expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); }); it('Delete a pending parent node - Hierarchical DS', () => { @@ -622,13 +659,45 @@ describe('IgxTreeGrid - Integration', () => { }); it('Delete a pending child node - Flat DS', () => { - // TODO: - // 1. Add a row at level 1 to the grid - // 2. Select it - // 3. Delete the selected row - // 4. Verify the row is deleted - // 5. Undo - // 6. Verify the row is visible and pending again + fix = TestBed.createComponent(IgxTreeGridPrimaryForeignKeyComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + treeGrid.foreignKey = 'ParentID'; + + const addedRowId = treeGrid.data.length; + const newRow = { + ID: addedRowId, + ParentID: 1, + Name: 'John Dow', + JobTitle: 'Copywriter', + Age: 22 + }; + treeGrid.addRow(newRow, 1); + fix.detectChanges(); + + const addedRow = treeGrid.rowList.filter(r => r.rowID === addedRowId)[0] as IgxTreeGridRowComponent; + treeGrid.selectRows([treeGrid.getRowByIndex(addedRow.index).rowID], true); + fix.detectChanges(); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); + expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(0); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(2); + expect(treeGrid.transactions.getTransactionLog()[1].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[1].type).toEqual('delete'); + expect(treeGrid.transactions.getTransactionLog()[1].newValue).toBeNull(); + + treeGrid.transactions.undo(); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); + expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); }); it('Delete a pending child node - Hierarchical DS', () => { From df2169ab9b00adca5edc75804bb160bb7c80f3c2 Mon Sep 17 00:00:00 2001 From: IvayloG Date: Tue, 20 Nov 2018 10:48:40 +0200 Subject: [PATCH 063/114] test(IgxTreeGrid): Adding RowEditingTransactionComponent. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 6 ++++-- .../test-utils/tree-grid-components.spec.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 10c4a1edc7c..f98c846e1f3 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -5,7 +5,8 @@ import { IgxTreeGridModule, IgxTreeGridRowComponent } from './index'; import { IgxTreeGridSimpleComponent, IgxTreeGridPrimaryForeignKeyComponent, IgxTreeGridStringTreeColumnComponent, IgxTreeGridDateTreeColumnComponent, IgxTreeGridBooleanTreeColumnComponent, - IgxTreeGridRowEditingComponent, IgxTreeGridMultiColHeadersComponent + IgxTreeGridRowEditingComponent, IgxTreeGridMultiColHeadersComponent, + IgxTreeGridRowEditingTransactionComponent } from '../../test-utils/tree-grid-components.spec'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TreeGridFunctions } from '../../test-utils/tree-grid-functions.spec'; @@ -34,7 +35,8 @@ describe('IgxTreeGrid - Integration', () => { IgxTreeGridDateTreeColumnComponent, IgxTreeGridBooleanTreeColumnComponent, IgxTreeGridRowEditingComponent, - IgxTreeGridMultiColHeadersComponent + IgxTreeGridMultiColHeadersComponent, + IgxTreeGridRowEditingTransactionComponent ], imports: [NoopAnimationsModule, IgxToggleModule, IgxTreeGridModule], providers: [ diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts index 78258e65cec..f796acb7cca 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts @@ -1,6 +1,8 @@ import { Component, ViewChild } from '@angular/core'; import { IgxTreeGridComponent } from '../grids/tree-grid/tree-grid.component'; import { SampleTestData } from './sample-test-data.spec'; +import { IgxTransactionService } from '../../public_api'; +import { IgxGridTransaction } from '../grids/grid-base.component'; @Component({ template: ` @@ -258,3 +260,19 @@ export class IgxTreeGridMultiColHeadersComponent { @ViewChild(IgxTreeGridComponent) public treeGrid: IgxTreeGridComponent; public data = SampleTestData.employeeSmallTreeData(); } +@Component({ + template: ` + + + + + + + ` + , providers: [{ provide: IgxGridTransaction, useClass: IgxTransactionService }], +}) +export class IgxTreeGridRowEditingTransactionComponent { + public data = SampleTestData.employeePrimaryForeignKeyTreeData(); + @ViewChild('treeGrid', { read: IgxTreeGridComponent }) public treeGrid: IgxTreeGridComponent; + public paging = false; +} From 51b67c55822a25dabe42b9b1a55b03c7b61fa632 Mon Sep 17 00:00:00 2001 From: IvayloG Date: Tue, 20 Nov 2018 12:33:46 +0200 Subject: [PATCH 064/114] test(IgxTreeGrid): Adding test. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 36 +++++++++++++------ .../test-utils/tree-grid-components.spec.ts | 6 ++-- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index f98c846e1f3..b274d94a0db 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -519,17 +519,31 @@ describe('IgxTreeGrid - Integration', () => { expect(editedParentCell.value).toEqual(80); }); - it('Children are transformed into parent nodes after their parent is deleted', () => { - // TODO: - // 1. Set 'Cascade On Delete' to false on a grid with Flat DS - // 2. Delete a parent node - // 3. Verify the correct style is applied before committing - // 4. Commit changes - // 5. Verify the correct style is applied after committing - // 6. Verify its children are transformed into parent nodes - // and are placed at the correct place in the grid - // 7. Verify the undo stack is empty - }); + it('Children are transformed into parent nodes after their parent is deleted', fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + + const row: HTMLElement = treeGrid.getRowByIndex(0).nativeElement; + treeGrid.cascadeOnDelete = false; + const trans = treeGrid.transactions; + + treeGrid.deleteRowById(1); + fix.detectChanges(); + tick(); + + expect(row.classList).toContain('igx-grid__tr--deleted'); + expect(treeGrid.getRowByKey(1).index).toBe(0); + expect(treeGrid.getRowByKey(2).index).toBe(1); + expect(treeGrid.getRowByKey(3).index).toBe(2); + trans.commit(treeGrid.data); + tick(); + + expect(row.classList).not.toContain('igx-grid__tr--deleted'); + expect(treeGrid.getRowByKey(2).index).toBe(0); + expect(treeGrid.getRowByKey(3).index).toBe(1); + expect(trans.canUndo).toBe(false); + })); it('Children are deleted along with their parent', () => { // TODO: diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts index f796acb7cca..9093aee8409 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts @@ -265,9 +265,9 @@ export class IgxTreeGridMultiColHeadersComponent { - - - + + + ` , providers: [{ provide: IgxGridTransaction, useClass: IgxTransactionService }], }) From 2141c79ded2a84916128b080c6dcfa3ccfa79f3c Mon Sep 17 00:00:00 2001 From: IvayloG Date: Tue, 20 Nov 2018 13:06:24 +0200 Subject: [PATCH 065/114] test(IgxTreeGrid): Adding test. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index b274d94a0db..64f7e615004 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -545,15 +545,40 @@ describe('IgxTreeGrid - Integration', () => { expect(trans.canUndo).toBe(false); })); - it('Children are deleted along with their parent', () => { - // TODO: - // 1. Set 'Cascade On Delete' to true on a grid with Flat DS - // 2. Delete a parent node - // 3. Verify the correct style is applied before committing to all nodes - // 4. Commit changes - // 5. Verify the parent node and its children are deleted - // 6. Verify the undo stack is empty - }); + it('Children are deleted along with their parent', fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + treeGrid.cascadeOnDelete = true; + const trans = treeGrid.transactions; + + treeGrid.deleteRowById(1); + fix.detectChanges(); + tick(); + + for (let i = 0; i < 5; i++) { + const curRow: HTMLElement = treeGrid.getRowByIndex(i).nativeElement; + expect(curRow.classList).toContain('igx-grid__tr--deleted'); + } + expect(treeGrid.getRowByKey(1).index).toBe(0); + expect(treeGrid.getRowByKey(2).index).toBe(1); + expect(treeGrid.getRowByKey(3).index).toBe(2); + expect(treeGrid.getRowByKey(7).index).toBe(3); + expect(treeGrid.getRowByKey(4).index).toBe(4); + + trans.commit(treeGrid.data); + tick(); + + expect(treeGrid.getRowByKey(1)).toBeUndefined(); + expect(treeGrid.getRowByKey(2)).toBeUndefined(); + expect(treeGrid.getRowByKey(3)).toBeUndefined(); + expect(treeGrid.getRowByKey(7)).toBeUndefined(); + expect(treeGrid.getRowByKey(4)).toBeUndefined(); + + expect(treeGrid.getRowByKey(6).index).toBe(0); + expect(treeGrid.getRowByKey(10).index).toBe(1); + expect(trans.canUndo).toBe(false); + })); it('Editing a cell is posible with Hierarchical DS', () => { // TODO: From dda7c42c7840cf78e348ee8e4a0b3841557f72a3 Mon Sep 17 00:00:00 2001 From: IvayloG Date: Tue, 20 Nov 2018 14:10:57 +0200 Subject: [PATCH 066/114] test(IgxTreeGrid): Adding test. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 64f7e615004..b361f07b0de 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -601,16 +601,32 @@ describe('IgxTreeGrid - Integration', () => { // 7. Verify the changes are comitted }); - it('Add parent node to a Flat DS tree grid', () => { - // TODO: - // 1. Add a row at level 0 to the grid - // 2. Verify the new row is pending with the correct styles - // 3. Commit - // 4. verify the row is committed, the styles are OK and the Undo stack is empty - // 5. Add another row at level 0 - // 6. verify the pending styles is applied only to the newly added row - // and not to the previously added row - }); + it('Add parent node to a Flat DS tree grid', fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + const trans = treeGrid.transactions; + + treeGrid.addRow({ ID: 11, ParentID: -1, Name: 'Dan Kolov', JobTitle: 'wrestler', Age: 32 }); + fix.detectChanges(); + tick(); + + expect(trans.canUndo).toBe(true); + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); + + trans.commit(treeGrid.data); + tick(); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(trans.canUndo).toBe(false); + + treeGrid.addRow({ ID: 12, ParentID: -1, Name: 'Kubrat Pulev', JobTitle: 'Boxer', Age: 33 }); + fix.detectChanges(); + tick(); + + expect(trans.canUndo).toBe(true); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + })); it('Add parent node to a Hierarchical DS tree grid', () => { // TODO: From e6f81da7745f33936a8c093d79da07c587fa68b5 Mon Sep 17 00:00:00 2001 From: AMarinov Date: Tue, 20 Nov 2018 13:50:57 +0200 Subject: [PATCH 067/114] chore(row=editing): Adding a template with Hierarchical DS #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 6 ++++-- .../lib/test-utils/tree-grid-components.spec.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index b361f07b0de..632518b906a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -6,7 +6,8 @@ import { IgxTreeGridSimpleComponent, IgxTreeGridPrimaryForeignKeyComponent, IgxTreeGridStringTreeColumnComponent, IgxTreeGridDateTreeColumnComponent, IgxTreeGridBooleanTreeColumnComponent, IgxTreeGridRowEditingComponent, IgxTreeGridMultiColHeadersComponent, - IgxTreeGridRowEditingTransactionComponent + IgxTreeGridRowEditingTransactionComponent, + IgxTreeGridRowEditingHierarchicalDSTransactionComponent } from '../../test-utils/tree-grid-components.spec'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TreeGridFunctions } from '../../test-utils/tree-grid-functions.spec'; @@ -36,7 +37,8 @@ describe('IgxTreeGrid - Integration', () => { IgxTreeGridBooleanTreeColumnComponent, IgxTreeGridRowEditingComponent, IgxTreeGridMultiColHeadersComponent, - IgxTreeGridRowEditingTransactionComponent + IgxTreeGridRowEditingTransactionComponent, + IgxTreeGridRowEditingHierarchicalDSTransactionComponent ], imports: [NoopAnimationsModule, IgxToggleModule, IgxTreeGridModule], providers: [ diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts index 9093aee8409..c9f66692f6c 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts @@ -276,3 +276,19 @@ export class IgxTreeGridRowEditingTransactionComponent { @ViewChild('treeGrid', { read: IgxTreeGridComponent }) public treeGrid: IgxTreeGridComponent; public paging = false; } + +@Component({ + template: ` + + + + + + ` + , providers: [{ provide: IgxGridTransaction, useClass: IgxTransactionService }], +}) +export class IgxTreeGridRowEditingHierarchicalDSTransactionComponent { + public data = SampleTestData.employeeAllTypesTreeData(); + @ViewChild('treeGrid', { read: IgxTreeGridComponent }) public treeGrid: IgxTreeGridComponent; + public paging = false; +} From 937d2a2bd889ea0878b71803e5cd3e0227f0baf1 Mon Sep 17 00:00:00 2001 From: plamenamiteva Date: Tue, 20 Nov 2018 18:47:36 +0200 Subject: [PATCH 068/114] test(IgxTreeGrid): Adding hierarchical data delete row tests. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 149 ++++++++++++++---- .../test-utils/tree-grid-components.spec.ts | 16 +- 2 files changed, 128 insertions(+), 37 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 632518b906a..c6dce58f485 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -669,6 +669,8 @@ describe('IgxTreeGrid - Integration', () => { fix = TestBed.createComponent(IgxTreeGridPrimaryForeignKeyComponent); fix.detectChanges(); treeGrid = fix.componentInstance.treeGrid; + const trans = treeGrid.transactions; + spyOn(trans, 'add').and.callThrough(); treeGrid.foreignKey = 'ParentID'; const addedRowId = treeGrid.data.length; @@ -686,9 +688,10 @@ describe('IgxTreeGrid - Integration', () => { treeGrid.selectRows([treeGrid.getRowByIndex(addedRow.index).rowID], true); fix.detectChanges(); expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); - expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); - expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); - expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(1); + const transParams = {id: addedRowId, type: 'add', newValue: newRow}; + expect(trans.add).toHaveBeenCalledWith(transParams); treeGrid.deleteRowById(treeGrid.selectedRows()[0]); fix.detectChanges(); @@ -702,25 +705,66 @@ describe('IgxTreeGrid - Integration', () => { fix.detectChanges(); expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(1); expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); - expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); - expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); - expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(2); + expect(trans.add).toHaveBeenCalledWith(transParams); }); - it('Delete a pending parent node - Hierarchical DS', () => { - // TODO: - // 1. Add a row at level 0 to the grid - // 2. Select it - // 3. Delete the selected row - // 4. Verify the row is deleted - // 5. Undo - // 6. Verify the row is visible and pending again - }); + it('Delete a pending parent node - Hierarchical DS', fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + const trans = treeGrid.transactions; + spyOn(trans, 'add').and.callThrough(); + + const parentRow = treeGrid.getRowByIndex(0) as IgxTreeGridRowComponent; + const addedRowId = treeGrid.rowList.length; + const newRow = { + ID: addedRowId, + Name: 'John Dow', + HireDate: new Date(2018, 10, 20), + Age: 22, + OnPTO: false, + Employees: [] + }; + + treeGrid.addRow(newRow, parentRow.rowID); + fix.detectChanges(); + + const addedRow = treeGrid.rowList.filter(r => r.rowID === addedRowId)[0] as IgxTreeGridRowComponent; + treeGrid.selectRows([treeGrid.getRowByIndex(addedRow.index).rowID], true); + tick(20); + fix.detectChanges(); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(1); + const transParams = {id: addedRowId, path: [parentRow.rowID], newValue: newRow, type: 'add'}; + expect(trans.add).toHaveBeenCalledWith(transParams, null); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + tick(); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(0); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(2); + expect(treeGrid.transactions.getTransactionLog()[1].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[1].type).toEqual('delete'); + expect(treeGrid.transactions.getTransactionLog()[1].newValue).toBeNull(); + + treeGrid.transactions.undo(); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(2); + expect(trans.add).toHaveBeenCalledWith(transParams, null); + })); it('Delete a pending child node - Flat DS', () => { fix = TestBed.createComponent(IgxTreeGridPrimaryForeignKeyComponent); fix.detectChanges(); treeGrid = fix.componentInstance.treeGrid; + const trans = treeGrid.transactions; + spyOn(trans, 'add').and.callThrough(); treeGrid.foreignKey = 'ParentID'; const addedRowId = treeGrid.data.length; @@ -738,9 +782,10 @@ describe('IgxTreeGrid - Integration', () => { treeGrid.selectRows([treeGrid.getRowByIndex(addedRow.index).rowID], true); fix.detectChanges(); expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); - expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); - expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); - expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(1); + const transParams = {id: addedRowId, type: 'add', newValue: newRow}; + expect(trans.add).toHaveBeenCalledWith(transParams); treeGrid.deleteRowById(treeGrid.selectedRows()[0]); fix.detectChanges(); @@ -754,20 +799,64 @@ describe('IgxTreeGrid - Integration', () => { fix.detectChanges(); expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(1); expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); - expect(treeGrid.transactions.getTransactionLog()[0].id).toEqual(addedRowId); - expect(treeGrid.transactions.getTransactionLog()[0].type).toEqual('add'); - expect(treeGrid.transactions.getTransactionLog()[0].newValue).toEqual(newRow); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(2); + expect(trans.add).toHaveBeenCalledWith(transParams); }); - it('Delete a pending child node - Hierarchical DS', () => { - // TODO: - // 1. Add a row at level 1 to the grid - // 2. Select it - // 3. Delete the selected row - // 4. Verify the row is deleted - // 5. Undo - // 6. Verify the row is visible and pending again - }); + it('Delete a pending child node - Hierarchical DS', fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + const trans = treeGrid.transactions; + spyOn(trans, 'add').and.callThrough(); + + const parentRow = treeGrid.getRowByIndex(1) as IgxTreeGridRowComponent; + const addedRowId = treeGrid.rowList.length; + const newRow = { + ID: addedRowId, + Name: 'John Dow', + HireDate: new Date(2018, 10, 20), + Age: 22, + OnPTO: false, + Employees: [] + }; + + treeGrid.addRow(newRow, parentRow.rowID); + fix.detectChanges(); + + const addedRow = treeGrid.rowList.filter(r => r.rowID === addedRowId)[0] as IgxTreeGridRowComponent; + treeGrid.selectRows([treeGrid.getRowByIndex(addedRow.index).rowID], true); + tick(20); + fix.detectChanges(); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(1); + const transPasrams = { + id: addedRowId, + path: [treeGrid.getRowByIndex(0).rowID, parentRow.rowID], + newValue: newRow, + type: 'add'}; + expect(trans.add).toHaveBeenCalledWith(transPasrams, null); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + tick(); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(0); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(2); + expect(treeGrid.transactions.getTransactionLog()[1].id).toEqual(addedRowId); + expect(treeGrid.transactions.getTransactionLog()[1].type).toEqual('delete'); + expect(treeGrid.transactions.getTransactionLog()[1].newValue).toBeNull(); + + treeGrid.transactions.undo(); + fix.detectChanges(); + expect(treeGrid.rowList.filter(r => r.rowID === addedRowId).length).toEqual(1); + expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(2); + expect(trans.add).toHaveBeenCalledWith(transPasrams, null); + + })); }); describe('Multi-column header', () => { diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts index c9f66692f6c..6c3dc147d23 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts @@ -1,7 +1,7 @@ import { Component, ViewChild } from '@angular/core'; import { IgxTreeGridComponent } from '../grids/tree-grid/tree-grid.component'; import { SampleTestData } from './sample-test-data.spec'; -import { IgxTransactionService } from '../../public_api'; +import { IgxTransactionService, IgxHierarchicalTransactionService } from '../../public_api'; import { IgxGridTransaction } from '../grids/grid-base.component'; @Component({ @@ -21,7 +21,7 @@ export class IgxTreeGridSortingComponent { @Component({ template: ` - + @@ -279,13 +279,15 @@ export class IgxTreeGridRowEditingTransactionComponent { @Component({ template: ` - + - - - + + + + ` - , providers: [{ provide: IgxGridTransaction, useClass: IgxTransactionService }], + , providers: [{ provide: IgxGridTransaction, useClass: IgxHierarchicalTransactionService }], }) export class IgxTreeGridRowEditingHierarchicalDSTransactionComponent { public data = SampleTestData.employeeAllTypesTreeData(); From ce05f247fa55e0db9e06a18a096c7fc5f465b2a0 Mon Sep 17 00:00:00 2001 From: plamenamiteva Date: Thu, 8 Nov 2018 15:07:38 +0200 Subject: [PATCH 069/114] chore(IgxTreeGrid): Add TreeGridintegration tests --- .../tree-grid/tree-grid-integration.spec.ts | 172 ++++++++++++------ .../test-utils/tree-grid-functions.spec.ts | 10 +- 2 files changed, 128 insertions(+), 54 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index c6dce58f485..4c50ddda87e 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -1,4 +1,5 @@ import { async, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { DebugElement } from '@angular/core'; import { IgxTreeGridComponent } from './tree-grid.component'; import { SortingDirection } from '../../data-operations/sorting-expression.interface'; import { IgxTreeGridModule, IgxTreeGridRowComponent } from './index'; @@ -14,15 +15,18 @@ import { TreeGridFunctions } from '../../test-utils/tree-grid-functions.spec'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { By } from '@angular/platform-browser'; import { configureTestSuite } from '../../test-utils/configure-suite'; +import { IgxNumberFilteringOperand } from '../../../public_api'; +import { IgxGridCommonModule } from '../grid-common.module'; import { IgxToggleModule } from '../../directives/toggle/toggle.directive'; import { IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; import { IgxHierarchicalTransactionService } from '../../services/transaction/igx-hierarchical-transaction'; import { IgxGridTransaction } from '../grid-base.component'; + const CSS_CLASS_BANNER = 'igx-banner'; -describe('IgxTreeGrid - Integration', () => { +fdescribe('IgxTreeGrid - Integration', () => { configureTestSuite(); let fix; let treeGrid: IgxTreeGridComponent; @@ -309,7 +313,7 @@ describe('IgxTreeGrid - Integration', () => { // Verify the overlay has the same width as the row that is edited })); - it('shows the banner below the edited parent node', fakeAsync(() => { + it('should show the banner below the edited parent node', () => { // Collapsed state const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; grid.collapseAll(); @@ -324,7 +328,6 @@ describe('IgxTreeGrid - Integration', () => { function verifyBannerPositioning(columnIndex: number) { const cell = grid.getCellByColumn(columnIndex, 'Name'); cell.inEditMode = true; - tick(); fix.detectChanges(); const editRow = cell.row.nativeElement; @@ -338,15 +341,15 @@ describe('IgxTreeGrid - Integration', () => { // No much space between the row and the banner expect(bannerTop - editRowBottom).toBeLessThan(2); } - })); + }); - it('shows the banner below the edited child node', fakeAsync(() => { + it('should show the banner below the edited child node', () => { const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; grid.expandAll(); fix.detectChanges(); + const cell = grid.getCellByColumn(1, 'Name'); cell.inEditMode = true; - tick(); fix.detectChanges(); const editRow = cell.row.nativeElement; @@ -359,15 +362,17 @@ describe('IgxTreeGrid - Integration', () => { expect(bannerTop).toBeGreaterThanOrEqual(editRowBottom); // No much space between the row and the banner expect(bannerTop - editRowBottom).toBeLessThan(2); - })); + }); - it('shows the banner above the edited parent node if it is the last one', fakeAsync(() => { + it('should show the banner above the last parent node when in edit mode', fakeAsync(() => { const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; grid.height = '200px'; tick(16); // height animationFrame fix.detectChanges(); + grid.collapseAll(); fix.detectChanges(); + const cell = grid.getCellByColumn(2, 'Name'); cell.inEditMode = true; tick(); @@ -385,13 +390,13 @@ describe('IgxTreeGrid - Integration', () => { expect(editRowTop - bannerBottom).toBeLessThan(2); })); - it('shows the banner above the edited child node if it is the last one', fakeAsync(() => { + it('should show the banner above the last child node when in edit mode', () => { const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; grid.expandAll(); fix.detectChanges(); - const cell = grid.getCellByColumn(9, 'Name'); + + const cell = grid.getCellByColumn(grid.rowList.length - 1, 'Name'); cell.inEditMode = true; - tick(); fix.detectChanges(); const editRow = cell.row.nativeElement; @@ -404,69 +409,130 @@ describe('IgxTreeGrid - Integration', () => { expect(bannerBottom).toBeLessThanOrEqual(editRowTop); // No much space between the row and the banner expect(editRowTop - bannerBottom).toBeLessThan(2); - })); + }); - it('banner hides when you expand/collapse the edited row', fakeAsync(() => { + it('should hide banner when edited parent row is being expanded/collapsed', () => { const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; grid.collapseAll(); fix.detectChanges(); + // Edit parent row cell const cell = grid.getCellByColumn(0, 'Name'); cell.inEditMode = true; - tick(); fix.detectChanges(); - const banner = document.getElementsByClassName(CSS_CLASS_BANNER)[0]; - console.log(banner.attributes); + let banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(banner.parent.attributes['aria-hidden']).toEqual('false'); - // let banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); - // expect(banner.parent.attributes['aria-hidden']).toEqual('false'); + // Expand parent row + grid.expandRow(cell.row.rowID); + fix.detectChanges(); - // const row = cell.row as IgxTreeGridRowComponent; - // grid.expandRow(row.rowID); - // tick(); - // fix.detectChanges(); + banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(cell.inEditMode).toBeFalsy(); + expect(banner.parent.attributes['aria-hidden']).toEqual('true'); - // banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); - // expect(cell.inEditMode).toBeFalsy(); - // // expect(banner.parent.attributes['aria-hidden']).toEqual('true'); + // Edit parent row cell + cell.inEditMode = true; + fix.detectChanges(); - // cell = grid.getCellByColumn(0, 'Name'); - // cell.inEditMode = true; - // tick(); - // fix.detectChanges(); + banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(banner.parent.attributes['aria-hidden']).toEqual('false'); - // banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); - // expect(banner.parent.attributes['aria-hidden']).toEqual('false'); + // Collapse parent row + grid.collapseRow(cell.row.rowID); + fix.detectChanges(); - // grid.collapseRow(row.rowID); - // tick(); - // fix.detectChanges(); + banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(cell.inEditMode).toBeFalsy(); + expect(banner.parent.attributes['aria-hidden']).toEqual('true'); + }); - // banner = fix.debugElement.query(By.css('.igx-overlay__content')); - // // console.log(banner); - // expect(cell.inEditMode).toBeFalsy(); - // expect(banner.parent.attributes['aria-hidden']).toEqual('true'); + it('should hide banner when edited child row is being expanded/collapsed', () => { + const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + grid.expandAll(); + fix.detectChanges(); + // Edit child row child cell + const childCell = grid.getCellByColumn(4, 'Name'); + childCell.inEditMode = true; + fix.detectChanges(); + let banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(banner.parent.attributes['aria-hidden']).toEqual('false'); - // TODO - // Verify the changes are preserved - // 1.) Expand a parent row while editing it - // 2.) Collapse an expanded parent row while editing it - // 3.) Collapse an expanded parent row while editing a child (test with more than 2 levels) - })); + // Collapse parent child row + let parentRow = grid.getRowByIndex(3); + grid.collapseRow(parentRow.rowID); + fix.detectChanges(); - it('TAB navigation cannot leave the edited row and the banner.', fakeAsync(() => { - const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; - const cell = grid.getCellByColumn(2, 'Name'); - cell.inEditMode = true; - tick(); + banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(childCell.inEditMode).toBeFalsy(); + expect(banner.parent.attributes['aria-hidden']).toEqual('true'); + + // Edit child row cell + const parentCell = grid.getCellByColumn(3, 'Name'); + parentCell.inEditMode = true; fix.detectChanges(); - // TODO - // Verify the focus do not go to the next row - // Verify non-editable columns are skipped while navigating - })); + + banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(banner.parent.attributes['aria-hidden']).toEqual('false'); + + // Collapse parent row + parentRow = grid.getRowByIndex(0); + grid.collapseRow(parentRow.rowID); + fix.detectChanges(); + + banner = fix.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); + expect(parentCell.inEditMode).toBeFalsy(); + expect(banner.parent.attributes['aria-hidden']).toEqual('true'); + }); + + it('TAB navigation should not leave the edited row and the banner.', async () => { + const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + const row = grid.getRowByIndex(2); + const dateCell = grid.getCellByColumn(2, 'HireDate'); + const nameCell = grid.getCellByColumn(2, 'Name'); + const idCell = grid.getCellByColumn(2, 'ID'); + const ageCell = grid.getCellByColumn(2, 'Age'); + dateCell.inEditMode = true; + await wait(30); + fix.detectChanges(); + + await TreeGridFunctions.moveGridCellWithTab(fix, dateCell); + expect(dateCell.inEditMode).toBeFalsy(); + expect(nameCell.inEditMode).toBeTruthy(); + + await TreeGridFunctions.moveGridCellWithTab(fix, nameCell); + expect(nameCell.inEditMode).toBeFalsy(); + expect(idCell.inEditMode).toBeFalsy(); + expect(ageCell.inEditMode).toBeTruthy(); + + const cancelBtn = fix.debugElement.queryAll(By.css('.igx-button--flat'))[0] as DebugElement; + const doneBtn = fix.debugElement.queryAll(By.css('.igx-button--flat'))[1]; + spyOn(cancelBtn.nativeElement, 'focus').and.callThrough(); + spyOn(grid.rowEditTabs.first, 'move').and.callThrough(); + spyOn(grid.rowEditTabs.last, 'move').and.callThrough(); + + await TreeGridFunctions.moveGridCellWithTab(fix, ageCell); + expect(cancelBtn.nativeElement.focus).toHaveBeenCalled(); + + const mockObj = jasmine.createSpyObj('mockObj', ['stopPropagation', 'preventDefault']); + cancelBtn.triggerEventHandler('keydown.Tab', mockObj); + await wait(30); + fix.detectChanges(); + expect((grid.rowEditTabs.first).move).not.toHaveBeenCalled(); + expect(mockObj.preventDefault).not.toHaveBeenCalled(); + expect(mockObj.stopPropagation).toHaveBeenCalled(); + + doneBtn.triggerEventHandler('keydown.Tab', mockObj); + await wait(30); + fix.detectChanges(); + expect(dateCell.inEditMode).toBeTruthy(); + expect((grid.rowEditTabs.last).move).toHaveBeenCalled(); + expect(mockObj.preventDefault).toHaveBeenCalled(); + expect(mockObj.stopPropagation).toHaveBeenCalled(); + }); it('should preserve updates after removing Filtering', () => { const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts index f2c4512d4c8..809e906c546 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts @@ -1,5 +1,5 @@ import { By } from '@angular/platform-browser'; -import { IgxTreeGridComponent, IgxRowComponent, IgxGridBaseComponent, IgxGridCellComponent } from '../grids/tree-grid'; +import { IgxTreeGridComponent, IgxRowComponent, IgxGridBaseComponent, IgxGridCellComponent, IgxTreeGridCellComponent } from '../grids/tree-grid'; import { IgxCheckboxComponent } from '../checkbox/checkbox.component'; import { UIInteractions, wait } from './ui-interactions.spec'; @@ -493,4 +493,12 @@ export class TreeGridFunctions { expect(newCell.inEditMode).toBe(true); resolve(); }) + + public static moveGridCellWithTab = + (fix, cell: IgxGridCellComponent) => new Promise(async (resolve, reject) => { + UIInteractions.triggerKeyDownEvtUponElem('Tab', cell.nativeElement, true); + await wait(DEBOUNCETIME); + fix.detectChanges(); + resolve(); + }) } From 9bcd01074728d19268472cc2803c552eded0493a Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Tue, 20 Nov 2018 15:30:42 +0200 Subject: [PATCH 070/114] chore(igxTreeGrid): update imports, use proper transaction service --- .../src/lib/grids/tree-grid/tree-grid-integration.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 4c50ddda87e..c61893d8fec 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -15,8 +15,6 @@ import { TreeGridFunctions } from '../../test-utils/tree-grid-functions.spec'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { By } from '@angular/platform-browser'; import { configureTestSuite } from '../../test-utils/configure-suite'; -import { IgxNumberFilteringOperand } from '../../../public_api'; -import { IgxGridCommonModule } from '../grid-common.module'; import { IgxToggleModule } from '../../directives/toggle/toggle.directive'; import { IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; From 81d0f9965f16506add492e91cf1597cabd41d6a5 Mon Sep 17 00:00:00 2001 From: IvayloG Date: Tue, 20 Nov 2018 17:01:05 +0200 Subject: [PATCH 071/114] test(IgxTreeGrid): Adding test. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index c61893d8fec..2bc99cf03e9 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -21,7 +21,6 @@ import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; import { IgxHierarchicalTransactionService } from '../../services/transaction/igx-hierarchical-transaction'; import { IgxGridTransaction } from '../grid-base.component'; - const CSS_CLASS_BANNER = 'igx-banner'; fdescribe('IgxTreeGrid - Integration', () => { @@ -646,15 +645,51 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(trans.canUndo).toBe(false); })); - it('Editing a cell is posible with Hierarchical DS', () => { - // TODO: - // 1. Enter row edit mode in a gridwith Hierarchical DS - // 2. Update a cell - // 3. Press ENTER or click Done - // 4. Verify the value is updated and the correct style is applied before committing - // 5. Commit - // 6. Verify the correct value is set - }); + fit('Editing a cell is possible with Hierarchical DS', fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + const trans = treeGrid.transactions; + + const targetCell = treeGrid.getCellByColumn(3, 'Age'); + targetCell.inEditMode = true; + targetCell.update('333'); + fix.detectChanges(); + tick(); + + // ged DONE button and click it + const rowEditingBannerElement = fix.debugElement.query(By.css('.igx-banner')); + const buttonElements = rowEditingBannerElement.queryAll(By.css('.igx-button--flat')); + const doneButtonElement = buttonElements.find(el => el.nativeElement.innerText === 'Done'); + doneButtonElement.nativeElement.click(); + tick(); + + // Verify the value is updated and the correct style is applied before committing + expect(targetCell.inEditMode).toBeFalsy(); + expect(targetCell.value).toBe('333'); + expect(targetCell.nativeElement.classList).toContain('igx-grid__td--edited'); + + // Commit + trans.commit(treeGrid.data); + tick(); + + // Verify the correct value is set + expect(targetCell.value).toBe('333'); + + // Add new root lv row + treeGrid.addRow({ ID: 11, ParentID: -1, Name: 'Dan Kolov', JobTitle: 'wrestler', Age: 32, OnPTO: true }); + tick(); + + // Edit a cell value and check it is correctly updated + const newTargetCell = treeGrid.getCellByColumn(10, 'Age'); + newTargetCell.inEditMode = true; + newTargetCell.update('666'); + fix.detectChanges(); + tick(); + + expect(newTargetCell.value).toBe('666'); + expect(newTargetCell.nativeElement.classList).toContain('igx-grid__td--edited'); + })); it('Undo/Redo keeps the correct number of steps with Hierarchical DS', () => { // TODO: From b497a3f91336e6ad4915f744f92058d743e0c2f8 Mon Sep 17 00:00:00 2001 From: wnvko Date: Tue, 20 Nov 2018 16:35:44 +0200 Subject: [PATCH 072/114] fix(tree-grid): allow edit of ADD row, #2921 --- .../igniteui-angular/src/lib/data-operations/data-util.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 30d648f7b86..1be73c89f21 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -241,8 +241,7 @@ export class DataUtil { const dataRow = this.findDataRowFromPath(data, primaryKey, childDataKey, path); switch (transaction.type) { case TransactionType.ADD: - // if there is no dataRow, but there is a path this is ADD row added to - // DELETED ADD row - we just skip this + // if there is no dataRow this is ADD row at root level if (dataRow) { if (!dataRow[childDataKey]) { dataRow[childDataKey] = []; @@ -250,6 +249,8 @@ export class DataUtil { if (!dataRow[childDataKey].find(r => r[primaryKey] === transaction.id)) { dataRow[childDataKey].push(transaction.newValue); } + } else { + data.push(transaction.newValue); } break; case TransactionType.UPDATE: From 20fe3d4df5d2924b5471ff5db51c1b2a0287cafc Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Tue, 20 Nov 2018 16:10:01 +0200 Subject: [PATCH 073/114] chore(igxTreeGrid): add test for UNDO/REDO --- .../tree-grid/tree-grid-integration.spec.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 2bc99cf03e9..be94c12c33c 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -20,6 +20,7 @@ import { IgxNumberFilteringOperand } from '../../data-operations/filtering-condi import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; import { IgxHierarchicalTransactionService } from '../../services/transaction/igx-hierarchical-transaction'; import { IgxGridTransaction } from '../grid-base.component'; +import { IgxGridCellComponent } from '../grid'; const CSS_CLASS_BANNER = 'igx-banner'; @@ -700,6 +701,61 @@ fdescribe('IgxTreeGrid - Integration', () => { // 5. Verify all the updates are shown with correct styles // 6. Press "Commit" // 7. Verify the changes are comitted + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + const trans = treeGrid.transactions; + const treeGridData = treeGrid.data; + // Get initial data + const rowData = { + 147: Object.assign({}, treeGrid.getRowByKey(147).rowData), + 475: Object.assign({}, treeGrid.getRowByKey(475).rowData), + 19: Object.assign({}, treeGrid.getRowByKey(19).rowData) + }; + const initialData = treeGrid.data.map(e => { + return Object.assign({}, e); + }); + let targetCell: IgxGridCellComponent; + // Get 147 row + targetCell = treeGrid.getCellByKey(147, 'Name'); + expect(targetCell.value).toEqual('John Winchester'); + // Edit 'Name' + targetCell.update('Testy Testington'); + // Get 475 row (1st child of 147) + targetCell = treeGrid.getCellByKey(475, 'Age'); + expect(targetCell.value).toEqual(30); + // Edit Age + targetCell.update(42); + // Get 19 row + targetCell = treeGrid.getCellByKey(19, 'Name'); + // Edit Name + expect(targetCell.value).toEqual('Yang Wang'); + targetCell.update('Old Richard'); + expect(rowData[147].Name).not.toEqual(treeGrid.getRowByKey(147).rowData.Name); + expect(rowData[475].Age).not.toEqual(treeGrid.getRowByKey(475).rowData.Age); + expect(rowData[19].Name).not.toEqual(treeGrid.getRowByKey(19).rowData.Name); + expect(treeGridData[0].Employees[475]).toEqual(initialData[0].Employees[475]); + expect(trans.canUndo).toBeTruthy(); + expect(trans.canRedo).toBeFalsy(); + trans.undo(); + trans.undo(); + trans.undo(); + expect(rowData[147].Name).toEqual(treeGrid.getRowByKey(147).rowData.Name); + expect(rowData[475].Age).toEqual(treeGrid.getRowByKey(475).rowData.Age); + expect(rowData[19].Name).toEqual(treeGrid.getRowByKey(19).rowData.Name); + expect(trans.canUndo).toBeFalsy(); + expect(trans.canRedo).toBeTruthy(); + trans.redo(); + trans.redo(); + trans.redo(); + expect(rowData[147].Name).not.toEqual(treeGrid.getRowByKey(147).rowData.Name); + expect(rowData[475].Age).not.toEqual(treeGrid.getRowByKey(475).rowData.Age); + expect(rowData[19].Name).not.toEqual(treeGrid.getRowByKey(19).rowData.Name); + expect(treeGridData[0].Employees[475]).toEqual(initialData[0].Employees[475]); + trans.commit(treeGridData, treeGrid.childDataKey, treeGrid.primaryKey); + expect(treeGridData[0].Name).toEqual('Testy Testington'); + expect(treeGridData[0].Employees[0].Age).toEqual(42); + expect(treeGridData[1].Name).toEqual('Old Richard'); }); it('Add parent node to a Flat DS tree grid', fakeAsync(() => { From bfc1a49cccd8b6e48ab98c8c94951047e14c1faa Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Tue, 20 Nov 2018 16:12:11 +0200 Subject: [PATCH 074/114] chore(igxTreeGrid): fix lint error in spec file --- .../src/lib/test-utils/tree-grid-functions.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts index 809e906c546..707c7be05ab 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts @@ -1,5 +1,5 @@ import { By } from '@angular/platform-browser'; -import { IgxTreeGridComponent, IgxRowComponent, IgxGridBaseComponent, IgxGridCellComponent, IgxTreeGridCellComponent } from '../grids/tree-grid'; +import { IgxTreeGridComponent, IgxRowComponent, IgxGridBaseComponent, IgxGridCellComponent } from '../grids/tree-grid'; import { IgxCheckboxComponent } from '../checkbox/checkbox.component'; import { UIInteractions, wait } from './ui-interactions.spec'; From 9098a789d583a6bc594dd6fc91c772a1aaf90e9e Mon Sep 17 00:00:00 2001 From: IvayloG Date: Tue, 20 Nov 2018 17:02:50 +0200 Subject: [PATCH 075/114] chore(IgxTreeGrid): Remove fit. #2921 --- .../src/lib/grids/tree-grid/tree-grid-integration.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index be94c12c33c..fd240e220b4 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -646,7 +646,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(trans.canUndo).toBe(false); })); - fit('Editing a cell is possible with Hierarchical DS', fakeAsync(() => { + it('Editing a cell is possible with Hierarchical DS', fakeAsync(() => { fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); fix.detectChanges(); treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; From 2beff83ae1d0fe49cf7bde6e692de336ae4370da Mon Sep 17 00:00:00 2001 From: IvayloG Date: Wed, 21 Nov 2018 09:29:46 +0200 Subject: [PATCH 076/114] test(IgxTreeGrid): Adding test. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index fd240e220b4..507a7cf5178 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -797,15 +797,36 @@ fdescribe('IgxTreeGrid - Integration', () => { }); it('Add a child node to a previously added parent node - Flat DS', () => { - // TODO: - // 1. Add a row at level 0 to the grid - // 2. Add a child row to that parent - // 3. Verify the new rows are pending with the correct styles - // 4. Commit - // 5. verify the rows are committed, the styles are OK - // 6. Add another child row at level 2 (grand-child of the first row) - // 7. verify the pending styles is applied only to the newly added row - // and not to the previously added rows + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + const rootRow = { ID: 11, ParentID: -1, Name: 'Kubrat Pulev', JobTitle: 'wrestler', Age: 32 }; + const childRow = { ID: 12, ParentID: 11, Name: 'Tervel Pulev', JobTitle: 'wrestler', Age: 30 }; + const grandChildRow = { ID: 13, ParentID: 12, Name: 'Asparuh Pulev', JobTitle: 'wrestler', Age: 14 }; + const trans = treeGrid.transactions; + spyOn(trans, 'add').and.callThrough(); + + treeGrid.addRow(rootRow, 0); + fix.detectChanges(); + + const parentRow = treeGrid.getRowByIndex(10) as IgxTreeGridRowComponent; + treeGrid.addRow(childRow, 11); + fix.detectChanges(); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + + trans.commit(treeGrid.data); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + + treeGrid.addRow(grandChildRow, 12); + fix.detectChanges(); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); }); it('Add a child node to a previously added parent node - Hierarchical DS', () => { From 0bd0eed0f6802520bf0888dc9709e72bc781634f Mon Sep 17 00:00:00 2001 From: IvayloG Date: Wed, 21 Nov 2018 12:16:07 +0200 Subject: [PATCH 077/114] test(IgxTreeGrid): Adding empty ChildDataKey test. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 507a7cf5178..9a8ebbb3cce 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -804,12 +804,10 @@ fdescribe('IgxTreeGrid - Integration', () => { const childRow = { ID: 12, ParentID: 11, Name: 'Tervel Pulev', JobTitle: 'wrestler', Age: 30 }; const grandChildRow = { ID: 13, ParentID: 12, Name: 'Asparuh Pulev', JobTitle: 'wrestler', Age: 14 }; const trans = treeGrid.transactions; - spyOn(trans, 'add').and.callThrough(); treeGrid.addRow(rootRow, 0); fix.detectChanges(); - const parentRow = treeGrid.getRowByIndex(10) as IgxTreeGridRowComponent; treeGrid.addRow(childRow, 11); fix.detectChanges(); @@ -829,16 +827,58 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); }); - it('Add a child node to a previously added parent node - Hierarchical DS', () => { - // TODO: - // 1. Add a row at level 0 to the grid - // 2. Add a child row to that parent - // 3. Verify the new rows are pending with the correct styles - // 4. Commit - // 5. verify the rows are committed, the styles are OK - // 6. Add another child row at level 2 (grand-child of the first row) - // 7. verify the pending styles is applied only to the newly added row - // and not to the previously added rows + it('Add a child node to a previously added parent node - Hierarchical DS - empty ChildDataKey -commit', () => { + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + const trans = treeGrid.transactions; + const rootRow = { + ID: 11, + Name: 'Kubrat Pulev', + HireDate: new Date(2018, 10, 20), + Age: 32, + OnPTO: false, + Employees: [] + }; + + const childRow = { + ID: 12, + Name: 'Tervel Pulev', + HireDate: new Date(2018, 10, 10), + Age: 30, + OnPTO: true, + Employees: [] + }; + + const grandChildRow = { + ID: 13, + Name: 'Asparuh Pulev', + HireDate: new Date(2017, 10, 10), + Age: 14, + OnPTO: true, + Employees: [] + }; + + treeGrid.addRow(rootRow); + treeGrid.addRow(childRow, 11); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + + trans.commit(treeGrid.data); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + + treeGrid.addRow(grandChildRow, 12); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.records.get(11).level).toBe(0); + expect(treeGrid.records.get(12).level).toBe(1); + expect(treeGrid.records.get(13).level).toBe(2); + }); it('Delete a pending parent node - Flat DS', () => { From 0f4542aa506e21a2acb45be18d3078c51c35335d Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 21 Nov 2018 11:46:37 +0200 Subject: [PATCH 078/114] fix(tree-grid): edit added row with rowEdit=true, #2921 --- projects/igniteui-angular/src/lib/grids/api.service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/api.service.ts b/projects/igniteui-angular/src/lib/grids/api.service.ts index 80e742135cd..628daef0267 100644 --- a/projects/igniteui-angular/src/lib/grids/api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/api.service.ts @@ -187,7 +187,7 @@ export class GridBaseAPIService { if (!grid) { return -1; } - const data = this.get_all_data(id); + const data = this.get_all_data(id, grid.transactions.enabled); return grid.primaryKey ? data.findIndex(record => record[grid.primaryKey] === rowID) : data.indexOf(rowID); } @@ -276,7 +276,7 @@ export class GridBaseAPIService { rowData: any } { const grid = this.get(id); - const data = this.get_all_data(id); + const data = this.get_all_data(id, grid.transactions.enabled); const isRowSelected = grid.selection.is_item_selected(id, rowID); const editableCell = this.get_cell_inEditMode(id); const column = grid.columnList.toArray()[columnID]; @@ -337,7 +337,7 @@ export class GridBaseAPIService { rowData: any }): void { const grid = this.get(id); - const data = this.get_all_data(id); + // const data = this.get_all_data(id, grid.transactions.enabled); const currentGridEditState = gridEditState || this.create_grid_edit_args(id, rowID, columnID, editValue); const emittedArgs = currentGridEditState.args; const column = grid.columnList.toArray()[columnID]; @@ -357,7 +357,7 @@ export class GridBaseAPIService { // if edit (new) value is same as old value do nothing here if (emittedArgs.oldValue !== undefined && isEqual(emittedArgs.oldValue, emittedArgs.newValue)) { return; } - const rowValue = this.get_all_data(id)[rowIndex]; + const rowValue = this.get_all_data(id, grid.transactions.enabled)[rowIndex]; this.updateData(grid, rowID, rowValue, currentGridEditState.rowData, { [column.field]: emittedArgs.newValue }); if (grid.primaryKey === column.field && currentGridEditState.isRowSelected) { grid.selection.deselect_item(id, rowID); @@ -396,7 +396,7 @@ export class GridBaseAPIService { rowData: any }): void { const grid = this.get(id); - const data = this.get_all_data(id); + const data = this.get_all_data(id, grid.transactions.enabled); const currentGridState = gridState ? gridState : this.create_grid_edit_args(id, rowID, null, value); const emitArgs = currentGridState.args; const index = this.get_row_index_in_data(id, rowID); From 8b427c7925c50e1f7eacb8538a73cfb55d9ed84b Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Wed, 21 Nov 2018 11:20:42 +0200 Subject: [PATCH 079/114] chore(igxTreeGrid): fix failing tests --- .../src/lib/grids/tree-grid/tree-grid-integration.spec.ts | 5 ++--- .../src/lib/grids/tree-grid/tree-grid.component.ts | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 9a8ebbb3cce..df79850c654 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -566,10 +566,9 @@ fdescribe('IgxTreeGrid - Integration', () => { const childCell = grid.getCellByColumn(0, 'Age'); const childRowID = childCell.row.rowID; + childCell.update(14); const parentCell = grid.getCellByColumn(1, 'Age'); const parentRowID = parentCell.row.rowID; - - childCell.update(14); parentCell.update(80); fix.detectChanges(); @@ -671,7 +670,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(targetCell.nativeElement.classList).toContain('igx-grid__td--edited'); // Commit - trans.commit(treeGrid.data); + trans.commit(treeGrid.data, treeGrid.childDataKey, treeGrid.primaryKey); tick(); // Verify the correct value is set diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 24ce1bf021a..c8a88f1ac8a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -436,7 +436,8 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } else { const record = this.records.get(rowID); - const childData = record.parent ? record.parent.data[this.childDataKey] : this._gridAPI.get_all_data(this.id); + const childData = record.parent ? record.parent.data[this.childDataKey] : + this.transactions.enabled ? this._gridAPI.get_all_data(this.id, true) : this.data; index = this.primaryKey ? childData.map(c => c[this.primaryKey]).indexOf(rowID) : childData.indexOf(rowID); if (this.transactions.enabled) { From da2e58e0dba4d6571300eba7a1d35ebf68f99187 Mon Sep 17 00:00:00 2001 From: IvayloG Date: Wed, 21 Nov 2018 14:40:59 +0200 Subject: [PATCH 080/114] chore(IgxTreeGrid): Remove test, add Batch Editing describe. #2921 --- .../src/lib/grids/tree-grid/tree-grid-integration.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index df79850c654..bdc7ea96b5f 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -583,7 +583,9 @@ fdescribe('IgxTreeGrid - Integration', () => { const editedParentCell = parentRow.cells.filter(c => c.column.field === 'Age')[0]; expect(editedParentCell.value).toEqual(80); }); + }); + describe('Batch Editing', () => { it('Children are transformed into parent nodes after their parent is deleted', fakeAsync(() => { fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); fix.detectChanges(); @@ -1070,7 +1072,6 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(trans.add).toHaveBeenCalled(); expect(trans.add).toHaveBeenCalledTimes(2); expect(trans.add).toHaveBeenCalledWith(transPasrams, null); - })); }); From 60af88502a5d062eec60f7f7c3ebb4b2aaea1e4b Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 21 Nov 2018 14:30:11 +0200 Subject: [PATCH 081/114] fix(tree-grid): add row with transactions to empty grid, #2921 --- .../igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 08a616cbad0..95212028a14 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -269,7 +269,7 @@ export class IgxTreeGridTransactionPipe implements PipeTransform { transform(collection: any[], id: string, pipeTrigger: number): any[] { const grid: IgxTreeGridComponent = this.gridAPI.get(id); - if (collection && collection.length > 0 && grid.transactions.enabled) { + if (collection && grid.transactions.enabled) { const aggregatedChanges = grid.transactions.getAggregatedChanges(true); if (aggregatedChanges.length > 0) { const primaryKey = grid.primaryKey; From 5079475b4070c560591df6718a6262460a20dc05 Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 21 Nov 2018 14:39:15 +0200 Subject: [PATCH 082/114] test(tree-grid): add empty grid test scenarios, #2921 --- .../lib/grids/tree-grid/tree-grid-integration.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index bdc7ea96b5f..eee29af2c84 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -879,6 +879,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(treeGrid.records.get(11).level).toBe(0); expect(treeGrid.records.get(12).level).toBe(1); expect(treeGrid.records.get(13).level).toBe(2); + }); }); @@ -1178,5 +1179,13 @@ fdescribe('IgxTreeGrid - Integration', () => { TreeGridFunctions.verifyTreeColumnInMultiColHeaders(fix, 'HireDate', 4); })); + + it('Add row to empty grid - Hierarchical DS', () => { + // TODO + }); + + it('Add row to empty grid - Flat DS', () => { + // TODO + }); }); }); From 3950887b02a0f8fc3f303a002474fb7df450aaa0 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 22 Nov 2018 08:36:56 +0200 Subject: [PATCH 083/114] refactor(tree-grid): remove path from ITreeGridRecord --- .../src/lib/data-operations/data-util.ts | 3 +-- .../lib/grids/tree-grid/tree-grid.interfaces.ts | 1 - .../src/lib/grids/tree-grid/tree-grid.pipes.ts | 14 ++------------ 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 1be73c89f21..d3c5d6919e7 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -58,8 +58,7 @@ export class DataUtil { children: hierarchicalRecord.children, isFilteredOutParent: hierarchicalRecord.isFilteredOutParent, level: hierarchicalRecord.level, - expanded: hierarchicalRecord.expanded, - path: [...hierarchicalRecord.path] + expanded: hierarchicalRecord.expanded }; return rec; } diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.interfaces.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.interfaces.ts index 3789aff836f..0ea55334161 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.interfaces.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.interfaces.ts @@ -7,7 +7,6 @@ export interface ITreeGridRecord { level?: number; isFilteredOutParent?: boolean; expanded?: boolean; - path: any[]; } export interface IRowToggleEventArgs { diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 95212028a14..2518a66dc27 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -55,16 +55,11 @@ export class IgxTreeGridHierarchizingPipe implements PipeTransform { const record: ITreeGridRecord = { rowID: this.getRowID(primaryKey, row), data: row, - children: [], - path: [] + children: [] }; const parent = map.get(row[foreignKey]); if (parent) { record.parent = parent; - if (parent) { - record.path.push(...parent.path); - record.path.push(parent.rowID); - } parent.children.push(record); } else { missingParentRecords.push(record); @@ -110,13 +105,8 @@ export class IgxTreeGridHierarchizingPipe implements PipeTransform { rowID: this.getRowID(primaryKey, item), data: item, parent: parent, - level: indentationLevel, - path: [] + level: indentationLevel }; - if (parent) { - record.path.push(...parent.path); - record.path.push(parent.rowID); - } record.expanded = this.gridAPI.get_row_expansion_state(id, record.rowID, record.level); flatData.push(item); map.set(record.rowID, record); From 7c0c166c4d8e60441a61616ce5b3803408d45834 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 22 Nov 2018 08:37:40 +0200 Subject: [PATCH 084/114] test(tree-grid): add more test for hierarchical transaction, #2921 --- .../igx-hierarchical-transaction.ts | 32 ++++++++ .../transaction/igx-transaction.spec.ts | 73 ++++++++++++++++++- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index 780947eaf0b..ab11e7f7167 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -25,6 +25,38 @@ export class IgxHierarchicalTransactionService { + if (v.path.indexOf(transaction.id) !== -1) { + switch (v.type) { + case TransactionType.ADD: + states.delete(k); + break; + case TransactionType.UPDATE: + states.get(k).type = TransactionType.DELETE; + states.get(k).value = null; + } + } + }); + } + } + + public commit(data: any[], childDataKey?: any, primaryKey?: any): void { + if (childDataKey) { + DataUtil.mergeHierarchicalTransactions(data, this.getAggregatedChanges(true), childDataKey, primaryKey, true); + } else { + super.commit(data); + } + this.clear(); } // TODO: remove this method. Force cloning to strip child arrays when needed instead diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts index fd527da29d7..ae79454624d 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts @@ -1,6 +1,7 @@ import { IgxTransactionService } from './igx-transaction'; -import { Transaction, TransactionType } from './transaction'; +import { Transaction, TransactionType, HierarchicalTransaction } from './transaction'; import { SampleTestData } from '../../test-utils/sample-test-data.spec'; +import { IgxHierarchicalTransactionService } from './igx-hierarchical-transaction'; describe('IgxTransaction', () => { describe('IgxTransaction UNIT tests', () => { @@ -612,5 +613,75 @@ describe('IgxTransaction', () => { expect(trans.getAggregatedChanges(true)).toEqual([]); }); }); + + describe('IgxHierarchicalTransaction UNIT Test', () => { + it('Should set path for each state when transaction is added in Hierarchical data source', () => { + const transaction = new IgxHierarchicalTransactionService(); + expect(transaction).toBeDefined(); + + const path: any[] = ['P1', 'P2']; + const addTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.ADD, newValue: 'Add row', path }; + transaction.add(addTransaction); + expect(transaction.getState(1).path).toBeDefined(); + expect(transaction.getState(1).path.length).toBe(2); + expect(transaction.getState(1).path).toEqual(path); + + path.push('P3'); + const updateTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.UPDATE, newValue: 'Updated row', path }; + transaction.add(updateTransaction, 'Update row'); + expect(transaction.getState(1).path.length).toBe(3); + expect(transaction.getState(1).path).toEqual(path); + }); + + it('Should remove added transaction from states when deleted in Hierarchical data source', () => { + const transaction = new IgxHierarchicalTransactionService(); + expect(transaction).toBeDefined(); + + const path: any[] = []; + let addTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.ADD, newValue: 'Parent row', path }; + transaction.add(addTransaction); + expect(transaction.getState(1).path).toBeDefined(); + expect(transaction.getState(1).path.length).toBe(0); + expect(transaction.getState(1).path).toEqual(path); + + path.push(addTransaction.id); + addTransaction = { id: 2, type: TransactionType.ADD, newValue: 'Child row', path }; + transaction.add(addTransaction); + expect(transaction.getState(2).path).toBeDefined(); + expect(transaction.getState(2).path.length).toBe(1); + expect(transaction.getState(2).path).toEqual(path); + + const deleteTransaction: HierarchicalTransaction = {id: 1, type: TransactionType.DELETE, newValue: null, path: []}; + transaction.add(deleteTransaction); + expect(transaction.getState(1)).toBeUndefined(); + expect(transaction.getState(2)).toBeUndefined(); + }); + + fit('Should mark update transactions state as deleted type when deleted in Hierarchical data source', () =>{ + const transaction = new IgxHierarchicalTransactionService(); + expect(transaction).toBeDefined(); + + const path: any[] = []; + let updateTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.UPDATE, newValue: 'Parent row', path }; + transaction.add(updateTransaction, 'Original value'); + expect(transaction.getState(1).path).toBeDefined(); + expect(transaction.getState(1).path.length).toBe(0); + expect(transaction.getState(1).path).toEqual(path); + + path.push(updateTransaction.id); + updateTransaction = { id: 2, type: TransactionType.UPDATE, newValue: 'Child row', path }; + transaction.add(updateTransaction, 'Original Value'); + expect(transaction.getState(2).path).toBeDefined(); + expect(transaction.getState(2).path.length).toBe(1); + expect(transaction.getState(2).path).toEqual(path); + + const deleteTransaction: HierarchicalTransaction = {id: 1, type: TransactionType.DELETE, newValue: null, path: []}; + transaction.add(deleteTransaction); + expect(transaction.getState(1)).toBeDefined(); + expect(transaction.getState(1).type).toBe(TransactionType.DELETE); + expect(transaction.getState(2)).toBeDefined(); + expect(transaction.getState(2).type).toBe(TransactionType.DELETE); + }); + }); }); From 1016d3e098c81393438ffeb622a4082383d7d716 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 22 Nov 2018 08:39:48 +0200 Subject: [PATCH 085/114] refactor(tree-grid): fix alphabetical order of components in dev demos --- src/app/app.component.ts | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ae06b2bf509..1ca4ff47a3a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -90,7 +90,7 @@ export class AppComponent implements OnInit { }, { link: '/dropDown', - icon: 'drop_down', + icon: 'view_list', name: 'DropDown' }, { @@ -183,16 +183,6 @@ export class AppComponent implements OnInit { icon: 'view_column', name: 'Grid Toolbar' }, - { - link: '/treeGrid', - icon: 'view_column', - name: 'Tree Grid' - }, - { - link: '/treeGridFlatData', - icon: 'view_column', - name: 'Tree Grid Flat Data' - }, { link: '/icon', icon: 'android', @@ -225,12 +215,12 @@ export class AppComponent implements OnInit { }, { link: '/overlay', - icon: 'overlay', + icon: 'flip_to_front', name: 'Overlay' }, { link: '/overlay-animation', - icon: 'overlay_animation', + icon: 'flip_to_front', name: 'Overlay Animation' }, { @@ -267,6 +257,16 @@ export class AppComponent implements OnInit { link: '/toast', icon: 'android', name: 'Toast' + }, + { + link: '/treeGrid', + icon: 'view_column', + name: 'Tree Grid' + }, + { + link: '/treeGridFlatData', + icon: 'view_column', + name: 'Tree Grid Flat Data' } ]; @@ -286,25 +286,25 @@ export class AppComponent implements OnInit { icon: 'view_quilt', name: 'Layout' }, - { - link: '/ripple', - icon: 'wifi_tethering', - name: 'Ripple' - }, - { - link: '/virtualForDirective', - icon: 'view_column', - name: 'Virtual-For Directive' - }, { link: '/mask', icon: 'view_column', name: 'Mask Directive' }, + { + link: '/ripple', + icon: 'wifi_tethering', + name: 'Ripple' + }, { link: '/tooltip', icon: 'info', name: 'Tooltip' + }, + { + link: '/virtualForDirective', + icon: 'view_column', + name: 'Virtual-For Directive' } ]; @@ -314,15 +314,15 @@ export class AppComponent implements OnInit { icon: 'color_lens', name: 'Colors' }, - { - link: '/typography', - icon: 'font_download', - name: 'Typography' - }, { link: '/shadows', icon: 'layers', name: 'Shadows' + }, + { + link: '/typography', + icon: 'font_download', + name: 'Typography' } ]; From 8445cab703091001f684ffca3bfd390412ca6ca6 Mon Sep 17 00:00:00 2001 From: plamenamiteva Date: Wed, 21 Nov 2018 16:13:37 +0200 Subject: [PATCH 086/114] test(tree-grid): test add parent node to a Hierarchical DS tree grid, #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 88 ++++++++++++++----- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index eee29af2c84..ec04b0ba7e1 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -23,6 +23,7 @@ import { IgxGridTransaction } from '../grid-base.component'; import { IgxGridCellComponent } from '../grid'; const CSS_CLASS_BANNER = 'igx-banner'; +const CSS_CLASS_ROW_EDITED = 'igx-grid__tr--edited'; fdescribe('IgxTreeGrid - Integration', () => { configureTestSuite(); @@ -306,11 +307,6 @@ fdescribe('IgxTreeGrid - Integration', () => { treeGrid = fix.componentInstance.treeGrid; }); - it('banner has no indentation when editing a parent node.', fakeAsync(() => { - // TODO - // Verify the overlay has the same width as the row that is edited - })); - it('should show the banner below the edited parent node', () => { // Collapsed state const grid = fix.componentInstance.treeGrid as IgxTreeGridComponent; @@ -770,12 +766,12 @@ fdescribe('IgxTreeGrid - Integration', () => { tick(); expect(trans.canUndo).toBe(true); - expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); trans.commit(treeGrid.data); tick(); - expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); expect(trans.canUndo).toBe(false); treeGrid.addRow({ ID: 12, ParentID: -1, Name: 'Kubrat Pulev', JobTitle: 'Boxer', Age: 33 }); @@ -783,18 +779,64 @@ fdescribe('IgxTreeGrid - Integration', () => { tick(); expect(trans.canUndo).toBe(true); - expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); })); it('Add parent node to a Hierarchical DS tree grid', () => { - // TODO: - // 1. Add a row at level 0 to the grid - // 2. Verify the new row is pending with the correct styles - // 3. Commit - // 4. Verify the row is committed, the styles are OK and the Undo stack is empty - // 5. Add another row at level 0 - // 6. verify the pending styles is applied only to the newly added row - // and not to the previously added row + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + const initialDataLength = treeGrid.data.length; + const trans = treeGrid.transactions; + spyOn(trans, 'add').and.callThrough(); + + const addedRowId_1 = treeGrid.rowList.length; + const newRow = { + ID: addedRowId_1, + Name: 'John Dow', + HireDate: new Date(2018, 10, 20), + Age: 22, + OnPTO: false, + Employees: [] + }; + + treeGrid.addRow(newRow); + fix.detectChanges(); + + expect(trans.getTransactionLog().length).toEqual(1); + expect(trans.add).toHaveBeenCalled(); + expect(trans.add).toHaveBeenCalledTimes(1); + const transParams = {id: addedRowId_1, type: 'add', newValue: newRow}; + expect(trans.add).toHaveBeenCalledWith(transParams); + + expect(treeGrid.records.get(addedRowId_1).level).toBe(0); + expect(treeGrid.getRowByKey(addedRowId_1).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); + + trans.commit(treeGrid.data); + fix.detectChanges(); + + expect(treeGrid.data.length).toEqual(initialDataLength + 1); + expect(treeGrid.data[initialDataLength]).toEqual(newRow); + expect(treeGrid.records.get(addedRowId_1).level).toBe(0); + expect(treeGrid.getRowByKey(addedRowId_1).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(trans.getTransactionLog().length).toEqual(0); + expect(trans.canUndo).toBeFalsy(); + + const addedRowId_2 = treeGrid.rowList.length; + const newParentRow = { + ID: addedRowId_2, + Name: 'Brad Pitt', + HireDate: new Date(2016, 8, 14), + Age: 54, + OnPTO: false + }; + + treeGrid.addRow(newParentRow); + fix.detectChanges(); + + expect(treeGrid.records.get(addedRowId_2).level).toBe(0); + expect(treeGrid.getRowByKey(addedRowId_2).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(addedRowId_1).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); }); it('Add a child node to a previously added parent node - Flat DS', () => { @@ -812,20 +854,20 @@ fdescribe('IgxTreeGrid - Integration', () => { treeGrid.addRow(childRow, 11); fix.detectChanges(); - expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); trans.commit(treeGrid.data); - expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); treeGrid.addRow(grandChildRow, 12); fix.detectChanges(); - expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); }); it('Add a child node to a previously added parent node - Hierarchical DS - empty ChildDataKey -commit', () => { From 0fc7d4f2c33db302a495b0697372b3cbcd2dbffb Mon Sep 17 00:00:00 2001 From: IvayloG Date: Wed, 21 Nov 2018 14:58:29 +0200 Subject: [PATCH 087/114] test(IgxTreeGrid): Adding rows to empty data grid tests. #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index ec04b0ba7e1..a2a9d4e07f1 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -1222,12 +1222,89 @@ fdescribe('IgxTreeGrid - Integration', () => { TreeGridFunctions.verifyTreeColumnInMultiColHeaders(fix, 'HireDate', 4); })); - it('Add row to empty grid - Hierarchical DS', () => { - // TODO + it('Add rows to empty grid - Hierarchical DS', () => { + fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + // set empty data + treeGrid.data = []; + + const trans = treeGrid.transactions; + const rootRow = { + ID: 11, + Name: 'Kubrat Pulev', + HireDate: new Date(2018, 10, 20), + Age: 32, + OnPTO: false, + Employees: [] + }; + const childRow = { + ID: 12, + Name: 'Tervel Pulev', + HireDate: new Date(2018, 10, 10), + Age: 30, + OnPTO: true, + Employees: [] + }; + const grandChildRow = { + ID: 13, + Name: 'Asparuh Pulev', + HireDate: new Date(2017, 10, 10), + Age: 14, + OnPTO: true, + Employees: [] + }; + treeGrid.addRow(rootRow); + treeGrid.addRow(childRow, 11); + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + trans.commit(treeGrid.data, treeGrid.childDataKey, treeGrid.primaryKey); + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + treeGrid.addRow(grandChildRow, 12); + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.records.get(11).level).toBe(0); + expect(treeGrid.records.get(12).level).toBe(1); + expect(treeGrid.records.get(13).level).toBe(2); }); - it('Add row to empty grid - Flat DS', () => { - // TODO + it('Add rows to empty grid - Flat DS', () => { + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; + // set empty data + treeGrid.data = []; + + const rootRow = { ID: 11, ParentID: -1, Name: 'Kubrat Pulev', JobTitle: 'wrestler', Age: 32 }; + const childRow = { ID: 12, ParentID: 11, Name: 'Tervel Pulev', JobTitle: 'wrestler', Age: 30 }; + const grandChildRow = { ID: 13, ParentID: 12, Name: 'Asparuh Pulev', JobTitle: 'wrestler', Age: 14 }; + const trans = treeGrid.transactions; + + treeGrid.addRow(rootRow, 0); + fix.detectChanges(); + + treeGrid.addRow(childRow, 11); + fix.detectChanges(); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); + + trans.commit(treeGrid.data); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + + treeGrid.addRow(grandChildRow, 12); + fix.detectChanges(); + + expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); + expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); + expect(treeGrid.records.get(11).level).toBe(0); + expect(treeGrid.records.get(12).level).toBe(1); + expect(treeGrid.records.get(13).level).toBe(2); }); }); }); From dc360a4ccbe5d9351eaace1bb42d38f2238c516a Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 22 Nov 2018 10:51:02 +0200 Subject: [PATCH 088/114] test(tree-grid): merge transactions tests add, #2921 --- .../src/lib/data-operations/data-util.spec.ts | 145 +++++++++++++++++- .../transaction/igx-transaction.spec.ts | 2 +- 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts index 02d32089e2b..a11fa59d56b 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts @@ -14,11 +14,15 @@ import { FilteringStrategy } from './filtering-strategy'; import { IFilteringExpressionsTree, FilteringExpressionsTree } from './filtering-expressions-tree'; import { IFilteringState } from './filtering-state.interface'; import { FilteringLogic } from './filtering-expression.interface'; -import { IgxNumberFilteringOperand, +import { + IgxNumberFilteringOperand, IgxStringFilteringOperand, IgxDateFilteringOperand, - IgxBooleanFilteringOperand } from './filtering-condition'; + IgxBooleanFilteringOperand +} from './filtering-condition'; import { IPagingState, PagingError } from './paging-state.interface'; +import { SampleTestData } from '../test-utils/sample-test-data.spec'; +import { Transaction, TransactionType, HierarchicalTransaction } from '../services'; /* Test sorting */ function testSort() { @@ -266,6 +270,7 @@ function testGroupBy() { }); } /* //Test sorting */ + /* Test filtering */ class CustomFilteringStrategy extends FilteringStrategy { public filter(data: T[], expressionsTree: IFilteringExpressionsTree): T[] { @@ -387,6 +392,7 @@ function testFilter() { }); } /* //Test filtering */ + /* Test paging */ function testPage() { const dataGenerator: DataGenerator = new DataGenerator(); @@ -426,9 +432,144 @@ function testPage() { }); } /* //Test paging */ + +/* Test merging */ +function testMerging() { + describe('Test merging', () => { + it('Should merge add transactions correctly', () => { + const data = SampleTestData.personIDNameData(); + const addRow4 = { ID: 4, Name: 'Peter' }; + const addRow5 = { ID: 5, Name: 'Mimi' }; + const addRow6 = { ID: 6, Name: 'Pedro' }; + const transactions: Transaction[] = [ + { id: addRow4.ID, newValue: addRow4, type: TransactionType.ADD }, + { id: addRow5.ID, newValue: addRow5, type: TransactionType.ADD }, + { id: addRow6.ID, newValue: addRow6, type: TransactionType.ADD }, + ]; + + DataUtil.mergeTransactions(data, transactions, 'ID'); + expect(data.length).toBe(6); + expect(data[3]).toBe(addRow4); + expect(data[4]).toBe(addRow5); + expect(data[5]).toBe(addRow6); + }); + + it('Should merge update transactions correctly', () => { + const data = SampleTestData.personIDNameData(); + const transactions: Transaction[] = [ + { id: 1, newValue: { Name: 'Peter' }, type: TransactionType.UPDATE }, + { id: 3, newValue: { Name: 'Mimi' }, type: TransactionType.UPDATE }, + ]; + + DataUtil.mergeTransactions(data, transactions, 'ID'); + expect(data.length).toBe(3); + expect(data[0].Name).toBe('Peter'); + expect(data[2].Name).toBe('Mimi'); + }); + + it('Should merge delete transactions correctly', () => { + const data = SampleTestData.personIDNameData(); + const secondRow = data[1]; + const transactions: Transaction[] = [ + { id: 1, newValue: null, type: TransactionType.DELETE }, + { id: 3, newValue: null, type: TransactionType.DELETE }, + ]; + + DataUtil.mergeTransactions(data, transactions, 'ID', true); + expect(data.length).toBe(1); + expect(data[0]).toEqual(secondRow); + }); + + it('Should merge add hierarchical transactions correctly', () => { + const data = SampleTestData.employeeSmallTreeData(); + const addRootRow = { ID: 1000, Name: 'Pit Peter', HireDate: new Date(2008, 3, 20), Age: 55 }; + const addChildRow1 = { ID: 1001, Name: 'Marry May', HireDate: new Date(2018, 4, 1), Age: 102 }; + const addChildRow2 = { ID: 1002, Name: 'April Alison', HireDate: new Date(2021, 5, 10), Age: 4 }; + const transactions: HierarchicalTransaction[] = [ + { id: addRootRow.ID, newValue: addRootRow, type: TransactionType.ADD, path: [] }, + { id: addChildRow1.ID, newValue: addChildRow1, type: TransactionType.ADD, path: [data[0].ID, data[0].Employees[1].ID] }, + { id: addChildRow2.ID, newValue: addChildRow2, type: TransactionType.ADD, path: [addRootRow.ID] }, + ]; + + DataUtil.mergeHierarchicalTransactions(data, transactions, 'Employees', 'ID', false); + expect(data.length).toBe(4); + + expect(data[3].Age).toBe(addRootRow.Age); + expect(data[3].Employees.length).toBe(1); + expect(data[3].HireDate).toBe(addRootRow.HireDate); + expect(data[3].ID).toBe(addRootRow.ID); + expect(data[3].Name).toBe(addRootRow.Name); + + expect((data[0].Employees[1] as any).Employees.length).toBe(1); + expect((data[0].Employees[1] as any).Employees[0]).toBe(addChildRow1); + + expect(data[3].Employees[0]).toBe(addChildRow2); + }); + + it('Should merge update hierarchical transactions correctly', () => { + const data = SampleTestData.employeeSmallTreeData(); + const updateRootRow = { Name: 'May Peter', Age: 13 }; + const updateChildRow1 = { HireDate: new Date(2100, 1, 12), Age: 1300 }; + const updateChildRow2 = { HireDate: new Date(2100, 1, 12), Name: 'Santa Claus' }; + + const transactions: HierarchicalTransaction[] = [ + { + id: data[1].ID, + newValue: updateRootRow, + type: TransactionType.UPDATE, + path: [] + }, + { + id: data[2].Employees[0].ID, + newValue: updateChildRow1, + type: TransactionType.UPDATE, + path: [data[2].ID, data[2].Employees[0].ID] + }, + { + id: (data[0].Employees[2] as any).Employees[0].ID, + newValue: updateChildRow2, + type: TransactionType.UPDATE, + path: [data[0].ID, data[0].Employees[2].ID, (data[0].Employees[2] as any).Employees[0].ID] + }, + ]; + + DataUtil.mergeHierarchicalTransactions(data, transactions, 'Employees', 'ID', false); + expect(data[1].Name).toBe(updateRootRow.Name); + expect(data[1].Age).toBe(updateRootRow.Age); + + expect(data[2].Employees[0].HireDate.getTime()).toBe(updateChildRow1.HireDate.getTime()); + expect(data[2].Employees[0].Age).toBe(updateChildRow1.Age); + + expect((data[0].Employees[2] as any).Employees[0].Name).toBe(updateChildRow2.Name); + expect((data[0].Employees[2] as any).Employees[0].HireDate.getTime()).toBe(updateChildRow2.HireDate.getTime()); + }); + + it('Should merge delete hierarchical transactions correctly', () => { + const data = SampleTestData.employeeSmallTreeData(); + const transactions: HierarchicalTransaction[] = [ + // root row with no children + { id: data[1].ID, newValue: null, type: TransactionType.DELETE, path: [data[1].ID] }, + // root row with children + { id: data[2].ID, newValue: null, type: TransactionType.DELETE, path: [data[2].ID] }, + // child row with no children + { id: data[0].Employees[0].ID, newValue: null, type: TransactionType.DELETE, path: [data[0].ID, data[0].Employees[0].ID] }, + // child row with children + { id: data[0].Employees[2].ID, newValue: null, type: TransactionType.DELETE, path: [data[0].ID, data[0].Employees[2].ID] } + ]; + + DataUtil.mergeHierarchicalTransactions(data, transactions, 'Employees', 'ID', true); + + expect(data.length).toBe(1); + expect(data[0].Employees.length).toBe(1); + }); + }); +} +/* //Test merging */ + describe('DataUtil', () => { testSort(); testGroupBy(); testFilter(); testPage(); + testMerging(); }); diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts index ae79454624d..06a0103418a 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts @@ -657,7 +657,7 @@ describe('IgxTransaction', () => { expect(transaction.getState(2)).toBeUndefined(); }); - fit('Should mark update transactions state as deleted type when deleted in Hierarchical data source', () =>{ + it('Should mark update transactions state as deleted type when deleted in Hierarchical data source', () =>{ const transaction = new IgxHierarchicalTransactionService(); expect(transaction).toBeDefined(); From 1d5ab29562e83adeb1f0c40aeb1058cbda5f4363 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 22 Nov 2018 13:47:54 +0200 Subject: [PATCH 089/114] refactor(tree-grid): fix tslint errors --- .../src/lib/services/transaction/igx-transaction.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts index 06a0103418a..e207da45939 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.spec.ts @@ -651,13 +651,13 @@ describe('IgxTransaction', () => { expect(transaction.getState(2).path.length).toBe(1); expect(transaction.getState(2).path).toEqual(path); - const deleteTransaction: HierarchicalTransaction = {id: 1, type: TransactionType.DELETE, newValue: null, path: []}; + const deleteTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.DELETE, newValue: null, path: [] }; transaction.add(deleteTransaction); expect(transaction.getState(1)).toBeUndefined(); expect(transaction.getState(2)).toBeUndefined(); }); - it('Should mark update transactions state as deleted type when deleted in Hierarchical data source', () =>{ + it('Should mark update transactions state as deleted type when deleted in Hierarchical data source', () => { const transaction = new IgxHierarchicalTransactionService(); expect(transaction).toBeDefined(); @@ -675,7 +675,7 @@ describe('IgxTransaction', () => { expect(transaction.getState(2).path.length).toBe(1); expect(transaction.getState(2).path).toEqual(path); - const deleteTransaction: HierarchicalTransaction = {id: 1, type: TransactionType.DELETE, newValue: null, path: []}; + const deleteTransaction: HierarchicalTransaction = { id: 1, type: TransactionType.DELETE, newValue: null, path: [] }; transaction.add(deleteTransaction); expect(transaction.getState(1)).toBeDefined(); expect(transaction.getState(1).type).toBe(TransactionType.DELETE); From c926efd3f67e4eef870ef805073ff41b3404794c Mon Sep 17 00:00:00 2001 From: wnvko Date: Wed, 5 Dec 2018 16:39:05 +0200 Subject: [PATCH 090/114] test(igxTreeGrid): whatever I put here Damian will squash it, #2921 --- .../tree-grid/tree-grid-integration.spec.ts | 101 ++++++++---------- 1 file changed, 42 insertions(+), 59 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index a2a9d4e07f1..4018bdd5156 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -7,7 +7,7 @@ import { IgxTreeGridSimpleComponent, IgxTreeGridPrimaryForeignKeyComponent, IgxTreeGridStringTreeColumnComponent, IgxTreeGridDateTreeColumnComponent, IgxTreeGridBooleanTreeColumnComponent, IgxTreeGridRowEditingComponent, IgxTreeGridMultiColHeadersComponent, - IgxTreeGridRowEditingTransactionComponent, + IgxTreeGridRowEditingTransactionComponent, IgxTreeGridRowEditingHierarchicalDSTransactionComponent } from '../../test-utils/tree-grid-components.spec'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -25,7 +25,7 @@ import { IgxGridCellComponent } from '../grid'; const CSS_CLASS_BANNER = 'igx-banner'; const CSS_CLASS_ROW_EDITED = 'igx-grid__tr--edited'; -fdescribe('IgxTreeGrid - Integration', () => { +describe('IgxTreeGrid - Integration', () => { configureTestSuite(); let fix; let treeGrid: IgxTreeGridComponent; @@ -806,7 +806,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(trans.getTransactionLog().length).toEqual(1); expect(trans.add).toHaveBeenCalled(); expect(trans.add).toHaveBeenCalledTimes(1); - const transParams = {id: addedRowId_1, type: 'add', newValue: newRow}; + const transParams = { id: addedRowId_1, type: 'add', newValue: newRow }; expect(trans.add).toHaveBeenCalledWith(transParams); expect(treeGrid.records.get(addedRowId_1).level).toBe(0); @@ -870,59 +870,41 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); }); - it('Add a child node to a previously added parent node - Hierarchical DS - empty ChildDataKey -commit', () => { - fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); + it('Add a child node to a previously added parent node - Hierarchical DS', () => { + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); fix.detectChanges(); treeGrid = fix.componentInstance.treeGrid as IgxTreeGridComponent; - const trans = treeGrid.transactions; - const rootRow = { - ID: 11, - Name: 'Kubrat Pulev', - HireDate: new Date(2018, 10, 20), - Age: 32, - OnPTO: false, - Employees: [] - }; - - const childRow = { - ID: 12, - Name: 'Tervel Pulev', - HireDate: new Date(2018, 10, 10), - Age: 30, - OnPTO: true, - Employees: [] - }; - - const grandChildRow = { - ID: 13, - Name: 'Asparuh Pulev', - HireDate: new Date(2017, 10, 10), - Age: 14, - OnPTO: true, - Employees: [] + const rowData = { + parent: { ID: 13, Name: 'Dr. Evil', JobTitle: 'Doctor of Evilness', Age: 52 }, + child: { ID: 133, Name: 'Scott', JobTitle: `Annoying Teen, Dr. Evil's son`, Age: 17 }, + grandChild: { ID: 1337, Name: 'Mr. Bigglesworth', JobTitle: 'Evil Cat', Age: 13 } }; - - treeGrid.addRow(rootRow); - treeGrid.addRow(childRow, 11); - - expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); - - trans.commit(treeGrid.data); - - expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - - treeGrid.addRow(grandChildRow, 12); - - expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); - expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain('igx-grid__tr--edited'); - expect(treeGrid.records.get(11).level).toBe(0); - expect(treeGrid.records.get(12).level).toBe(1); - expect(treeGrid.records.get(13).level).toBe(2); - }); - + // 1. Add a row at level 0 to the grid + treeGrid.addRow(rowData.parent); + // 2. Add a child row to that parent + treeGrid.addRow(rowData.child, rowData.parent.ID); + // 3. Verify the new rows are pending with the correct styles + expect(treeGrid.getRowByKey(13).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(133).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.data.findIndex(e => e.ID === rowData.parent.ID)).toEqual(-1); + expect(treeGrid.data.findIndex(e => e.ID === rowData.child.ID)).toEqual(-1); + expect(treeGrid.transactions.getAggregatedChanges(true).length).toEqual(2); + // 4. Commit + treeGrid.transactions.commit(treeGrid.data, treeGrid.childDataKey, treeGrid.primaryKey); + // 5. verify the rows are committed, the styles are OK + expect(treeGrid.data.findIndex(e => e.ID === rowData.parent.ID)).not.toEqual(-1); + expect(treeGrid.data.findIndex(e => e.ID === rowData.child.ID)).not.toEqual(-1); + expect(treeGrid.getRowByKey(13).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(133).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.transactions.getAggregatedChanges(true).length).toEqual(0); + // 6. Add another child row at level 2 (grand-child of the first row) + treeGrid.addRow(rowData.grandChild, rowData.child.ID); + // 7. verify the pending styles is applied only to the newly added row + // and not to the previously added rows + expect(treeGrid.getRowByKey(13).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(133).nativeElement.classList).not.toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.getRowByKey(1337).nativeElement.classList).toContain(CSS_CLASS_ROW_EDITED); + expect(treeGrid.transactions.getAggregatedChanges(true).length).toEqual(1); }); it('Delete a pending parent node - Flat DS', () => { @@ -950,7 +932,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); expect(trans.add).toHaveBeenCalled(); expect(trans.add).toHaveBeenCalledTimes(1); - const transParams = {id: addedRowId, type: 'add', newValue: newRow}; + const transParams = { id: addedRowId, type: 'add', newValue: newRow }; expect(trans.add).toHaveBeenCalledWith(transParams); treeGrid.deleteRowById(treeGrid.selectedRows()[0]); @@ -998,7 +980,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); expect(trans.add).toHaveBeenCalled(); expect(trans.add).toHaveBeenCalledTimes(1); - const transParams = {id: addedRowId, path: [parentRow.rowID], newValue: newRow, type: 'add'}; + const transParams = { id: addedRowId, path: [parentRow.rowID], newValue: newRow, type: 'add' }; expect(trans.add).toHaveBeenCalledWith(transParams, null); treeGrid.deleteRowById(treeGrid.selectedRows()[0]); @@ -1044,7 +1026,7 @@ fdescribe('IgxTreeGrid - Integration', () => { expect(treeGrid.transactions.getTransactionLog().length).toEqual(1); expect(trans.add).toHaveBeenCalled(); expect(trans.add).toHaveBeenCalledTimes(1); - const transParams = {id: addedRowId, type: 'add', newValue: newRow}; + const transParams = { id: addedRowId, type: 'add', newValue: newRow }; expect(trans.add).toHaveBeenCalledWith(transParams); treeGrid.deleteRowById(treeGrid.selectedRows()[0]); @@ -1096,7 +1078,8 @@ fdescribe('IgxTreeGrid - Integration', () => { id: addedRowId, path: [treeGrid.getRowByIndex(0).rowID, parentRow.rowID], newValue: newRow, - type: 'add'}; + type: 'add' + }; expect(trans.add).toHaveBeenCalledWith(transPasrams, null); treeGrid.deleteRowById(treeGrid.selectedRows()[0]); @@ -1238,7 +1221,7 @@ fdescribe('IgxTreeGrid - Integration', () => { OnPTO: false, Employees: [] }; - const childRow = { + const childRow = { ID: 12, Name: 'Tervel Pulev', HireDate: new Date(2018, 10, 10), @@ -1246,7 +1229,7 @@ fdescribe('IgxTreeGrid - Integration', () => { OnPTO: true, Employees: [] }; - const grandChildRow = { + const grandChildRow = { ID: 13, Name: 'Asparuh Pulev', HireDate: new Date(2017, 10, 10), From 703035b3558f35183e1ba1d247d13ab45867daac Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Wed, 5 Dec 2018 16:45:29 +0200 Subject: [PATCH 091/114] feat(mask): enhance mask by adding pipes and placeholder #2337 --- CHANGELOG.md | 10 +- .../directives/mask/mask.directive.spec.ts | 205 +++++++++++++----- .../src/lib/directives/mask/mask.directive.ts | 53 ++++- 3 files changed, 200 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b321b405ba4..11f087805c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ All notable changes for each version of this project will be documented in this - `IgxIconModule`: - **Breaking change** `igxIconService` is now provided in root (providedIn: 'root') and `IgxIconModule.forRoot()` method is deprecated. - **Breaking change** `glyphName` property of the `igxIconComponent` is deprecated. +- `IgxMask`: + - `placeholder` input property is added to allow develpers to specify a custom a short hint that describes the expected value; + - `displayValuePipe` input property is provided that allows develpers to additionally transform the value on blur; + - `inputValuePipe` input property is provided that allows develpers to additionally transform the value on focus; ## 7.0.2 @@ -21,7 +25,7 @@ All notable changes for each version of this project will be documented in this - `igxNavbar`: - Added a new `igx-action-icon` directive that can be used to provide a custom template to be used instead of the default action icon on the left-most part of the navbar. (If `igx-action-icon` is provided, the default action icon will not be used.) - + ### Bug fixes - `igxGrid` @@ -43,7 +47,7 @@ All notable changes for each version of this project will be documented in this ## 6.2.3 - `igxGrid` - - `resourceStrings` property added, which allows changing/localizing strings for component. If a new instance is set, + - `resourceStrings` property added, which allows changing/localizing strings for component. If a new instance is set, the changes will be applied to the particular instance of the component: ```typescript this.grid.resourceStrings = { @@ -93,7 +97,7 @@ All notable changes for each version of this project will be documented in this - `igxDropDown` - Added a new property `maxHeight`, defining the max height of the drop down. ([#3001](https://github.com/IgniteUI/igniteui-angular/issues/3001)) - Added migrations for Sass theme properties changes in 6.2.0 ([#2994](https://github.com/IgniteUI/igniteui-angular/issues/2994)) -- Themes +- Themes - Introducing schemas for easier bootstrapping of component themes. - **Breaking change** removed $variant from `igx-checkbox-theme`, `igx-ripple-theme`, `igx-switch-theme`, `igx-input-group-theme`, `igx-slider-theme`, and `igx-tooltip-theme`. Use the `$schema` prop, now available on all component themes to change the look for a specific theme. See the [Theming](https://www.infragistics.com/products/ignite-ui-angular/angular/components/themes/schemas.html) documentation to learn more. diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts index 32958009981..d2296e6cfcb 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts @@ -1,4 +1,4 @@ -import { Component, Input, ViewChild } from '@angular/core'; +import { Component, Input, ViewChild, OnInit, ElementRef, Pipe, PipeTransform } from '@angular/core'; import { async, fakeAsync, @@ -6,7 +6,6 @@ import { tick } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; -import { By } from '@angular/platform-browser'; import { IgxInputGroupModule } from '../../input-group/input-group.component'; import { IgxMaskModule } from './mask.directive'; @@ -26,7 +25,8 @@ describe('igxMask', () => { IncludeLiteralsComponent, LetterSpaceMaskComponent, MaskComponent, - OneWayBindComponent + OneWayBindComponent, + PipesMaskComponent ], imports: [ FormsModule, @@ -41,21 +41,21 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(DefMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; expect(input.nativeElement.value).toEqual('__________'); - input.triggerEventHandler('click', {}); + input.nativeElement.dispatchEvent(new Event('click')); tick(); input.nativeElement.value = '@#$YUA123'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('@#$YUA123_'); })); @@ -63,10 +63,11 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(DigitSpaceMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); + fixture.detectChanges(); expect(input.nativeElement.value).toEqual('555 55'); @@ -76,10 +77,11 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(DigitPlusMinusMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); + fixture.detectChanges(); expect(input.nativeElement.value).toEqual('+359-884 19 08 54'); })); @@ -88,9 +90,9 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(LetterSpaceMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('AB _CD E'); @@ -100,9 +102,9 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(AlphanumSpaceMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('7c_ 8u'); @@ -112,9 +114,9 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(AnyCharMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('_=%. p]'); @@ -125,58 +127,56 @@ describe('igxMask', () => { fixture.detectChanges(); const comp = fixture.componentInstance; - const input = fixture.debugElement.query(By.css('input')); + const input = comp.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(123) 4567-890'); expect(comp.value).toEqual('1234567890'); - input.nativeElement.value = '7777'; - input.nativeElement.dispatchEvent(new Event('input')); - tick(); + comp.value = '7777'; + fixture.detectChanges(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('input')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(777) 7___-___'); expect(comp.value).toEqual('7777'); - })); it('Enter incorrect value with a preset mask', fakeAsync(() => { const fixture = TestBed.createComponent(MaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input[type=text]')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); input.nativeElement.value = 'abc4569d12'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(___) 4569-_12'); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); input.nativeElement.value = '1111111111111111111'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(111) 1111-111'); })); @@ -184,29 +184,28 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(IncludeLiteralsComponent); fixture.detectChanges(); - const comp = fixture.componentInstance; - const inputs = fixture.debugElement.queryAll(By.css('input')); + const input = fixture.componentInstance.input; - inputs[0].triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - expect(inputs[0].nativeElement.value).toEqual('(555) 55__-___'); + expect(input.nativeElement.value).toEqual('(555) 55__-___'); })); it('Correct event firing', fakeAsync(() => { const fixture = TestBed.createComponent(EventFiringComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input[type=text]')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); input.nativeElement.value = '123'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); fixture.detectChanges(); @@ -220,13 +219,12 @@ describe('igxMask', () => { fixture.detectChanges(); const comp = fixture.componentInstance; - const input = fixture.debugElement.query(By.css('input')); + const input = comp.input; expect(input.nativeElement.value).toEqual('3456'); - input.triggerEventHandler('focus', null); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('3456****'); @@ -236,7 +234,7 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', null); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('A*******'); @@ -246,7 +244,7 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(MaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; input.nativeElement.focus(); tick(); @@ -277,7 +275,7 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(MaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; input.nativeElement.focus(); tick(); @@ -306,83 +304,132 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(666) 6___-___'); })); + + it('Apply display and input pipes on blour and focus.', fakeAsync(() => { + const fixture = TestBed.createComponent(PipesMaskComponent); + fixture.detectChanges(); + + const input = fixture.componentInstance.input; + + input.nativeElement.focus(); + tick(); + + expect(input.nativeElement.value).toEqual('SSS'); + + input.nativeElement.dispatchEvent(new Event('blur')); + fixture.detectChanges(); + + expect(input.nativeElement.value).toEqual('sss'); + + input.nativeElement.dispatchEvent(new Event('focus')); + fixture.detectChanges(); + + expect(input.nativeElement.value).toEqual('SSS'); + })); }); @Component({ template: ` - + ` }) class DefMaskComponent { public mask; public value; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class MaskComponent { - public mask = '(000) 0000-000'; - public value = '1234567890'; + mask = '(000) 0000-000'; + value = '1234567890'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + - + ` }) class IncludeLiteralsComponent { public mask = '(000) 0000-000'; @Input() public value = '55555'; + + @ViewChild('input') + public input: ElementRef; + + @ViewChild('input1') + public input1: ElementRef; } @Component({ template: ` - + ` }) class DigitSpaceMaskComponent { public mask = '999999'; public value = '555 555'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class DigitPlusMinusMaskComponent { public mask = '####-### ## ## ##'; public value = '+359884190854'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class LetterSpaceMaskComponent { public mask = 'LL??LL??'; public value = 'AB 2CD E'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class AlphanumSpaceMaskComponent { public mask = 'AAAaaa'; public value = '7c 8u'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class AnyCharMaskComponent { public mask = '&&&.CCC'; public value = ' =% p]'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - ` }) class EventFiringComponent { @@ -391,6 +438,9 @@ class EventFiringComponent { raw: string; formatted: string; + @ViewChild('input') + public input: ElementRef; + handleValueChange(event) { this.raw = event.rawValue; this.formatted = event.formattedValue; @@ -398,9 +448,44 @@ class EventFiringComponent { } @Component({ template: ` - + ` }) class OneWayBindComponent { myMask = 'AAAAAAAA'; value = 3456; + + @ViewChild('input') + public input: ElementRef; +} + +@Component({ template: ` + + ` }) +class PipesMaskComponent { + public mask = 'CCC'; + public value = 'SSS'; + + public displayFormat = new DisplayFormatPipe(); + public inputFormat = new InputFormatPipe(); + + @ViewChild('input') + public input: ElementRef; +} + +@Pipe({ name: "inputFormat" }) +export class InputFormatPipe implements PipeTransform { + transform(value: any): string { + return value.toUpperCase(); + } +} + +@Pipe({ name: "displayFormat" }) +export class DisplayFormatPipe implements PipeTransform { + transform(value: any): string { + return value.toLowerCase(); + } } diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts index 1c1fa4913ba..17faa07c555 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts @@ -7,7 +7,8 @@ import { Input, NgModule, OnInit, - Output + Output, + PipeTransform } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { KEYS, MaskHelper } from './mask-helper'; @@ -47,6 +48,34 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Input() public includeLiterals: boolean; + /** + * Specifies a placeholder. + * ```html + * + * ``` + * @memberof IgxMaskDirective + */ + @Input() + public placeholder: string; + /** + * Specifies a pipe to be used on blur. + * ```html + * + * ``` + * @memberof IgxMaskDirective + */ + @Input() + public displayValuePipe: PipeTransform; + /** + * Specifies a pipe to be used on focus. + * ```html + * + * ``` + * @memberof IgxMaskDirective + */ + @Input() + public inputValuePipe: PipeTransform; + /** *@hidden */ @@ -152,7 +181,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._maskOptions.format = this.mask ? this.mask : 'CCCCCCCCCC'; this._maskOptions.promptChar = this.promptChar ? this.promptChar : '_'; - this.nativeElement.setAttribute('placeholder', this.mask); + this.nativeElement.setAttribute('placeholder', this.placeholder ? this.placeholder : this.mask); } /** *@hidden @@ -228,10 +257,24 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { /** *@hidden */ - @HostListener('focus', ['$event']) - public onFocus(event) { - this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions); + @HostListener('focus', ['$event.target.value']) + public onFocus(value) { + if (this.inputValuePipe) { + this.value = this.inputValuePipe.transform(value); + } else { + this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions); + } } + /** + *@hidden + */ + @HostListener('blur', ['$event.target.value']) + public onBlur(value) { + if (this.displayValuePipe) { + this.value = this.displayValuePipe.transform(value); + } + } + /** *@hidden */ From ac0cb6d122b9283984ffbc02c2efd030fc1ae0cb Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Wed, 5 Dec 2018 18:10:00 +0200 Subject: [PATCH 092/114] feat(mask): update README.md file #2337 --- .../src/lib/directives/mask/README.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/projects/igniteui-angular/src/lib/directives/mask/README.md b/projects/igniteui-angular/src/lib/directives/mask/README.md index aced6b6745c..9f67def51aa 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/README.md +++ b/projects/igniteui-angular/src/lib/directives/mask/README.md @@ -50,6 +50,29 @@ handleValueChange(event) { ``` +Use the `placeholder` input property to specify a custom a short hint that describes the expected value. +```typescript +placeholder = 'hello'; +``` +```html + +``` + +Use the `inputValuePipe` and `displayValuePipe` input properties to additionally transform the value on focus and blur. +```typescript +@Pipe({ name: "displayFormat" }) +export class DisplayFormatPipe implements PipeTransform { + transform(value: any): string { + return value.toLowerCase(); + } +} + +displayFormat = new DisplayFormatPipe(); +``` +```html + +``` + ### API ### Inputs @@ -58,6 +81,9 @@ handleValueChange(event) { | `mask`| `String` | Represents the current mask. | | `promptChar`| `String` | Character representing a fillable spot in the mask. | | `includeLiterals`| `Boolean` | Include or exclude literals in the raw value. | +| `placeholder`| `string` | Specifies a short hint that describes the expected value. | +| `displayValuePipe`| `PipeTransform` | A pipe to transform the input value on blur. | +| `inputValuePipe`| `PipeTransform` | A pipe to transform the input value on focus. | ### Outputs | Name | Return Type | Description | From 3c4027e90bae0cb6af667bfd31c85e4eec6ea0a2 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Thu, 6 Dec 2018 09:53:58 +0200 Subject: [PATCH 093/114] feat(mask): address review comments #2337 --- CHANGELOG.md | 4 +- .../src/lib/directives/mask/README.md | 6 +-- .../directives/mask/mask.directive.spec.ts | 12 +++-- .../src/lib/directives/mask/mask.directive.ts | 44 +++++++++++++++---- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f087805c8..0cee873fbff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,9 @@ All notable changes for each version of this project will be documented in this - **Breaking change** `igxIconService` is now provided in root (providedIn: 'root') and `IgxIconModule.forRoot()` method is deprecated. - **Breaking change** `glyphName` property of the `igxIconComponent` is deprecated. - `IgxMask`: - - `placeholder` input property is added to allow develpers to specify a custom a short hint that describes the expected value; + - `placeholder` input property is added to allow developers to specify the placeholder attribute of the host input element that the `igxMask` is applied on; - `displayValuePipe` input property is provided that allows develpers to additionally transform the value on blur; - - `inputValuePipe` input property is provided that allows develpers to additionally transform the value on focus; + - `focusedValuePipe` input property is provided that allows devel0pers to additionally transform the value on focus; ## 7.0.2 diff --git a/projects/igniteui-angular/src/lib/directives/mask/README.md b/projects/igniteui-angular/src/lib/directives/mask/README.md index 9f67def51aa..bae586552b1 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/README.md +++ b/projects/igniteui-angular/src/lib/directives/mask/README.md @@ -50,7 +50,7 @@ handleValueChange(event) { ``` -Use the `placeholder` input property to specify a custom a short hint that describes the expected value. +Use the `placeholder` input property to specify the placeholder attribute of the host input element that the `igxMask` is applied on. ```typescript placeholder = 'hello'; ``` @@ -58,7 +58,7 @@ placeholder = 'hello'; ``` -Use the `inputValuePipe` and `displayValuePipe` input properties to additionally transform the value on focus and blur. +Use the `focusedValuePipe` and `displayValuePipe` input properties to additionally transform the value on focus and blur. ```typescript @Pipe({ name: "displayFormat" }) export class DisplayFormatPipe implements PipeTransform { @@ -83,7 +83,7 @@ displayFormat = new DisplayFormatPipe(); | `includeLiterals`| `Boolean` | Include or exclude literals in the raw value. | | `placeholder`| `string` | Specifies a short hint that describes the expected value. | | `displayValuePipe`| `PipeTransform` | A pipe to transform the input value on blur. | -| `inputValuePipe`| `PipeTransform` | A pipe to transform the input value on focus. | +| `focusedValuePipe`| `PipeTransform` | A pipe to transform the input value on focus. | ### Outputs | Name | Return Type | Description | diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts index d2296e6cfcb..569b589896a 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts @@ -311,7 +311,7 @@ describe('igxMask', () => { expect(input.nativeElement.value).toEqual('(666) 6___-___'); })); - it('Apply display and input pipes on blour and focus.', fakeAsync(() => { + it('Apply display and input pipes on blur and focus.', fakeAsync(() => { const fixture = TestBed.createComponent(PipesMaskComponent); fixture.detectChanges(); @@ -448,7 +448,11 @@ class EventFiringComponent { } @Component({ template: ` - + ` }) class OneWayBindComponent { myMask = 'AAAAAAAA'; @@ -476,14 +480,14 @@ class PipesMaskComponent { public input: ElementRef; } -@Pipe({ name: "inputFormat" }) +@Pipe({ name: 'inputFormat' }) export class InputFormatPipe implements PipeTransform { transform(value: any): string { return value.toUpperCase(); } } -@Pipe({ name: "displayFormat" }) +@Pipe({ name: 'displayFormat' }) export class DisplayFormatPipe implements PipeTransform { transform(value: any): string { return value.toLowerCase(); diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts index 17faa07c555..e21849a83c0 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts @@ -29,6 +29,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Input('igxMask') public mask: string; + /** * Sets the character representing a fillable spot in the input mask. * Default value is "'_'". @@ -39,6 +40,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Input() public promptChar: string; + /** * Specifies if the bound value includes the formatting symbols. * ```html @@ -48,15 +50,17 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Input() public includeLiterals: boolean; + /** * Specifies a placeholder. * ```html - * + * * ``` * @memberof IgxMaskDirective */ @Input() public placeholder: string; + /** * Specifies a pipe to be used on blur. * ```html @@ -66,21 +70,23 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Input() public displayValuePipe: PipeTransform; + /** * Specifies a pipe to be used on focus. * ```html - * + * * ``` * @memberof IgxMaskDirective */ @Input() - public inputValuePipe: PipeTransform; + public focusedValuePipe: PipeTransform; /** *@hidden */ @Input() private dataValue: string; + /** * Emits an event each time the value changes. * Provides `rawValue: string` and `formattedValue: string` as event arguments. @@ -90,52 +96,57 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Output() public onValueChange = new EventEmitter(); + /** *@hidden */ private get value() { return this.nativeElement.value; } + /** *@hidden */ private set value(val) { this.nativeElement.value = val; } + /** *@hidden */ private get nativeElement() { return this.elementRef.nativeElement; } + /** *@hidden */ private get selectionStart() { return this.nativeElement.selectionStart; } + /** *@hidden */ private get selectionEnd() { return this.nativeElement.selectionEnd; } + /** *@hidden */ private _ctrlDown: boolean; - /** - *@hidden - */ - private _cachedVal: string; + /** *@hidden */ private _paste: boolean; + /** *@hidden */ private _selection: number; + /** *@hidden */ @@ -143,26 +154,32 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { format: '', promptChar: '' }; + /** *@hidden */ private _key; + /** *@hidden */ private _cursorOnPaste; + /** *@hidden */ private _valOnPaste; + /** *@hidden */ private maskHelper: MaskHelper; + /** *@hidden */ private _onTouchedCallback: () => void = noop; + /** *@hidden */ @@ -171,6 +188,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { constructor(private elementRef: ElementRef) { this.maskHelper = new MaskHelper(); } + /** *@hidden */ @@ -183,6 +201,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._maskOptions.promptChar = this.promptChar ? this.promptChar : '_'; this.nativeElement.setAttribute('placeholder', this.placeholder ? this.placeholder : this.mask); } + /** *@hidden */ @@ -201,6 +220,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._key = key; this._selection = Math.abs(this.selectionEnd - this.selectionStart); } + /** *@hidden */ @@ -212,6 +232,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._ctrlDown = false; } } + /** *@hidden */ @@ -222,6 +243,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._valOnPaste = this.value; this._cursorOnPaste = this.getCursorPosition(); } + /** *@hidden */ @@ -254,6 +276,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this.onValueChange.emit({ rawValue: rawVal, formattedValue: this.value }); } + /** *@hidden */ @@ -265,6 +288,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions); } } + /** *@hidden */ @@ -281,12 +305,14 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { private getCursorPosition(): number { return this.nativeElement.selectionStart; } + /** *@hidden */ private setCursorPosition(start: number, end: number = start): void { this.nativeElement.setSelectionRange(start, end); } + /** *@hidden */ @@ -302,19 +328,21 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this.onValueChange.emit({ rawValue: value, formattedValue: this.value }); } + /** *@hidden */ public registerOnChange(fn: (_: any) => void) { this._onChangeCallback = fn; } + /** *@hidden */ public registerOnTouched(fn: () => void) { this._onTouchedCallback = fn; } } + /** * The IgxMaskModule provides the {@link IgxMaskDirective} inside your application. */ - export interface IMaskEventArgs { rawValue: string; formattedValue: string; From c617527ad2fe31a3b02b02124137a1782a025a01 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 6 Dec 2018 14:02:40 +0200 Subject: [PATCH 094/114] refactor(igxTreeGrid): do not change path during transaction merge --- .../igniteui-angular/src/lib/data-operations/data-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index d3c5d6919e7..158adbd420f 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -228,9 +228,9 @@ export class DataUtil { for (let i = 0; i < transactions.length; i++) { const transaction = transactions[i]; - const path = transaction.path; - if (path) { + if (transaction.path) { + const path = [...transaction.path]; // We need to get parent data row. If there is a path and path contains this row id, // this is the case for UPDATE and DELETE transactions type, remove the last id from // the path From 4e6568ae23991b37a8048b49659e30f4da483279 Mon Sep 17 00:00:00 2001 From: NikolayAlipiev Date: Thu, 6 Dec 2018 14:51:27 +0200 Subject: [PATCH 095/114] fix(igx-combo): deselect child rows for nested data #3068 --- .../src/lib/core/selection.ts | 2 +- .../grids/tree-grid/tree-grid-api.service.ts | 14 ++++++++++++++ .../tree-grid/tree-grid-selection.spec.ts | 19 +++++++++++++++++++ .../grids/tree-grid/tree-grid.component.ts | 7 +++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/core/selection.ts b/projects/igniteui-angular/src/lib/core/selection.ts index 1835316806c..41ce9496d79 100644 --- a/projects/igniteui-angular/src/lib/core/selection.ts +++ b/projects/igniteui-angular/src/lib/core/selection.ts @@ -208,7 +208,7 @@ export class IgxSelectionAPIService { * @returns If all items are selected. */ public are_all_selected(componentID: string, dataCount: number): boolean { - return this.size(componentID) === dataCount; + return dataCount > 0 && dataCount === this.size(componentID); } /** diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index d7c56066018..d12129c2907 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -123,4 +123,18 @@ export class IgxTreeGridAPIService extends GridBaseAPIService { TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); }); + it('when all items are selected and then some of the selected rows are deleted, still all the items should be selected', () => { + treeGrid.selectAllRows(); + fix.detectChanges(); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + fix.detectChanges(); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + fix.detectChanges(); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + + treeGrid.deleteRowById(treeGrid.selectedRows()[0]); + fix.detectChanges(); + // When deleting the last selected row, header checkbox will be unchecked. + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); + }); + it('should be able to select row of any level', () => { treeGrid.selectRows([treeGrid.getRowByIndex(0).rowID], true); fix.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index f770e5c43ae..b2d3572a280 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -429,6 +429,13 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { const childData = record.parent ? record.parent.data[this.childDataKey] : this.data; index = this.primaryKey ? childData.map(c => c[this.primaryKey]).indexOf(rowID) : childData.indexOf(rowID); + + const selectedChildren = []; + this._gridAPI.get_selected_children(this.id, record, selectedChildren); + if (selectedChildren.length > 0) { + this.deselectRows(selectedChildren); + } + if (this.transactions.enabled) { this.transactions.add({ id: rowID, From 78e66248e13589dd7eb279a1121dc575bdc6117c Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Thu, 6 Dec 2018 15:36:46 +0200 Subject: [PATCH 096/114] docs(readme): Changes to the Add/Update sections.Adding Crypto App description. --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 16281efaac6..00ce5bb62bb 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ Including the `igniteui-angular` and `igniteui-cli` packages to your project: ng add igniteui-angular ``` +After this operation you can use the Ignite UI CLI commands. + ## Updating Existing Project Analyze your project for possible migrations: @@ -92,7 +94,9 @@ ng update If there are new versions available, update your packages: ``` -ng update +ng update igniteui-angular +... +ng update igniteui-cli ``` ## Building the Library @@ -155,6 +159,8 @@ You can include Ignite UI for Angular in your project as a dependency using the ## Demo Apps & Documentation The [Warehouse Picklist App](https://github.com/IgniteUI/warehouse-js-blocks) demonstrates using several Ignite UI for Angular widgets together to build a modern, mobile app. +The [Crypto Portfolio App](https://igniteui.github.io/crypto-portfolio-app/#/home) is a delicate web and mobile app, developed with Ignite UI for Angular most solid components and styled with one of a kind theming engine. + To get started with the Data Grid, use the steps in the [grid walk-through](https://www.infragistics.com/angular-samples/components/grid.html). All help, related API documents and walk-throughs can be found for each control [here](https://www.infragistics.com/angular-samples/components/grid.html). From b7d28dd8d3643b58a2e58d77e12e0ce2c3de36d1 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 6 Dec 2018 15:46:35 +0200 Subject: [PATCH 097/114] refactor(igxTreeGrid): remove path splice in merge, #2921 Also fix incorrect test. --- .../src/lib/data-operations/data-util.spec.ts | 12 ++++++------ .../src/lib/data-operations/data-util.ts | 9 +-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts index a11fa59d56b..4e8559f0273 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts @@ -523,13 +523,13 @@ function testMerging() { id: data[2].Employees[0].ID, newValue: updateChildRow1, type: TransactionType.UPDATE, - path: [data[2].ID, data[2].Employees[0].ID] + path: [data[2].ID] }, { id: (data[0].Employees[2] as any).Employees[0].ID, newValue: updateChildRow2, type: TransactionType.UPDATE, - path: [data[0].ID, data[0].Employees[2].ID, (data[0].Employees[2] as any).Employees[0].ID] + path: [data[0].ID, data[0].Employees[2].ID] }, ]; @@ -548,13 +548,13 @@ function testMerging() { const data = SampleTestData.employeeSmallTreeData(); const transactions: HierarchicalTransaction[] = [ // root row with no children - { id: data[1].ID, newValue: null, type: TransactionType.DELETE, path: [data[1].ID] }, + { id: data[1].ID, newValue: null, type: TransactionType.DELETE, path: [] }, // root row with children - { id: data[2].ID, newValue: null, type: TransactionType.DELETE, path: [data[2].ID] }, + { id: data[2].ID, newValue: null, type: TransactionType.DELETE, path: [] }, // child row with no children - { id: data[0].Employees[0].ID, newValue: null, type: TransactionType.DELETE, path: [data[0].ID, data[0].Employees[0].ID] }, + { id: data[0].Employees[0].ID, newValue: null, type: TransactionType.DELETE, path: [data[0].ID] }, // child row with children - { id: data[0].Employees[2].ID, newValue: null, type: TransactionType.DELETE, path: [data[0].ID, data[0].Employees[2].ID] } + { id: data[0].Employees[2].ID, newValue: null, type: TransactionType.DELETE, path: [data[0].ID] } ]; DataUtil.mergeHierarchicalTransactions(data, transactions, 'Employees', 'ID', true); diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 158adbd420f..919c1ac5966 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -230,14 +230,7 @@ export class DataUtil { const transaction = transactions[i]; if (transaction.path) { - const path = [...transaction.path]; - // We need to get parent data row. If there is a path and path contains this row id, - // this is the case for UPDATE and DELETE transactions type, remove the last id from - // the path - if (path.find(id => id === transaction.id)) { - path.splice(-1, 1); - } - const dataRow = this.findDataRowFromPath(data, primaryKey, childDataKey, path); + const dataRow = this.findDataRowFromPath(data, primaryKey, childDataKey, transaction.path); switch (transaction.type) { case TransactionType.ADD: // if there is no dataRow this is ADD row at root level From cf29dd08d93a198c6204ba98f70faa73122077ac Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Thu, 6 Dec 2018 16:31:35 +0200 Subject: [PATCH 098/114] fix(mask): broken build #2337 --- .../src/lib/directives/mask/mask.directive.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts index e21849a83c0..3351e72dff0 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts @@ -282,8 +282,8 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @HostListener('focus', ['$event.target.value']) public onFocus(value) { - if (this.inputValuePipe) { - this.value = this.inputValuePipe.transform(value); + if (this.focusedValuePipe) { + this.value = this.focusedValuePipe.transform(value); } else { this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions); } From 5fda34120242b25d90688ed1fce796d22caeb692 Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 6 Dec 2018 16:39:24 +0200 Subject: [PATCH 099/114] refactor(igxTreeGrid): update mergeHierarchicalTransaction, #2921 --- .../src/lib/data-operations/data-util.ts | 51 +++++++------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 919c1ac5966..99be1328679 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -230,31 +230,28 @@ export class DataUtil { const transaction = transactions[i]; if (transaction.path) { - const dataRow = this.findDataRowFromPath(data, primaryKey, childDataKey, transaction.path); + const parent = this.findParentFromPath(data, primaryKey, childDataKey, transaction.path); + let collection: any[] = parent ? parent[childDataKey] : data; switch (transaction.type) { case TransactionType.ADD: - // if there is no dataRow this is ADD row at root level - if (dataRow) { - if (!dataRow[childDataKey]) { - dataRow[childDataKey] = []; - } - if (!dataRow[childDataKey].find(r => r[primaryKey] === transaction.id)) { - dataRow[childDataKey].push(transaction.newValue); - } - } else { - data.push(transaction.newValue); + // if there is no parent this is ADD row at root level + if (parent && !parent[childDataKey]) { + parent[childDataKey] = collection = []; } + collection.push(transaction.newValue); break; case TransactionType.UPDATE: - const collectionToUpdate: any[] = this.findCollectionToManipulate(childDataKey, dataRow, data); - const updateIndex: number = collectionToUpdate.findIndex(r => r[primaryKey] === transaction.id); - collectionToUpdate[updateIndex] = mergeObjects(cloneValue(collectionToUpdate[updateIndex]), transaction.newValue); + const updateIndex = collection.findIndex(x => x[primaryKey] === transaction.id); + if (updateIndex !== -1) { + collection[updateIndex] = mergeObjects(cloneValue(collection[updateIndex]), transaction.newValue); + } break; case TransactionType.DELETE: if (deleteRows) { - const collectionToDelete: any[] = this.findCollectionToManipulate(childDataKey, dataRow, data); - const deleteIndex: number = collectionToDelete.findIndex(r => r[primaryKey] === transaction.id); - collectionToDelete.splice(deleteIndex, 1); + const deleteIndex = collection.findIndex(r => r[primaryKey] === transaction.id); + if (deleteIndex !== -1) { + collection.splice(deleteIndex, 1); + } } break; } @@ -266,27 +263,17 @@ export class DataUtil { return data; } - private static findDataRowFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any { + private static findParentFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any { let collection: any[] = data; let result: any; - for (let i = 0; i < path.length; i++) { - const rowIndex = collection ? collection.findIndex(r => r[primaryKey] === path[i]) : undefined; - result = collection ? collection[rowIndex] : undefined; + + for (const id of path) { + result = collection && collection.find(x => x[primaryKey] === id); if (!result) { break; } - collection = result[childDataKey]; - } - return result; - } - - private static findCollectionToManipulate(childDataKey: any, dataRow: any, data: any[]): any[] { - let result: any[]; - if (dataRow && dataRow[childDataKey]) { - result = dataRow[childDataKey]; - } else { - result = data; + collection = result[childDataKey]; } return result; From 90add5cff9fce11b8c927059c3208241a74737fd Mon Sep 17 00:00:00 2001 From: wnvko Date: Thu, 6 Dec 2018 16:45:34 +0200 Subject: [PATCH 100/114] chore(igxTreeGrid): remove commented rows, #2921 --- projects/igniteui-angular/src/lib/grids/api.service.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/api.service.ts b/projects/igniteui-angular/src/lib/grids/api.service.ts index 628daef0267..610e9bf08c8 100644 --- a/projects/igniteui-angular/src/lib/grids/api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/api.service.ts @@ -421,11 +421,6 @@ export class GridBaseAPIService { grid.transactions.endPending(false); } this.updateData(grid, rowID, data[index], emitArgs.oldValue, emitArgs.newValue); - // if (grid.transactions.enabled && emitArgs.newValue !== null) { - // grid.transactions.add({id: rowID, newValue: emitArgs.newValue, type: TransactionType.UPDATE}, emitArgs.oldValue); - // } else if (emitArgs.newValue !== null && emitArgs.newValue !== undefined) { - // Object.assign(data[index], emitArgs.newValue); - // } if (currentGridState.isRowSelected) { grid.selection.deselect_item(id, rowID); const newRowID = (grid.primaryKey) ? emitArgs.newValue[grid.primaryKey] : emitArgs.newValue; @@ -609,9 +604,9 @@ export class GridBaseAPIService { return column.dataType === DataType.Number; } - public get_all_data(id: string, transactions?: boolean): any[] { + public get_all_data(id: string, includeTransactions = false): any[] { const grid = this.get(id); - const data = transactions ? grid.dataWithAddedInTransactionRows : grid.data; + const data = includeTransactions ? grid.dataWithAddedInTransactionRows : grid.data; return data ? data : []; } From f8ebcdbe7fd1f4acb6680864f5d2d1f4b0a5e82e Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Thu, 6 Dec 2018 17:14:43 +0200 Subject: [PATCH 101/114] docs(readme): Updating the Adding section. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00ce5bb62bb..e2680e160dd 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,8 @@ Including the `igniteui-angular` and `igniteui-cli` packages to your project: ng add igniteui-angular ``` -After this operation you can use the Ignite UI CLI commands. +After this operation you can use the Ignite UI CLI commands in your project, such as `ig` and `ig add`. +[Learn more](https://github.com/IgniteUI/igniteui-cli#usage) ## Updating Existing Project From 2b4108961dd976bd7bdd4e90d299e361f143b6f5 Mon Sep 17 00:00:00 2001 From: NikolayAlipiev Date: Thu, 6 Dec 2018 18:56:41 +0200 Subject: [PATCH 102/114] refactor(igx-tree-grid): refactor loop #1852 --- .../src/lib/grids/tree-grid/tree-grid-api.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index d12129c2907..044aca3fc71 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -129,8 +129,7 @@ export class IgxTreeGridAPIService extends GridBaseAPIService Date: Thu, 6 Dec 2018 17:25:22 +0200 Subject: [PATCH 103/114] feat(mask): fix bugs and address review comments #2337 --- CHANGELOG.md | 4 +- .../src/lib/directives/mask/mask-helper.ts | 16 +++++++ .../directives/mask/mask.directive.spec.ts | 44 +++++++++++++++++-- .../src/lib/directives/mask/mask.directive.ts | 8 +++- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cee873fbff..cbbd4467e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ All notable changes for each version of this project will be documented in this - **Breaking change** `glyphName` property of the `igxIconComponent` is deprecated. - `IgxMask`: - `placeholder` input property is added to allow developers to specify the placeholder attribute of the host input element that the `igxMask` is applied on; - - `displayValuePipe` input property is provided that allows develpers to additionally transform the value on blur; - - `focusedValuePipe` input property is provided that allows devel0pers to additionally transform the value on focus; + - `displayValuePipe` input property is provided that allows developers to additionally transform the value on blur; + - `focusedValuePipe` input property is provided that allows developers to additionally transform the value on focus; ## 7.0.2 diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask-helper.ts b/projects/igniteui-angular/src/lib/directives/mask/mask-helper.ts index 8462ca73013..babb30b81c2 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask-helper.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask-helper.ts @@ -87,6 +87,22 @@ export class MaskHelper { return inputValue; } + public parseMask(maskOptions): string { + let outputVal = ''; + const mask: string = maskOptions.format; + const literals: Map = this.getMaskLiterals(mask); + + for (const maskSym of mask) { + outputVal += maskOptions.promptChar; + } + + literals.forEach((val: string, key: number) => { + outputVal = this.replaceCharAt(outputVal, key, val); + }); + + return outputVal; + } + public parseValueByMaskOnInit(inputVal, maskOptions): string { let outputVal = ''; let value = ''; diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts index 569b589896a..ad79f6efa66 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts @@ -26,7 +26,8 @@ describe('igxMask', () => { LetterSpaceMaskComponent, MaskComponent, OneWayBindComponent, - PipesMaskComponent + PipesMaskComponent, + PlaceholderMaskComponent ], imports: [ FormsModule, @@ -43,7 +44,8 @@ describe('igxMask', () => { const input = fixture.componentInstance.input; - expect(input.nativeElement.value).toEqual('__________'); + expect(input.nativeElement.value).toEqual(''); + expect(input.nativeElement.getAttribute('placeholder')).toEqual('CCCCCCCCCC'); input.nativeElement.dispatchEvent(new Event('click')); tick(); @@ -332,6 +334,28 @@ describe('igxMask', () => { expect(input.nativeElement.value).toEqual('SSS'); })); + + it('Apply placehodler when value is not defined.', fakeAsync(() => { + const fixture = TestBed.createComponent(PlaceholderMaskComponent); + fixture.detectChanges(); + + const input = fixture.componentInstance.input; + + expect(input.nativeElement.value).toEqual(''); + expect(input.nativeElement.placeholder).toEqual('hello'); + + input.nativeElement.dispatchEvent(new Event('focus')); + fixture.detectChanges(); + + expect(input.nativeElement.value).toEqual('(__) (__)'); + expect(input.nativeElement.placeholder).toEqual('hello'); + + input.nativeElement.dispatchEvent(new Event('blur')); + fixture.detectChanges(); + + expect(input.nativeElement.value).toEqual(''); + expect(input.nativeElement.placeholder).toEqual('hello'); + })); }); @Component({ template: ` @@ -462,10 +486,24 @@ class OneWayBindComponent { public input: ElementRef; } +@Component({ template: ` + + ` }) +class PlaceholderMaskComponent { + public mask = '(00) (00)'; + public value = null; + + @ViewChild('input') + public input: ElementRef; +} + @Component({ template: ` ` }) diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts index 3351e72dff0..d8fd233fc8e 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts @@ -199,7 +199,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._maskOptions.format = this.mask ? this.mask : 'CCCCCCCCCC'; this._maskOptions.promptChar = this.promptChar ? this.promptChar : '_'; - this.nativeElement.setAttribute('placeholder', this.placeholder ? this.placeholder : this.mask); + this.nativeElement.setAttribute('placeholder', this.placeholder ? this.placeholder : this._maskOptions.format); } /** @@ -296,6 +296,8 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { public onBlur(value) { if (this.displayValuePipe) { this.value = this.displayValuePipe.transform(value); + } else if (value === this.maskHelper.parseMask(this._maskOptions)) { + this.value = ''; } } @@ -321,7 +323,9 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._maskOptions.promptChar = this.promptChar.substring(0, 1); } - this.value = this.maskHelper.parseValueByMaskOnInit(value, this._maskOptions); + if (value) { + this.value = this.maskHelper.parseValueByMaskOnInit(value, this._maskOptions); + } this.dataValue = this.includeLiterals ? this.value : value; this._onChangeCallback(this.dataValue); From 867588c6eccab6f7ec3ba4ea9601b6aa4e8de972 Mon Sep 17 00:00:00 2001 From: wnvko Date: Fri, 7 Dec 2018 07:58:09 +0200 Subject: [PATCH 104/114] chore(igxTreGeid): update CHANGELOG.md, #2921 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b321b405ba4..65f75125adb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes for each version of this project will be documented in this - `IgxIconModule`: - **Breaking change** `igxIconService` is now provided in root (providedIn: 'root') and `IgxIconModule.forRoot()` method is deprecated. - **Breaking change** `glyphName` property of the `igxIconComponent` is deprecated. +- `IgxTreeGrid`: + - Batch editing - an injectable transaction provider accumulates pending changes, which are not directly applied to the grid's data source. Those can later be inspected, manipulated and submitted at once. Changes are collected for individual cells or rows, depending on editing mode, and accumulated per data row/record. ## 7.0.2 From 8422d08c6ae2aadac16adfe067b3c9bb4a2c520a Mon Sep 17 00:00:00 2001 From: wnvko Date: Fri, 7 Dec 2018 08:04:33 +0200 Subject: [PATCH 105/114] refactor(igxTreeGrid): make deleteRowById protected and refactor it, #2921 Refactor and deleteRowFromData - it should not use data with added rows. Switch primaryKey and childDataKey parameters positions in commit function. --- .../src/lib/grids/grid-base.component.ts | 7 +-- .../tree-grid/tree-grid-integration.spec.ts | 8 +-- .../grids/tree-grid/tree-grid.component.ts | 55 ++++++++++--------- .../igx-hierarchical-transaction.ts | 4 +- .../tree-grid-flat-data.sample.ts | 2 +- src/app/tree-grid/tree-grid.sample.ts | 4 +- 6 files changed, 39 insertions(+), 41 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index 0bb9d4cdf51..84cfd6badad 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -2896,11 +2896,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } } - /** - * @hidden - * @param - */ - public deleteRowById(rowId: any) { + /** @hidden */ + protected deleteRowById(rowId: any) { let index: number; const data = this.gridAPI.get_all_data(this.id); if (this.primaryKey) { diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 4018bdd5156..c4ac2deb7cf 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -668,7 +668,7 @@ describe('IgxTreeGrid - Integration', () => { expect(targetCell.nativeElement.classList).toContain('igx-grid__td--edited'); // Commit - trans.commit(treeGrid.data, treeGrid.childDataKey, treeGrid.primaryKey); + trans.commit(treeGrid.data, treeGrid.primaryKey, treeGrid.childDataKey); tick(); // Verify the correct value is set @@ -749,7 +749,7 @@ describe('IgxTreeGrid - Integration', () => { expect(rowData[475].Age).not.toEqual(treeGrid.getRowByKey(475).rowData.Age); expect(rowData[19].Name).not.toEqual(treeGrid.getRowByKey(19).rowData.Name); expect(treeGridData[0].Employees[475]).toEqual(initialData[0].Employees[475]); - trans.commit(treeGridData, treeGrid.childDataKey, treeGrid.primaryKey); + trans.commit(treeGridData, treeGrid.primaryKey, treeGrid.childDataKey); expect(treeGridData[0].Name).toEqual('Testy Testington'); expect(treeGridData[0].Employees[0].Age).toEqual(42); expect(treeGridData[1].Name).toEqual('Old Richard'); @@ -890,7 +890,7 @@ describe('IgxTreeGrid - Integration', () => { expect(treeGrid.data.findIndex(e => e.ID === rowData.child.ID)).toEqual(-1); expect(treeGrid.transactions.getAggregatedChanges(true).length).toEqual(2); // 4. Commit - treeGrid.transactions.commit(treeGrid.data, treeGrid.childDataKey, treeGrid.primaryKey); + treeGrid.transactions.commit(treeGrid.data, treeGrid.primaryKey, treeGrid.childDataKey); // 5. verify the rows are committed, the styles are OK expect(treeGrid.data.findIndex(e => e.ID === rowData.parent.ID)).not.toEqual(-1); expect(treeGrid.data.findIndex(e => e.ID === rowData.child.ID)).not.toEqual(-1); @@ -1241,7 +1241,7 @@ describe('IgxTreeGrid - Integration', () => { treeGrid.addRow(childRow, 11); expect(treeGrid.getRowByKey(11).nativeElement.classList).toContain('igx-grid__tr--edited'); expect(treeGrid.getRowByKey(12).nativeElement.classList).toContain('igx-grid__tr--edited'); - trans.commit(treeGrid.data, treeGrid.childDataKey, treeGrid.primaryKey); + trans.commit(treeGrid.data, treeGrid.primaryKey, treeGrid.childDataKey); expect(treeGrid.getRowByKey(11).nativeElement.classList).not.toContain('igx-grid__tr--edited'); expect(treeGrid.getRowByKey(12).nativeElement.classList).not.toContain('igx-grid__tr--edited'); treeGrid.addRow(grandChildRow, 12); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index c8a88f1ac8a..ff1de5d61f9 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -397,30 +397,29 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } - /** - * Removes the `IgxGridRowComponent` and the corresponding data record by primary key. - * Requires that the `primaryKey` property is set. - * The method accept rowSelector as a parameter, which is the rowID. - * ```typescript - * this.grid1.deleteRow(0); - * ``` - * @param rowSelector - * @memberof IgxGridBaseComponent - */ - public deleteRowById(rowId: any) { - if (this.primaryKey && this.foreignKey && this.cascadeOnDelete && this.transactions.enabled) { + /** @hidden */ + protected deleteRowById(rowId: any) { + // if this is flat self-referencing data, and CascadeOnDelete is set to true + // and if we have transactions we should start pending transaction. This allows + // us in case of delete action to delete all child rows as single undo action + const flatDataWithCascadeOnDeleteAndTransactions = + this.primaryKey && + this.foreignKey && + this.cascadeOnDelete && + this.transactions.enabled; + + if (flatDataWithCascadeOnDeleteAndTransactions) { this.transactions.startPending(); } + super.deleteRowById(rowId); - if (this.primaryKey && this.foreignKey && this.cascadeOnDelete && this.transactions.enabled) { + if (flatDataWithCascadeOnDeleteAndTransactions) { this.transactions.endPending(true); } } - /** - * @hidden - */ + /** @hidden */ protected deleteRowFromData(rowID: any, index: number) { if (this.primaryKey && this.foreignKey) { super.deleteRowFromData(rowID, index); @@ -436,21 +435,23 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } } else { const record = this.records.get(rowID); - const childData = record.parent ? record.parent.data[this.childDataKey] : - this.transactions.enabled ? this._gridAPI.get_all_data(this.id, true) : this.data; - index = this.primaryKey ? childData.map(c => c[this.primaryKey]).indexOf(rowID) : - childData.indexOf(rowID); + const collection = record.parent ? record.parent.data[this.childDataKey] : this.data; + index = this.primaryKey ? + collection.map(c => c[this.primaryKey]).indexOf(rowID) : + collection.indexOf(rowID); + if (this.transactions.enabled) { const path = this.generateRowPath(rowID); this.transactions.add({ - id: rowID, - type: TransactionType.DELETE, - newValue: null, - path: path - }, - childData[index]); + id: rowID, + type: TransactionType.DELETE, + newValue: null, + path: path + }, + collection[index] + ); } else { - childData.splice(index, 1); + collection.splice(index, 1); } } } diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts index ab11e7f7167..2f722f5d4e3 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-hierarchical-transaction.ts @@ -36,7 +36,7 @@ export class IgxHierarchicalTransactionService { - if (v.path.indexOf(transaction.id) !== -1) { + if (v.path && v.path.indexOf(transaction.id) !== -1) { switch (v.type) { case TransactionType.ADD: states.delete(k); @@ -50,7 +50,7 @@ export class IgxHierarchicalTransactionService Date: Fri, 7 Dec 2018 09:29:17 +0200 Subject: [PATCH 106/114] chore(igxTreeGrid): fix after merge errors, #2921 --- projects/igniteui-angular/src/lib/grids/grid-base.component.ts | 2 +- .../src/lib/grids/tree-grid/tree-grid.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index 480a2e4370a..29432f265bd 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -2899,7 +2899,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } /** @hidden */ - protected deleteRowById(rowId: any) { + public deleteRowById(rowId: any) { let index: number; const data = this.gridAPI.get_all_data(this.id); if (this.primaryKey) { diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 54157f57297..e12683fd4d6 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -398,7 +398,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { } /** @hidden */ - protected deleteRowById(rowId: any) { + public deleteRowById(rowId: any) { // if this is flat self-referencing data, and CascadeOnDelete is set to true // and if we have transactions we should start pending transaction. This allows // us in case of delete action to delete all child rows as single undo action From ae0683bbcdfe066e2f46ab24edeb78f9a7bb91c3 Mon Sep 17 00:00:00 2001 From: wnvko Date: Fri, 7 Dec 2018 10:18:18 +0200 Subject: [PATCH 107/114] refactor(igxTreeGrid): switch array.map to for-of, #2921 --- .../services/transaction/igx-transaction.ts | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..c8f6e433956 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -86,8 +86,7 @@ export class IgxTransactionService exten this._isPending = false; if (commit) { const actions: { transaction: T, recordRef: any }[] = []; - for (let i = 0; i < this._pendingTransactions.length; i++) { - const transaction: T = this._pendingTransactions[i]; + for (const transaction of this._pendingTransactions) { const pendingState = this._pendingStates.get(transaction.id); this._transactions.push(transaction); this.updateState(this._states, transaction, pendingState.recordRef); @@ -135,12 +134,17 @@ export class IgxTransactionService exten return; } - const actions: { transaction: T, recordRef: any }[] = this._undoStack.pop(); - this._transactions.splice(this._transactions.length - actions.length); - this._redoStack.push(actions); + const lastActions: { transaction: T, recordRef: any }[] = this._undoStack.pop(); + this._transactions.splice(this._transactions.length - lastActions.length); + this._redoStack.push(lastActions); this._states.clear(); - this._undoStack.map(a => a.map(t => this.updateState(this._states, t.transaction, t.recordRef))); + for (const currentActions of this._undoStack) { + for (const transaction of currentActions) { + this.updateState(this._states, transaction.transaction, transaction.recordRef); + } + } + this.onStateUpdate.emit(); } @@ -148,10 +152,11 @@ export class IgxTransactionService exten if (this._redoStack.length > 0) { let actions: { transaction: T, recordRef: any, useInUndo?: boolean }[]; actions = this._redoStack.pop(); - actions.map(a => { - this.updateState(this._states, a.transaction, a.recordRef); - this._transactions.push(a.transaction); - }); + for (const action of actions) { + this.updateState(this._states, action.transaction, action.recordRef); + this._transactions.push(action.transaction); + } + this._undoStack.push(actions); this.onStateUpdate.emit(); } From c33484b73d46d6bdd392d8eccc951cd817cdc8f1 Mon Sep 17 00:00:00 2001 From: NikolayAlipiev Date: Fri, 7 Dec 2018 10:18:44 +0200 Subject: [PATCH 108/114] docs(igx-grid): hide custom strategy #2908 --- projects/igniteui-angular/src/lib/grids/grid.common.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/projects/igniteui-angular/src/lib/grids/grid.common.ts b/projects/igniteui-angular/src/lib/grids/grid.common.ts index 6f65ad68b32..3c5ca200cd7 100644 --- a/projects/igniteui-angular/src/lib/grids/grid.common.ts +++ b/projects/igniteui-angular/src/lib/grids/grid.common.ts @@ -590,10 +590,16 @@ export class IgxDecimalPipeComponent extends DecimalPipe implements PipeTransfor } } +/** + * @hidden + */ export interface ContainerPositionSettings extends PositionSettings { container?: HTMLElement; } +/** + * @hidden + */ export class ContainerPositioningStrategy extends ConnectedPositioningStrategy { isTop = false; isTopInitialPosition = null; From 39fa4605087f1254f5ee8953211448f6bb3845b4 Mon Sep 17 00:00:00 2001 From: wnvko Date: Fri, 7 Dec 2018 10:18:18 +0200 Subject: [PATCH 109/114] refactor(igxTreeGrid): switch array.map and for loops with for-of, #2921 --- .../src/lib/data-operations/data-util.ts | 4 +-- .../services/transaction/igx-transaction.ts | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 57fbc432d37..05659c75cee 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -234,9 +234,7 @@ export class DataUtil { primaryKey?: any, deleteRows: boolean = false): any[] { - for (let i = 0; i < transactions.length; i++) { - const transaction = transactions[i]; - + for (const transaction of transactions) { if (transaction.path) { const parent = this.findParentFromPath(data, primaryKey, childDataKey, transaction.path); let collection: any[] = parent ? parent[childDataKey] : data; diff --git a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts index 6a8e3caeb95..c8f6e433956 100644 --- a/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts +++ b/projects/igniteui-angular/src/lib/services/transaction/igx-transaction.ts @@ -86,8 +86,7 @@ export class IgxTransactionService exten this._isPending = false; if (commit) { const actions: { transaction: T, recordRef: any }[] = []; - for (let i = 0; i < this._pendingTransactions.length; i++) { - const transaction: T = this._pendingTransactions[i]; + for (const transaction of this._pendingTransactions) { const pendingState = this._pendingStates.get(transaction.id); this._transactions.push(transaction); this.updateState(this._states, transaction, pendingState.recordRef); @@ -135,12 +134,17 @@ export class IgxTransactionService exten return; } - const actions: { transaction: T, recordRef: any }[] = this._undoStack.pop(); - this._transactions.splice(this._transactions.length - actions.length); - this._redoStack.push(actions); + const lastActions: { transaction: T, recordRef: any }[] = this._undoStack.pop(); + this._transactions.splice(this._transactions.length - lastActions.length); + this._redoStack.push(lastActions); this._states.clear(); - this._undoStack.map(a => a.map(t => this.updateState(this._states, t.transaction, t.recordRef))); + for (const currentActions of this._undoStack) { + for (const transaction of currentActions) { + this.updateState(this._states, transaction.transaction, transaction.recordRef); + } + } + this.onStateUpdate.emit(); } @@ -148,10 +152,11 @@ export class IgxTransactionService exten if (this._redoStack.length > 0) { let actions: { transaction: T, recordRef: any, useInUndo?: boolean }[]; actions = this._redoStack.pop(); - actions.map(a => { - this.updateState(this._states, a.transaction, a.recordRef); - this._transactions.push(a.transaction); - }); + for (const action of actions) { + this.updateState(this._states, action.transaction, action.recordRef); + this._transactions.push(action.transaction); + } + this._undoStack.push(actions); this.onStateUpdate.emit(); } From dea7e24d5cdcbc2663ab5f60f69fada32d9ffb19 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Fri, 7 Dec 2018 10:51:38 +0200 Subject: [PATCH 110/114] feat(mask): address review comments regarding tests #2337 --- .../directives/mask/mask.directive.spec.ts | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts index ad79f6efa66..245deeb6128 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts @@ -56,7 +56,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('@#$YUA123_'); })); @@ -69,7 +68,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('555 55'); @@ -83,7 +81,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('+359-884 19 08 54'); })); @@ -133,7 +130,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('input')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(123) 4567-890'); expect(comp.value).toEqual('1234567890'); @@ -143,7 +139,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('input')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(777) 7___-___'); expect(comp.value).toEqual('7777'); @@ -164,7 +159,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(___) 4569-_12'); @@ -177,7 +171,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(111) 1111-111'); })); @@ -210,9 +203,7 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(123) ____-___'); - expect(fixture.componentInstance.raw).toEqual('123'); })); @@ -227,7 +218,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('3456****'); expect(comp.value).toEqual(3456); @@ -258,8 +248,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(keyEvent); tick(); - fixture.detectChanges(); - input.nativeElement.value = ''; input.nativeElement.dispatchEvent(new Event('input')); tick(); @@ -267,10 +255,7 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); - expect(input.nativeElement.value).toEqual('(___) ____-___'); - })); it('Enter value over literal', fakeAsync(() => { @@ -289,8 +274,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(keyEvent); tick(); - fixture.detectChanges(); - input.nativeElement.value = ''; input.nativeElement.dispatchEvent(new Event('input')); tick(); @@ -298,8 +281,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); - expect(input.nativeElement.value).toEqual('(___) ____-___'); input.nativeElement.value = '6666'; @@ -308,7 +289,6 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(666) 6___-___'); })); @@ -325,12 +305,12 @@ describe('igxMask', () => { expect(input.nativeElement.value).toEqual('SSS'); input.nativeElement.dispatchEvent(new Event('blur')); - fixture.detectChanges(); + tick(); expect(input.nativeElement.value).toEqual('sss'); input.nativeElement.dispatchEvent(new Event('focus')); - fixture.detectChanges(); + tick(); expect(input.nativeElement.value).toEqual('SSS'); })); @@ -345,13 +325,13 @@ describe('igxMask', () => { expect(input.nativeElement.placeholder).toEqual('hello'); input.nativeElement.dispatchEvent(new Event('focus')); - fixture.detectChanges(); + tick(); expect(input.nativeElement.value).toEqual('(__) (__)'); expect(input.nativeElement.placeholder).toEqual('hello'); input.nativeElement.dispatchEvent(new Event('blur')); - fixture.detectChanges(); + tick(); expect(input.nativeElement.value).toEqual(''); expect(input.nativeElement.placeholder).toEqual('hello'); From 48bea40a24ee4c31f627088b0ada77592b2d81b4 Mon Sep 17 00:00:00 2001 From: HristoP96 Date: Fri, 7 Dec 2018 12:10:11 +0200 Subject: [PATCH 111/114] docs(readme): Change to Crupto app description. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2680e160dd..217640b95f5 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ You can include Ignite UI for Angular in your project as a dependency using the ## Demo Apps & Documentation The [Warehouse Picklist App](https://github.com/IgniteUI/warehouse-js-blocks) demonstrates using several Ignite UI for Angular widgets together to build a modern, mobile app. -The [Crypto Portfolio App](https://igniteui.github.io/crypto-portfolio-app/#/home) is a delicate web and mobile app, developed with Ignite UI for Angular most solid components and styled with one of a kind theming engine. +The [Crypto Portfolio App](https://igniteui.github.io/crypto-portfolio-app/#/home) is web and mobile application, developed with Ignite UI for Angular most solid components and styled with one of a kind theming engine. To get started with the Data Grid, use the steps in the [grid walk-through](https://www.infragistics.com/angular-samples/components/grid.html). From 41f6b0e293dbe92da15f65c34db90cbdfed35b2f Mon Sep 17 00:00:00 2001 From: Zdravko Kolev Date: Fri, 7 Dec 2018 15:09:55 +0200 Subject: [PATCH 112/114] Update Changelog with 7.0.3 section --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d3d2323eb..038d6251a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,33 @@ All notable changes for each version of this project will be documented in this - You can now export the tree grid both to CSV and Excel. - The hierarchy and the records' expanded states would be reflected in the exported Excel worksheet. +## 7.0.3 +### Bug fixes + +- ng add igniteui-angular adds igniteui-cli package to both dependencies and devDependencies ([#3254](https://github.com/IgniteUI/igniteui-angular/issues/3254)) +- Group column header is not styled correctly when moving that column ([#3072](https://github.com/IgniteUI/igniteui-angular/issues/3072)) +- igx-grid: Filter row remains after disabling filtering feature ([#3255](https://github.com/IgniteUI/igniteui-angular/issues/3255)) +- [igxGrid] Keyboard navigation between cells and filtering row with MCH ([#3179](https://github.com/IgniteUI/igniteui-angular/issues/3179)) +- Argument $color of red($color) must be a color ([#3190](https://github.com/IgniteUI/igniteui-angular/issues/3190)) +- Shell strings localization ([#3237](https://github.com/IgniteUI/igniteui-angular/issues/3237)) +- Tabbing out of the combo search input not possible ([#3200](https://github.com/IgniteUI/igniteui-angular/issues/3200)) +- Localization (i18n) not available for inputs/buttons on the grid filtering dialog ([#2517](https://github.com/IgniteUI/igniteui-angular/issues/2517)) +- When in the tree grid are pinned columns and scroll horizontal the cells text is over the pinned text #3163 +- Request for update of shell strings in Japanese ([#3163](https://github.com/IgniteUI/igniteui-angular/issues/3163)) +- Refactor(themes): remove get-function calls ([#3327](https://github.com/IgniteUI/igniteui-angular/issues/3327)) +- Fix(grid): recalculate grid body size when changing allowFiltering dynamically ([#3321](https://github.com/IgniteUI/igniteui-angular/issues/3321)) +- Fix - Combo - Hide Search input when !filterable && !allowCustomValues - 7.0.x ([#3314](https://github.com/IgniteUI/igniteui-angular/issues/3314)) +- Fixing column chooser column updating - 7.0.x ([#3235](https://github.com/IgniteUI/igniteui-angular/issues/3235)) +- Disable combo checkbox animations on scroll ([#3303](https://github.com/IgniteUI/igniteui-angular/issues/3303)) +- Added validation if last column collides with grid's scroll. ([#3028](https://github.com/IgniteUI/igniteui-angular/issues/3028)) ([#3100](https://github.com/IgniteUI/igniteui-angular/issues/3100)) +- Use value instead of ngModel to update editValue for checkbox and calendar in igxCell ([#3225](https://github.com/IgniteUI/igniteui-angular/issues/3225)) +- Add @inheritdoc, create ScrollStrategy abstract class and fix method signatures 7.0.x ([#3222](https://github.com/IgniteUI/igniteui-angular/issues/3222)) +- When scroll with the mouse wheel the value in datePicker editor for edited cell is empty ([#2958](https://github.com/IgniteUI/igniteui-angular/issues/2958)) +- igxToolbar should have the option to add custom template ([#2983](https://github.com/IgniteUI/igniteui-angular/issues/2983)) +- fix(grid): mark grid for check inside NgZone when resizing ([#2792](https://github.com/IgniteUI/igniteui-angular/issues/2792)) ([#3277](https://github.com/IgniteUI/igniteui-angular/issues/3277)) +- IgxGridHeaderGroupComponent should have preset min width ([#3071](https://github.com/IgniteUI/igniteui-angular/issues/3071)) +- Tree grid selection ([#3334](https://github.com/IgniteUI/igniteui-angular/issues/3334)) + ## 7.0.2 ### Features - `ng add igniteui-angular` support :tada: From 60ab88d5793920d683a81fb5e92511c496c9bb85 Mon Sep 17 00:00:00 2001 From: wnvko Date: Fri, 7 Dec 2018 16:20:57 +0200 Subject: [PATCH 113/114] chore(igxTreeGrid): switch back from ChromeHeadless to Chrome, #2921 --- projects/igniteui-angular/karma.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/karma.conf.js b/projects/igniteui-angular/karma.conf.js index 0229458838b..8e02fa3d4a6 100644 --- a/projects/igniteui-angular/karma.conf.js +++ b/projects/igniteui-angular/karma.conf.js @@ -38,7 +38,7 @@ module.exports = function (config) { colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['ChromeHeadless'], + browsers: ['Chrome'], singleRun: false }); }; From 7699191c5869f3c7c7d6968588893210a29da159 Mon Sep 17 00:00:00 2001 From: Desislava Dincheva <34240583+ddincheva@users.noreply.github.com> Date: Fri, 7 Dec 2018 18:22:58 +0200 Subject: [PATCH 114/114] Tree Grid and GroupBy Summaries (#3333) * feat(GridSummaries): added tree grid and groupby summaries #3076 * refactor(themes): update grid summaries theme after refactor * feat(GridSummaries): trigger CD when change summaryCalc mode runtime #3076 * refactor(GridSummaries): retrigger rootSummariesPipe when enable/disable summary * feat(GridSummaries): escape edit mode when change calculationMode #3076 * feat(summaries): fixing samples build issue #3076 * test(treeGrid): Add summaries tests #3162 * test(grid): add groupBy with summaries test #3162 * test(treeGrid): Update summaries tests #3162 * chore(*): remove trailing whitespace * feat(GridSummaries): add keyboard navigation for grid summaries #3076 * feat(summaries): fix summary cell width for % #3076 * test(grid): Add groupBy and Summary tests #3162 * feat(summaries): fix tree grid root summary indentation #3076 * test(grid): up[date for lint #3162 * refactor(summaryCell): removed gridAPI service from summary cell * test(grid): add missing ds #3162 * refactor(summaryCell): return when we are at last root summary * refactor(IgxGridNavigationService): remove unnecessary code * test(grid): Update failing test #3162 * test(treeGrid): Update failing tests #3162 * test(grid): Exclude failing search tests #3162 * refactor(GridSummary): appy check if grid data is null or undefined --- .../grid-summary/_grid-summary-theme.scss | 9 - .../components/grid/_grid-component.scss | 9 + .../styles/components/grid/_grid-theme.scss | 51 +- .../styles/themes/schemas/dark/_grid.scss | 28 +- .../styles/themes/schemas/light/_grid.scss | 26 + .../template_outlet.directive.ts | 7 +- .../src/lib/grids/api.service.ts | 72 +- .../src/lib/grids/column.component.ts | 48 +- .../src/lib/grids/grid-base.component.ts | 259 ++- .../src/lib/grids/grid-common.module.ts | 16 +- .../src/lib/grids/grid-navigation.service.ts | 106 +- .../src/lib/grids/grid-summary.component.html | 10 - .../src/lib/grids/grid-summary.component.ts | 107 -- .../src/lib/grids/grid/column-group.spec.ts | 30 - .../src/lib/grids/grid/grid-api.service.ts | 17 + .../src/lib/grids/grid/grid-filtering.spec.ts | 7 +- .../src/lib/grids/grid/grid-summary.spec.ts | 1504 +++++++++-------- .../src/lib/grids/grid/grid.component.html | 32 +- .../src/lib/grids/grid/grid.component.spec.ts | 2 +- .../src/lib/grids/grid/grid.component.ts | 13 +- .../src/lib/grids/grid/grid.groupby.spec.ts | 29 - .../src/lib/grids/grid/grid.module.ts | 4 +- .../src/lib/grids/grid/grid.search.spec.ts | 8 +- .../src/lib/grids/grid/grid.summary.pipe.ts | 103 ++ .../igniteui-angular/src/lib/grids/index.ts | 2 +- .../grids/summaries/grid-root-summary.pipe.ts | 20 + .../grids/summaries/grid-summary.service.ts | 188 +++ .../lib/grids/{ => summaries}/grid-summary.ts | 7 +- .../summaries/summary-cell.component.html | 19 + .../grids/summaries/summary-cell.component.ts | 174 ++ .../summaries/summary-row.component.html | 18 + .../grids/summaries/summary-row.component.ts | 111 ++ .../grids/tree-grid/tree-grid-api.service.ts | 7 + .../tree-grid/tree-grid-summaries.spec.ts | 740 ++++++++ .../grids/tree-grid/tree-grid.component.html | 27 +- .../grids/tree-grid/tree-grid.component.ts | 27 +- .../lib/grids/tree-grid/tree-grid.module.ts | 4 +- .../lib/grids/tree-grid/tree-grid.pipes.ts | 12 +- .../grids/tree-grid/tree-grid.summary.pipe.ts | 79 + .../src/lib/test-utils/grid-samples.spec.ts | 86 +- .../src/lib/test-utils/helper-utils.spec.ts | 125 +- .../lib/test-utils/sample-test-data.spec.ts | 263 ++- .../lib/test-utils/template-strings.spec.ts | 9 + .../test-utils/tree-grid-components.spec.ts | 97 +- .../grid-cellEditing.component.html | 5 +- .../grid-cellEditing.component.ts | 8 + .../grid-column-moving.sample.html | 4 +- .../grid-column-moving.sample.ts | 2 +- src/app/grid-groupby/grid-groupby.sample.html | 7 +- src/app/grid-groupby/grid-groupby.sample.ts | 106 +- .../grid-summaries/grid-summaries.sample.ts | 4 +- .../tree-grid-flat-data.sample.html | 10 +- .../tree-grid-flat-data.sample.ts | 107 +- src/app/tree-grid/tree-grid.sample.html | 2 +- 54 files changed, 3432 insertions(+), 1335 deletions(-) delete mode 100644 projects/igniteui-angular/src/lib/grids/grid-summary.component.html delete mode 100644 projects/igniteui-angular/src/lib/grids/grid-summary.component.ts create mode 100644 projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts create mode 100644 projects/igniteui-angular/src/lib/grids/summaries/grid-root-summary.pipe.ts create mode 100644 projects/igniteui-angular/src/lib/grids/summaries/grid-summary.service.ts rename projects/igniteui-angular/src/lib/grids/{ => summaries}/grid-summary.ts (97%) create mode 100644 projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html create mode 100644 projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts create mode 100644 projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.html create mode 100644 projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.ts create mode 100644 projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts create mode 100644 projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss index 3b77824b955..92d64117bc7 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid-summary/_grid-summary-theme.scss @@ -104,14 +104,9 @@ display: flex; flex-direction: column; flex: 1 1 0%; - border-left: 1px solid --var($theme, 'border-color'); padding: map-get($summary-padding, 'comfortable'); background: --var($theme, 'background-color'); overflow: hidden; - - &:first-of-type { - border-left: 0; - } } %igx-grid-summary--cosy { @@ -135,10 +130,6 @@ border-right: map-get($cell-pin, 'style') map-get($cell-pin, 'color'); } - %igx-grid-summary--empty { - border-left: 1px solid --var($theme, 'border-color'); - } - %igx-grid-summary__item { display: flex; align-items: center; diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss index 04a78dc229b..38b3ed23736 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss @@ -86,6 +86,15 @@ } } + @include e(summaries, $m: 'body') { + @extend %grid-summaries !optional; + @extend %grid-summaries--body !optional; + + igx-display-container { + @extend %grid-display-container-tr !optional; + } + } + @include e(summaries-patch) { @extend %grid-summaries-patch !optional; } diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss index 34618b2ca40..3650444f161 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss @@ -61,6 +61,11 @@ /// @param {Color} $filtering-row-background [null] - The background color of the filtering row. /// @param {Color} $filtering-row-text-color [null] - The text-color color of the filtering row. /// +/// @param {Color} $body-summaries-background [null] - The background color of the summary groups located the body. +/// @param {Color} $body-summaries-text-color [null] - The text color of the summary results located the body. +/// @param {Color} $root-summaries-background [null] - The background color of the summary groups located the footer. +/// @param {Color} $root-summaries-text-color [null] - The text color of the summary results located the footer. +/// /// @requires $default-palette /// @requires $light-schema /// @requires apply-palette @@ -139,7 +144,12 @@ $filtering-header-text-color: null, $filtering-row-background: null, $filtering-row-text-color: null, - $tree-filtered-text-color: null + $tree-filtered-text-color: null, + + $body-summaries-background: null, + $body-summaries-text-color: null, + $root-summaries-background: null, + $root-summaries-text-color: null ) { $name: 'igx-grid'; $theme: apply-palette(map-get($schema, $name), $palette); @@ -283,6 +293,14 @@ $filtering-row-text-color: text-contrast(hexrgba($filtering-row-background)); } + @if not($body-summaries-text-color) and $body-summaries-background { + $body-summaries-text-color: text-contrast($body-summaries-background); + } + + @if not($root-summaries-text-color) and $root-summaries-background { + $root-summaries-text-color: text-contrast($root-summaries-background); + } + @return extend($theme, ( name: $name, palette: $palette, @@ -358,7 +376,12 @@ tree-filtered-text-color: $tree-filtered-text-color, tree-selected-filtered-row-text-color: $tree-selected-filtered-row-text-color, - tree-selected-filtered-cell-text-color: $tree-selected-filtered-cell-text-color + tree-selected-filtered-cell-text-color: $tree-selected-filtered-cell-text-color, + + body-summaries-background: $body-summaries-background, + body-summaries-text-color: $body-summaries-text-color, + root-summaries-background: $root-summaries-background, + root-summaries-text-color: $root-summaries-text-color )); } @@ -614,7 +637,7 @@ } %grid-scroll-start { - background: igx-color($palette, 'grays', 200); + background: --var($theme, 'header-background'); } %grid-scroll-main { @@ -1023,13 +1046,33 @@ %grid-summaries { display: flex; - background: inherit; + background: --var($theme, 'root-summaries-background'); + + // %igx-grid-summary__label, + %igx-grid-summary__result { + color: --var($theme, 'root-summaries-text-color'); + } + } + + %grid-summaries--body { + background: --var($theme, 'body-summaries-background'); + border-bottom: 1px dashed --var($theme, 'row-border-color'); + + &:last-of-type { + border-bottom: none; + } + + // %igx-grid-summary__label, + %igx-grid-summary__result { + color: --var($theme, 'body-summaries-text-color'); + } } %grid-summaries-patch { background: inherit; position: relative; z-index: 1; + border-right: 1px solid --var($theme, 'header-border-color'); } // Column moving diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_grid.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_grid.scss index 8f09cb6ad82..885aab7aa8b 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_grid.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_grid.scss @@ -22,6 +22,10 @@ /// @prop {Map} group-row-selected-background [igx-color: ('grays', 200), hexrgba: #222] - The drop area background on drop color. /// @prop {Color} filtering-header-background [#222] - The background color of the filtered column header. /// @prop {Color} filtering-row-background [#222] - The background color of the filtering row. +/// @prop {Map} body-summaries-background [igx-color: ('grays', 300), hexrgba: #222] - The background color of the summary groups located the body. +/// @prop {Map} body-summaries-text-color [igx-color: ('grays', 300), hexrgba: #222, text-contrast: ()] - The text color of the summary groups located the body. +/// @prop {Map} root-summaries-background [igx-color: ('grays', 100), hexrgba: #222] - The background color of the summary groups located the footer. +/// @prop {Map} root-summaries-text-color [igx-color: ('grays', 100), hexrgba: #222, text-contrast: ()] - The text color of the summary groups located the footer. /// @requires extend /// @requires $_light-grid /// @see $default-palette @@ -75,5 +79,27 @@ $_dark-grid: extend($_light-grid, ( filtering-row-background: #222, - cell-selected-text-color: #fff + cell-selected-text-color: #fff, + + body-summaries-background: ( + igx-color: ('grays', 100), + hexrgba: #222 + ), + + body-summaries-text-color: ( + igx-color: ('grays', 100), + hexrgba: #222, + text-contrast: () + ), + + root-summaries-background: ( + igx-color: ('grays', 300), + hexrgba: #222 + ), + + root-summaries-text-color: ( + igx-color: ('grays', 300), + hexrgba: #222, + text-contrast: () + ) )); diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_grid.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_grid.scss index 507796d315b..bd82267be24 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_grid.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_grid.scss @@ -54,6 +54,10 @@ /// @prop {Map} filtering-header-text-color [igx-color: ('grays', 800)] - The text color color of the filtered column header. /// @prop {Color} filtering-row-background [#fff] - The background color of the filtering row. /// @prop {Map} filtering-row-text-color [igx-color: ('grays', 800)] - The text-color color of the filtering row. +/// @prop {Map} body-summaries-background [igx-color: ('grays', 300), hexrgba: #fff] - The background color of the summary groups located the body. +/// @prop {Map} body-summaries-text-color [igx-color: ('grays', 300), hexrgba: #fff, text-contrast: ()] - The text color of the summary groups located the body. +/// @prop {Map} root-summaries-background [igx-color: ('grays', 100), hexrgba: #fff] - The background color of the summary groups located the footer. +/// @prop {Map} root-summaries-text-color [igx--color: ('grays', 100), hexrgba: #fff, text-contrast: ()] - The text color of the summary groups located the footer. /// @see $default-palette $_light-grid: ( header-background: ( @@ -239,5 +243,27 @@ $_light-grid: ( tree-filtered-text-color: ( igx-color: ('grays', 500) + ), + + body-summaries-background: ( + igx-color: ('grays', 100), + hexrgba: #fff + ), + + body-summaries-text-color: ( + igx-color: ('grays', 100), + hexrgba: #fff, + text-contrast: () + ), + + root-summaries-background: ( + igx-color: ('grays', 300), + hexrgba: #fff + ), + + root-summaries-text-color: ( + igx-color: ('grays', 300), + hexrgba: #fff, + text-contrast: () ) ); diff --git a/projects/igniteui-angular/src/lib/directives/template-outlet/template_outlet.directive.ts b/projects/igniteui-angular/src/lib/directives/template-outlet/template_outlet.directive.ts index a1f4b4b6635..0384de3dfb4 100644 --- a/projects/igniteui-angular/src/lib/directives/template-outlet/template_outlet.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/template-outlet/template_outlet.directive.ts @@ -58,7 +58,7 @@ export class IgxTemplateOutletDirective implements OnChanges { private _recreateView() { // remove and recreate if (this._viewRef) { - this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef)); + this._viewContainerRef.detach(this._viewContainerRef.indexOf(this._viewRef)); } if (this.igxTemplateOutlet) { this._viewRef = this._viewContainerRef.createEmbeddedView( @@ -70,10 +70,7 @@ export class IgxTemplateOutletDirective implements OnChanges { // Note: Views in detached state do not appear in the DOM, however they remain stored in memory. const res = this._embeddedViewsMap.get(this.igxTemplateOutletContext['templateID']); if (!res) { - let emptyView = this._viewContainerRef.createEmbeddedView( - this.igxTemplateOutlet, {}); - emptyView = this._viewContainerRef.detach(this._viewContainerRef.indexOf(emptyView)) as EmbeddedViewRef; - this._embeddedViewsMap.set(this.igxTemplateOutletContext['templateID'], emptyView); + this._embeddedViewsMap.set(this.igxTemplateOutletContext['templateID'], this._viewRef); } } } diff --git a/projects/igniteui-angular/src/lib/grids/api.service.ts b/projects/igniteui-angular/src/lib/grids/api.service.ts index 610e9bf08c8..19da0f5a345 100644 --- a/projects/igniteui-angular/src/lib/grids/api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/api.service.ts @@ -11,7 +11,6 @@ import { IgxRowComponent } from './row.component'; import { IFilteringOperation } from '../data-operations/filtering-condition'; import { IFilteringExpressionsTree, FilteringExpressionsTree } from '../data-operations/filtering-expressions-tree'; import { Transaction, TransactionType } from '../services/index'; -import { ISortingStrategy } from '../data-operations/sorting-strategy'; /** *@hidden */ @@ -22,7 +21,6 @@ export class GridBaseAPIService { protected state: Map = new Map(); protected editCellState: Map = new Map(); protected editRowState: Map = new Map(); - protected summaryCacheMap: Map> = new Map>(); protected destroyMap: Map> = new Map>(); public register(grid: T) { @@ -40,7 +38,6 @@ export class GridBaseAPIService { public unset(id: string) { this.state.delete(id); - this.summaryCacheMap.delete(id); this.editCellState.delete(id); this.editRowState.delete(id); this.destroyMap.delete(id); @@ -48,7 +45,6 @@ export class GridBaseAPIService { public reset(oldId: string, newId: string) { const destroy = this.destroyMap.get(oldId); - const summary = this.summaryCacheMap.get(oldId); const editCellState = this.editCellState.get(oldId); const editRowState = this.editRowState.get(oldId); const grid = this.get(oldId); @@ -63,10 +59,6 @@ export class GridBaseAPIService { this.destroyMap.set(newId, destroy); } - if (summary) { - this.summaryCacheMap.set(newId, summary); - } - if (editCellState) { this.editCellState.set(newId, editCellState); } @@ -80,11 +72,7 @@ export class GridBaseAPIService { return this.get(id).columnList.find((col) => col.field === name); } - public set_summary_by_column_name(id: string, name: string) { - if (!this.summaryCacheMap.get(id)) { - this.summaryCacheMap.set(id, new Map()); - } - const column = this.get_column_by_name(id, name); + public get_summary_data(id) { const grid = this.get(id); let data = grid.filteredData; if (!data) { @@ -94,28 +82,19 @@ export class GridBaseAPIService { grid.transactions.getAggregatedChanges(true), grid.primaryKey ); + const deletedRows = grid.transactions.getTransactionLog().filter(t => t.type === TransactionType.DELETE).map(t => t.id); + deletedRows.forEach(rowID => { + const tempData = grid.primaryKey ? data.map(rec => rec[grid.primaryKey]) : data; + const index = tempData.indexOf(rowID); + if (index !== -1) { + data.splice(index, 1); + } + }); } else { data = grid.data; } } - if (data) { - const columnValues = data.map((rec) => rec[column.field]); - this.calculateSummaries(id, column, columnValues); - } - } - - public get_summaries(id: string) { - return this.summaryCacheMap.get(id); - } - - public remove_summary(id: string, name?: string) { - if (this.summaryCacheMap.has(id)) { - if (!name) { - this.summaryCacheMap.delete(id); - } else { - this.summaryCacheMap.get(id).delete(name); - } - } + return data; } public set_cell_inEditMode(gridId: string, cell: IgxGridCellComponent) { @@ -359,10 +338,16 @@ export class GridBaseAPIService { && isEqual(emittedArgs.oldValue, emittedArgs.newValue)) { return; } const rowValue = this.get_all_data(id, grid.transactions.enabled)[rowIndex]; this.updateData(grid, rowID, rowValue, currentGridEditState.rowData, { [column.field]: emittedArgs.newValue }); - if (grid.primaryKey === column.field && currentGridEditState.isRowSelected) { - grid.selection.deselect_item(id, rowID); - grid.selection.select_item(id, emittedArgs.newValue); + if (grid.primaryKey === column.field) { + if (currentGridEditState.isRowSelected) { + grid.selection.deselect_item(id, rowID); + grid.selection.select_item(id, emittedArgs.newValue); + } + if (grid.hasSummarizedColumns) { + grid.summaryService.removeSummaries(rowID); + } } + grid.summaryService.clearSummaryCache(emittedArgs); if (!grid.rowEditable || !grid.rowInEditMode || grid.rowInEditMode.rowID !== rowID || !grid.transactions.enabled) { (grid as any)._pipeTrigger++; } @@ -426,6 +411,9 @@ export class GridBaseAPIService { const newRowID = (grid.primaryKey) ? emitArgs.newValue[grid.primaryKey] : emitArgs.newValue; grid.selection.select_item(id, newRowID); } + if (grid.hasSummarizedColumns) { + grid.summaryService.removeSummaries(rowID); + } (grid as any)._pipeTrigger++; } } @@ -484,8 +472,6 @@ export class GridBaseAPIService { } filteringTree.filteringOperands = []; - this.remove_summary(id); - if (condition) { for (const column of grid.columns) { this.prepare_filtering_expression(filteringTree, column.field, term, @@ -510,23 +496,14 @@ export class GridBaseAPIService { if (index > -1) { filteringState.filteringOperands.splice(index, 1); - this.remove_summary(id, fieldName); } else { filteringState.filteringOperands = []; - this.remove_summary(id); } grid.filteredData = null; grid.filteringExpressionsTree = filteringState; } - protected calculateSummaries(id: string, column, data) { - if (!this.summaryCacheMap.get(id).get(column.field)) { - this.summaryCacheMap.get(id).set(column.field, - column.summaries.operate(data)); - } - } - public clear_sort(id, fieldName) { const sortingState = this.get(id).sortingExpressions; const index = sortingState.findIndex((expr) => expr.fieldName === fieldName); @@ -614,4 +591,9 @@ export class GridBaseAPIService { return this.get_column_by_name(this.get(id).id, fieldName) ? this.get_column_by_name(id, fieldName).sortStrategy : undefined; } + + public get_row_id(id: string, rowData) { + const grid = this.get(id); + return grid.primaryKey ? rowData[grid.primaryKey] : rowData; + } } diff --git a/projects/igniteui-angular/src/lib/grids/column.component.ts b/projects/igniteui-angular/src/lib/grids/column.component.ts index 245dd28a7a8..4fd20253d85 100644 --- a/projects/igniteui-angular/src/lib/grids/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/column.component.ts @@ -14,7 +14,7 @@ import { DataType } from '../data-operations/data-util'; import { IgxTextHighlightDirective } from '../directives/text-highlight/text-highlight.directive'; import { GridBaseAPIService } from './api.service'; import { IgxGridCellComponent } from './cell.component'; -import { IgxDateSummaryOperand, IgxNumberSummaryOperand, IgxSummaryOperand } from './grid-summary'; +import { IgxDateSummaryOperand, IgxNumberSummaryOperand, IgxSummaryOperand } from './summaries/grid-summary'; import { IgxRowComponent } from './row.component'; import { IgxCellEditorTemplateDirective, @@ -139,18 +139,31 @@ export class IgxColumnComponent implements AfterContentInit { @Input() public resizable = false; /** - * Enables/disables summary for the column. - * Default value is `false`. + * Gets a value indicating whether the summary for the column is enabled. * ```typescript * let hasSummary = this.column.hasSummary; * ``` + * @memberof IgxColumnComponent + */ + @Input() + get hasSummary() { + return this._hasSummary; + } + /** + * Sets a value indicating whether the summary for the column is enabled. + * Default value is `false`. * ```html * * ``` * @memberof IgxColumnComponent */ - @Input() - public hasSummary = false; + set hasSummary(value) { + this._hasSummary = value; + + if (this.grid) { + this.grid.recalculateSummaries(); + } + } /** * Gets whether the column is hidden. * ```typescript @@ -198,10 +211,7 @@ export class IgxColumnComponent implements AfterContentInit { this.grid.refreshSearch(); } } - if (this.hasSummary) { - this.grid.summariesHeight = 0; - } - + this.grid.summaryService.resetSummaryHeight(); this.grid.reflow(); this.grid.filteringService.refreshExpressions(); } @@ -453,6 +463,12 @@ export class IgxColumnComponent implements AfterContentInit { */ public set summaries(classRef: any) { this._summaries = new classRef(); + + if (this.grid) { + this.grid.summaryService.removeSummariesCachePerColumn(this.field); + (this.grid as any)._summaryPipeTrigger++; + this.grid.recalculateSummaries(); + } } /** * Sets/gets whether the column is `searchable`. @@ -724,6 +740,16 @@ export class IgxColumnComponent implements AfterContentInit { return lvl; } + get isLastPinned(): boolean { + const pinnedCols = this.grid.pinnedColumns; + + if (pinnedCols.length === 0) { + return false; + } + + return pinnedCols.indexOf(this) === pinnedCols.length - 1; + } + /** * hidden */ @@ -822,6 +848,10 @@ export class IgxColumnComponent implements AfterContentInit { *@hidden */ protected _defaultMinWidth = '64'; + /** + *@hidden + */ + protected _hasSummary = false; /** *@hidden */ diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index 42be5b53361..246390235fa 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -41,7 +41,7 @@ import { GridBaseAPIService } from './api.service'; import { IgxGridCellComponent } from './cell.component'; import { IColumnVisibilityChangedEventArgs } from './column-hiding-item.directive'; import { IgxColumnComponent } from './column.component'; -import { ISummaryExpression } from './grid-summary'; +import { ISummaryExpression } from './summaries/grid-summary'; import { DropPosition, ContainerPositioningStrategy } from './grid.common'; import { IgxGridToolbarComponent } from './grid-toolbar.component'; import { IgxRowComponent } from './row.component'; @@ -65,6 +65,8 @@ import { IgxGridHeaderGroupComponent } from './grid-header-group.component'; import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; import { IGridResourceStrings } from '../core/i18n/grid-resources'; import { CurrentResourceStrings } from '../core/i18n/resources'; +import { IgxGridSummaryService } from './summaries/grid-summary.service'; +import { IgxSummaryRowComponent } from './summaries/summary-row.component'; const MINIMUM_COLUMN_WIDTH = 136; const FILTER_ROW_HEIGHT = 50; @@ -150,6 +152,17 @@ export interface IFocusChangeEventArgs { cancel: boolean; } +export enum GridSummaryPosition { + top = 'top', + bottom = 'bottom' +} + +export enum GridSummaryCalculationMode { + rootLevelOnly = 'rootLevelOnly', + childLevelsOnly = 'childLevelsOnly', + rootAndChildLevels = 'rootAndChildLevels' +} + export abstract class IgxGridBaseComponent extends DisplayDensityBase implements OnInit, OnDestroy, AfterContentInit, AfterViewInit { /** @@ -174,12 +187,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this._resourceStrings = Object.assign({}, this._resourceStrings, value); } - /** - * An accessor that returns the resource strings. - */ - get resourceStrings(): IGridResourceStrings { - return this._resourceStrings; - } + /** + * An accessor that returns the resource strings. + */ + get resourceStrings(): IGridResourceStrings { + return this._resourceStrings; + } /** * An @Input property that autogenerates the `IgxGridComponent` columns. @@ -489,7 +502,9 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements */ set rowEditable(val: boolean) { this._rowEditable = val; - this.refreshGridState(); + if (this.gridAPI.get(this.id)) { + this.refreshGridState(); + } } /** @@ -729,7 +744,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * ```typescript * let filtering = this.grid.allowFiltering; * ``` - * @memberof IgxGridComponent + * @memberof IgxGridBaseComponent */ @Input() get allowFiltering() { @@ -742,7 +757,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * ```html * * ``` - * @memberof IgxGridComponent + * @memberof IgxGridBaseComponent */ set allowFiltering(value) { if (this._allowFiltering !== value) { @@ -766,6 +781,63 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } } + /** + * Returns the summary position. + * ```typescript + * let summaryPosition = this.grid.summaryPosition; + * ``` + * @memberof IgxGridBaseComponent + */ + @Input() + get summaryPosition() { + return this._summaryPosition; + } + + /** + * Sets summary position. + * By default it is bottom. + * ```html + * + * ``` + * @memberof IgxGridBaseComponent + */ + set summaryPosition(value) { + this._summaryPosition = value; + if (this.gridAPI.get(this.id)) { + this.markForCheck(); + } + } + + /** + * Returns the summary calculation mode. + * ```typescript + * let summaryCalculationMode = this.grid.summaryCalculationMode; + * ``` + * @memberof IgxGridBaseComponent + */ + @Input() + get summaryCalculationMode() { + return this._summaryCalculationMode; + } + + /** + * Sets summary calculation mode. + * By default it is rootAndChildLevels which means the summaries are calculated for the root level and each child level. + * ```html + * + * ``` + * @memberof IgxGridBaseComponent + */ + set summaryCalculationMode(value) { + this._summaryCalculationMode = value; + if (this.gridAPI.get(this.id)) { + this.summaryService.summaryHeight = 0; + this.endEdit(true); + this.calculateGridHeight(); + this.cdr.markForCheck(); + } + } + /** * Emitted when `IgxGridCellComponent` is clicked. Returns the `IgxGridCellComponent`. * ```html @@ -1246,6 +1318,22 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements @ViewChildren('row') private _rowList: QueryList; + @ViewChildren('summaryRow', { read: IgxSummaryRowComponent }) + protected _summaryRowList: QueryList; + + + public get summariesRowList() { + const res = new QueryList(); + if (!this._summaryRowList) { + return res; + } + const sumList = this._summaryRowList.filter((item) => { + return item.element.nativeElement.parentElement !== null; + }); + res.reset(sumList); + return res; + } + /** * A list of `IgxGridRowComponent`. * ```typescript @@ -1329,9 +1417,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements @ViewChild('verticalScrollContainer', { read: IgxGridForOfDirective }) public verticalScrollContainer: IgxGridForOfDirective; - @ViewChild('summaryContainer', { read: IgxGridForOfDirective }) - protected summaryContainer: IgxGridForOfDirective; - /** * @hidden */ @@ -1386,11 +1471,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements @ViewChild('tfoot') public tfoot: ElementRef; - /** - * @hidden - */ - @ViewChild('summaries') - public summaries: ElementRef; /** * @hidden @@ -1523,6 +1603,13 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements return this._pipeTrigger; } + /** + * @hidden + */ + get summaryPipeTrigger(): number { + return this._summaryPipeTrigger; + } + /** * Returns the sorting state of the `IgxGridComponent`. * ```typescript @@ -2015,6 +2102,10 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * @hidden */ protected _pipeTrigger = 0; + /** + * @hidden + */ + protected _summaryPipeTrigger = 0; /** * @hidden */ @@ -2080,6 +2171,9 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements private _defaultTargetRecordNumber = 10; + private _summaryPosition = GridSummaryPosition.bottom; + private _summaryCalculationMode = GridSummaryCalculationMode.rootAndChildLevels; + private rowEditPositioningStrategy = new ContainerPositioningStrategy({ horizontalDirection: HorizontalAlignment.Left, verticalDirection: VerticalAlignment.Bottom, @@ -2114,9 +2208,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.headerContainer.onHScroll(scrollLeft); this._horizontalForOfs.forEach(vfor => vfor.onHScroll(scrollLeft)); - if (this.summaryContainer) { - this.summaryContainer.onHScroll(scrollLeft); - } this.zone.run(() => { this.cdr.detectChanges(); this.parentVirtDir.onChunkLoad.emit(this.headerContainer.state); @@ -2150,6 +2241,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements private viewRef: ViewContainerRef, private navigation: IgxGridNavigationService, public filteringService: IgxFilteringService, + public summaryService: IgxGridSummaryService, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { super(_displayDensityOptions); this.resizeHandler = () => { @@ -2165,16 +2257,15 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.gridAPI.register(this); this.navigation.grid = this; this.filteringService.gridId = this.id; + this.summaryService.grid = this; this.columnListDiffer = this.differs.find([]).create(null); this.calcWidth = this._width && this._width.indexOf('%') === -1 ? parseInt(this._width, 10) : 0; this.calcHeight = 0; this.calcRowCheckboxWidth = 0; - this.onRowAdded.pipe(takeUntil(this.destroy$)).subscribe(() => this.refreshGridState()); - this.onRowDeleted.pipe(takeUntil(this.destroy$)).subscribe(() => this.clearSummaryCache()); - this.onFilteringDone.pipe(takeUntil(this.destroy$)).subscribe(() => this.refreshGridState()); - this.onCellEdit.pipe(takeUntil(this.destroy$)).subscribe((editCell) => this.clearSummaryCache(editCell)); - this.onRowEdit.pipe(takeUntil(this.destroy$)).subscribe(() => this.clearSummaryCache()); + this.onRowAdded.pipe(takeUntil(this.destroy$)).subscribe((args) => this.refreshGridState(args)); + this.onRowDeleted.pipe(takeUntil(this.destroy$)).subscribe((args) => this.clearSummaryCache(args)); + this.onFilteringDone.pipe(takeUntil(this.destroy$)).subscribe(() => this.endEdit(true)); this.onColumnMoving.pipe(takeUntil(this.destroy$)).subscribe(() => { this.endEdit(true); }); @@ -2199,8 +2290,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.initColumns(this.columnList, (col: IgxColumnComponent) => this.onColumnInit.emit(col)); this.columnListDiffer.diff(this.columnList); - this.clearSummaryCache(); - this.summariesHeight = this.calcMaxSummaryHeight(); this._derivePossibleHeight(); this.markForCheck(); @@ -2248,7 +2337,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.calculateGridSizes(); this.onDensityChanged.pipe(takeUntil(this.destroy$)).subscribe(() => { requestAnimationFrame(() => { - this.summariesHeight = 0; + this.summaryService.summaryHeight = 0; this.reflow(); this.verticalScrollContainer.recalcUpdateSizes(); }); @@ -2281,10 +2370,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } this._dataRowList.changes.pipe(takeUntil(this.destroy$)).subscribe(list => - this._horizontalForOfs = list.toArray() - .filter(item => item.element.nativeElement.parentElement !== null) - .map(row => row.virtDirRow) + this._horizontalForOfs = this.combineForOfCollections(list.toArray() + .filter(item => item.element.nativeElement.parentElement !== null), this._summaryRowList) ); + this._summaryRowList.changes.pipe(takeUntil(this.destroy$)).subscribe(summaryList => + this._horizontalForOfs - this.combineForOfCollections(this._dataRowList, summaryList.toArray() + .filter(item => item.element.nativeElement.parentElement !== null))); this.zone.runOutsideAngular(() => { this._vScrollListener = this.verticalScrollHandler.bind(this); @@ -2295,11 +2386,16 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this._hScrollListener = this.horizontalScrollHandler.bind(this); this.parentVirtDir.getHorizontalScroll().addEventListener('scroll', this._hScrollListener); }); - this._horizontalForOfs = this._dataRowList.map(row => row.virtDirRow); + this._horizontalForOfs = this.combineForOfCollections(this._dataRowList, this._summaryRowList); const vertScrDC = this.verticalScrollContainer.dc.instance._viewContainer.element.nativeElement; vertScrDC.addEventListener('scroll', (evt) => { this.scrollHandler(evt); }); } + private combineForOfCollections(dataList, summaryList) { + return dataList.map(row => row.virtDirRow).concat(summaryList.map(row => row.virtDirRow)); + + } + /** * @hidden */ @@ -3127,8 +3223,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } else { this._summaries(rest[0], true, rest[1]); } - this.summariesHeight = 0; - this.markForCheck(); this.calculateGridHeight(); this.cdr.detectChanges(); } @@ -3151,8 +3245,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } else { this._summaries(rest[0], false); } - this.summariesHeight = 0; - this.markForCheck(); this.calculateGridHeight(); this.cdr.detectChanges(); } @@ -3200,20 +3292,16 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements /** * @hidden */ - public clearSummaryCache(editCell?) { - if (editCell && editCell.cell) { - this.gridAPI.remove_summary(this.id, editCell.cell.column.filed); - } else { - this.gridAPI.remove_summary(this.id); - } + public clearSummaryCache(args?) { + this.summaryService.clearSummaryCache(args); } /** * @hidden */ - public refreshGridState(editCell?) { - this.endEdit(true); - this.clearSummaryCache(editCell); + public refreshGridState(args?) { + this.endEdit(true); + this.clearSummaryCache(args); } // TODO: We have return values here. Move them to event args ?? @@ -3259,16 +3347,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } /** - * Recalculates grid summary area. - * Should be run for example when enabling or disabling summaries for a column. - * ```typescript - * this.grid.recalculateSummaries(); - * ``` - * @memberof IgxGridBaseComponent + * @hidden */ public recalculateSummaries() { - this.summariesHeight = 0; - requestAnimationFrame(() => this.calculateGridSizes()); + this.summaryService.resetSummaryHeight(); + this.calculateGridHeight(); + this.cdr.detectChanges(); } /** @@ -3398,10 +3482,15 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * @memberof IgxGridBaseComponent */ get hasSummarizedColumns(): boolean { - const summarizedColumns = this.columnList.filter(col => col.hasSummary); - return summarizedColumns.length > 0 && summarizedColumns.some(col => !col.hidden); + return this.summaryService.hasSummarizedColumns; } + /** + * @hidden + */ + get rootSummariesEnabled(): boolean { + return this.summaryCalculationMode !== GridSummaryCalculationMode.childLevelsOnly; + } /** * Returns if the `IgxGridComponent` has moveable columns. * ```typescript @@ -3499,12 +3588,11 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.theadRow.nativeElement.style.height = `${(this.maxLevelHeaderDepth + 1) * this.defaultRowHeight + (this.allowFiltering ? FILTER_ROW_HEIGHT : 0) + 1}px`; } - + this.summariesHeight = 0; if (!this._height) { this.calcHeight = null; - if (this.hasSummarizedColumns && !this.summariesHeight) { - this.summariesHeight = this.summaries ? - this.calcMaxSummaryHeight() : 0; + if (this.hasSummarizedColumns && this.rootSummariesEnabled) { + this.summariesHeight = this.summaryService.calcMaxSummaryHeight(); } return; } @@ -3521,11 +3609,9 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.paginator.nativeElement.offsetHeight : 0; } - if (!this.summariesHeight) { - this.summariesHeight = this.summaries ? - this.calcMaxSummaryHeight() : 0; + if (this.hasSummarizedColumns && this.rootSummariesEnabled) { + this.summariesHeight = this.summaryService.calcMaxSummaryHeight(); } - const groupAreaHeight = this.getGroupAreaHeight(); if (this._height && this._height.indexOf('%') !== -1) { @@ -3613,23 +3699,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this._derivePossibleWidth(); } - /** - * @hidden - */ - protected calcMaxSummaryHeight() { - let maxSummaryLength = 0; - this.columnList.filter((col) => col.hasSummary && !col.hidden).forEach((column) => { - this.gridAPI.set_summary_by_column_name(this.id, column.field); - const getCurrentSummaryColumn = this.gridAPI.get_summaries(this.id).get(column.field); - if (getCurrentSummaryColumn) { - if (maxSummaryLength < getCurrentSummaryColumn.length) { - maxSummaryLength = getCurrentSummaryColumn.length; - } - } - }); - return maxSummaryLength * this.defaultRowHeight; - } - /** * @hidden */ @@ -3637,12 +3706,15 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.calculateGridWidth(); this.cdr.detectChanges(); this.calculateGridHeight(); + if (this.showRowCheckboxes) { - this.calcRowCheckboxWidth = this.headerCheckboxContainer.nativeElement.clientWidth; + this.calcRowCheckboxWidth = this.headerCheckboxContainer.nativeElement.getBoundingClientRect().width; } + if (this.rowEditable) { this.repositionRowEditingOverlay(this.rowInEditMode); } + this.cdr.detectChanges(); } @@ -3687,9 +3759,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements */ protected _summaries(fieldName: string, hasSummary: boolean, summaryOperand?: any) { const column = this.gridAPI.get_column_by_name(this.id, fieldName); - column.hasSummary = hasSummary; - if (summaryOperand) { - column.summaries = summaryOperand; + if (column) { + column.hasSummary = hasSummary; + if (summaryOperand) { + if (this.rootSummariesEnabled) {this.summaryService.retriggerRootPipe = !this.summaryService.retriggerRootPipe; } + column.summaries = summaryOperand; + } } } @@ -3704,8 +3779,10 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements /** * @hidden */ - protected _disableMultipleSummaries(expressions: string[]) { - expressions.forEach((column) => { this._summaries(column, false); }); + protected _disableMultipleSummaries(expressions) { + expressions.forEach((column) => { + const columnName = column && column.fieldName ? column.fieldName : column; + this._summaries(columnName, false); }); } /** @@ -4616,4 +4693,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements protected getExportCsv(): boolean { return this._exportCsv; } + + /** + * @hidden + */ + public isSummaryRow(rowData): boolean { + return rowData.summaries && (rowData.summaries instanceof Map); + } + } diff --git a/projects/igniteui-angular/src/lib/grids/grid-common.module.ts b/projects/igniteui-angular/src/lib/grids/grid-common.module.ts index 1dcbc2ba3d3..ce038c86735 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-common.module.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-common.module.ts @@ -20,7 +20,6 @@ import { IgxGridCellComponent } from './cell.component'; import { IgxColumnComponent, IgxColumnGroupComponent } from './column.component'; import { IgxColumnHidingModule } from './column-hiding.component'; import { IgxGridHeaderComponent } from './grid-header.component'; -import { IgxGridSummaryComponent } from './grid-summary.component'; import { IgxGridToolbarComponent } from './grid-toolbar.component'; import { IgxGridFilteringCellComponent } from './filtering/grid-filtering-cell.component'; import { IgxGridFilteringRowComponent } from './filtering/grid-filtering-row.component'; @@ -58,6 +57,9 @@ import { IgxGridNavigationService } from './grid-navigation.service'; import { IgxGridHeaderGroupComponent } from './grid-header-group.component'; import { IgxColumnResizingService } from './grid-column-resizing.service'; import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; +import { IgxSummaryRowComponent } from './summaries/summary-row.component'; +import { IgxSummaryCellComponent } from './summaries/summary-cell.component'; +import { IgxSummaryDataPipe } from './summaries/grid-root-summary.pipe'; @NgModule({ declarations: [ @@ -65,7 +67,6 @@ import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; IgxColumnComponent, IgxColumnGroupComponent, IgxGridHeaderComponent, - IgxGridSummaryComponent, IgxGridToolbarComponent, IgxGridToolbarCustomContentDirective, IgxCellFooterTemplateDirective, @@ -85,8 +86,11 @@ import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; IgxGridFilteringRowComponent, IgxDatePipeComponent, IgxDecimalPipeComponent, + IgxSummaryDataPipe, IgxRowComponent, - IgxGridHeaderGroupComponent + IgxGridHeaderGroupComponent, + IgxSummaryRowComponent, + IgxSummaryCellComponent ], entryComponents: [ IgxColumnComponent, @@ -97,7 +101,6 @@ import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; IgxColumnComponent, IgxColumnGroupComponent, IgxGridHeaderComponent, - IgxGridSummaryComponent, IgxGridToolbarComponent, IgxGridToolbarCustomContentDirective, IgxCellFooterTemplateDirective, @@ -114,6 +117,7 @@ import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; IgxRowComponent, IgxGridFilterConditionPipe, IgxGridTransactionPipe, + IgxSummaryDataPipe, IgxDatePipeComponent, IgxDecimalPipeComponent, IgxButtonModule, @@ -137,7 +141,9 @@ import { IgxGridToolbarCustomContentDirective } from './grid-toolbar.component'; IgxColumnPinningModule, IgxGridFilteringCellComponent, IgxGridFilteringRowComponent, - IgxGridHeaderGroupComponent + IgxGridHeaderGroupComponent, + IgxSummaryRowComponent, + IgxSummaryCellComponent ], imports: [ CommonModule, diff --git a/projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts b/projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts index d593eaa0a3c..a4e6b098a98 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts @@ -27,7 +27,11 @@ export class IgxGridNavigationService { } public horizontalScroll(rowIndex) { - return this.grid.dataRowList.find((row) => row.index === rowIndex).virtDirRow; + let rowComp = this.grid.dataRowList.find((row) => row.index === rowIndex); + if (!rowComp) { + rowComp = this.grid.summariesRowList.find((row) => row.index === rowIndex); + } + return rowComp.virtDirRow; } public getColumnUnpinnedIndex(visibleColumnIndex: number) { @@ -90,7 +94,11 @@ export class IgxGridNavigationService { } } - public getCellElementByVisibleIndex(rowIndex, visibleColumnIndex) { + public getCellElementByVisibleIndex(rowIndex, visibleColumnIndex, isSummary = false) { + if (isSummary) { + return this.grid.nativeElement.querySelector( + `igx-grid-summary-cell[data-rowindex="${rowIndex}"][data-visibleIndex="${visibleColumnIndex}"]`); + } if (this.isTreeGrid && visibleColumnIndex === 0) { return this.grid.nativeElement.querySelector( `igx-tree-grid-cell[data-rowindex="${rowIndex}"][data-visibleIndex="${visibleColumnIndex}"]`); @@ -99,12 +107,12 @@ export class IgxGridNavigationService { `igx-grid-cell[data-rowindex="${rowIndex}"][data-visibleIndex="${visibleColumnIndex}"]`); } - public onKeydownArrowRight(element, rowIndex, visibleColumnIndex) { + public onKeydownArrowRight(element, rowIndex, visibleColumnIndex, isSummary = false) { if (this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex === visibleColumnIndex) { return; } if (this.isColumnFullyVisible(visibleColumnIndex + 1)) { // if next column is fully visible or is pinned - if (element.classList.contains('igx-grid__th--pinned-last')) { + if (element.classList.contains('igx-grid__th--pinned-last') || element.classList.contains('igx-grid-summary--pinned-last')) { if (this.isColumnLeftFullyVisible(visibleColumnIndex + 1)) { element.nextElementSibling.firstElementChild.focus(); } else { @@ -121,11 +129,11 @@ export class IgxGridNavigationService { } } else { this.grid.nativeElement.focus({preventScroll: true}); - this.performHorizontalScrollToCell(rowIndex, visibleColumnIndex + 1); + this.performHorizontalScrollToCell(rowIndex, visibleColumnIndex + 1, isSummary); } } - public onKeydownArrowLeft(element, rowIndex, visibleColumnIndex) { + public onKeydownArrowLeft(element, rowIndex, visibleColumnIndex, isSummary = false) { if (visibleColumnIndex === 0) { return; } @@ -134,7 +142,7 @@ export class IgxGridNavigationService { element.parentNode.previousElementSibling.focus(); } else if (!this.isColumnLeftFullyVisible(visibleColumnIndex - 1)) { this.grid.nativeElement.focus({preventScroll: true}); - this.performHorizontalScrollToCell(rowIndex, visibleColumnIndex - 1); + this.performHorizontalScrollToCell(rowIndex, visibleColumnIndex - 1, isSummary); } else { element.previousElementSibling.focus(); } @@ -188,11 +196,16 @@ export class IgxGridNavigationService { this.performHorizontalScrollToCell(rowIndex, editableIndex); } } - public onKeydownHome(rowIndex) { - const rowElement = this.grid.dataRowList.find((row) => row.index === rowIndex).nativeElement; - let firstCell = this.isTreeGrid ? - rowElement.querySelector('igx-tree-grid-cell') : - rowElement.querySelector('igx-grid-cell'); + public onKeydownHome(rowIndex, isSummary = false) { + let rowElement = this.grid.dataRowList.find((row) => row.index === rowIndex); + let cellTag = this.isTreeGrid ? 'igx-tree-grid-cell' : 'igx-grid-cell'; + if (isSummary) { + rowElement = this.grid.summariesRowList.find((row) => row.index === rowIndex); + cellTag = 'igx-grid-summary-cell'; + } + if (!rowElement) { return; } + rowElement = rowElement.nativeElement; + let firstCell = rowElement.querySelector(`${cellTag}`); if (this.grid.pinnedColumns.length || this.displayContainerScrollLeft === 0) { firstCell.focus(); } else { @@ -200,18 +213,24 @@ export class IgxGridNavigationService { .pipe(first()) .subscribe(() => { this.grid.nativeElement.focus({preventScroll: true}); - firstCell = this.isTreeGrid ? rowElement.querySelector('igx-tree-grid-cell') : - rowElement.querySelector('igx-grid-cell'); + firstCell = rowElement.querySelector(`${cellTag}`); firstCell.focus(); }); this.horizontalScroll(rowIndex).scrollTo(0); } } - public onKeydownEnd(rowIndex) { + public onKeydownEnd(rowIndex, isSummary = false) { const index = this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex; - const rowElement = this.grid.dataRowList.find((row) => row.index === rowIndex).nativeElement; - const allCells = rowElement.querySelectorAll('igx-grid-cell'); + let rowElement = this.grid.dataRowList.find((row) => row.index === rowIndex); + let cellTag = 'igx-grid-cell'; + if (isSummary) { + rowElement = this.grid.summariesRowList.find((row) => row.index === rowIndex); + cellTag = 'igx-grid-summary-cell'; + } + if (!rowElement) { return; } + rowElement = rowElement.nativeElement; + const allCells = rowElement.querySelectorAll(`${cellTag}`); const lastCell = allCells[allCells.length - 1]; if (this.isColumnFullyVisible(index)) { lastCell.focus(); @@ -277,8 +296,8 @@ export class IgxGridNavigationService { .pipe(first()) .subscribe(() => { const tag = rowElement.tagName.toLowerCase(); - if (tag === 'igx-grid-row' || tag === 'igx-tree-grid-row') { - rowElement = this.getRowByIndex(currentRowIndex); + if (tag === 'igx-grid-row' || tag === 'igx-tree-grid-row' || tag === 'igx-grid-summary-row') { + rowElement = this.getRowByIndex(currentRowIndex, tag); } else { rowElement = this.grid.nativeElement.querySelector( `igx-grid-groupby-row[data-rowindex="${currentRowIndex}"]`); @@ -294,7 +313,13 @@ export class IgxGridNavigationService { if (currentRowEl.previousElementSibling.tagName.toLowerCase() === 'igx-grid-groupby-row') { currentRowEl.previousElementSibling.focus(); } else { + const isSummaryRow = currentRowEl.previousElementSibling.tagName.toLowerCase() === 'igx-grid-summary-row'; if (this.isColumnFullyVisible(visibleColumnIndex) && this.isColumnLeftFullyVisible(visibleColumnIndex)) { + if (isSummaryRow) { + currentRowEl.previousElementSibling. + querySelector(`igx-grid-summary-cell[data-visibleIndex="${visibleColumnIndex}"]`).focus(); + return; + } const cell = this.isTreeGrid && visibleColumnIndex === 0 ? currentRowEl.previousElementSibling.querySelector(`igx-tree-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`) : currentRowEl.previousElementSibling.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`); @@ -303,7 +328,7 @@ export class IgxGridNavigationService { } this.grid.nativeElement.focus({preventScroll: true}); this.performHorizontalScrollToCell(parseInt( - currentRowEl.previousElementSibling.getAttribute('data-rowindex'), 10), visibleColumnIndex); + currentRowEl.previousElementSibling.getAttribute('data-rowindex'), 10), visibleColumnIndex, isSummaryRow); } } @@ -323,8 +348,8 @@ export class IgxGridNavigationService { .pipe(first()) .subscribe(() => { const tag = rowElement.tagName.toLowerCase(); - if (tag === 'igx-grid-row' || tag === 'igx-tree-grid-row') { - rowElement = this.getRowByIndex(currentRowIndex); + if (tag === 'igx-grid-row' || tag === 'igx-tree-grid-row' || tag === 'igx-grid-summary-row') { + rowElement = this.getRowByIndex(currentRowIndex, tag); } else { rowElement = this.grid.nativeElement.querySelector( `igx-grid-groupby-row[data-rowindex="${currentRowIndex}"]`); @@ -340,7 +365,12 @@ export class IgxGridNavigationService { if (rowElement.nextElementSibling.tagName.toLowerCase() === 'igx-grid-groupby-row') { rowElement.nextElementSibling.focus(); } else { + const isSummaryRow = rowElement.nextElementSibling.tagName.toLowerCase() === 'igx-grid-summary-row'; if (this.isColumnFullyVisible(visibleColumnIndex) && this.isColumnLeftFullyVisible(visibleColumnIndex)) { + if (isSummaryRow) { + rowElement.nextElementSibling.querySelector(`igx-grid-summary-cell[data-visibleIndex="${visibleColumnIndex}"]`).focus(); + return; + } const cell = this.isTreeGrid && visibleColumnIndex === 0 ? rowElement.nextElementSibling.querySelector(`igx-tree-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`) : rowElement.nextElementSibling.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`); @@ -348,7 +378,7 @@ export class IgxGridNavigationService { return; } this.performHorizontalScrollToCell(parseInt( - rowElement.nextElementSibling.getAttribute('data-rowindex'), 10), visibleColumnIndex); + rowElement.nextElementSibling.getAttribute('data-rowindex'), 10), visibleColumnIndex, isSummaryRow); } } @@ -394,23 +424,26 @@ export class IgxGridNavigationService { } } - public performTab(currentRowEl, rowIndex, visibleColumnIndex) { + public performTab(currentRowEl, rowIndex, visibleColumnIndex, isSummaryRow = false) { if (this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex === visibleColumnIndex) { if (this.isRowInEditMode(rowIndex)) { this.grid.rowEditTabs.first.element.nativeElement.focus(); return; } - if (this.grid.rowList.find(row => row.index === rowIndex + 1)) { + const rowEl = this.grid.rowList.find(row => row.index === rowIndex + 1) ? + this.grid.rowList.find(row => row.index === rowIndex + 1) : + this.grid.summariesRowList.find(row => row.index === rowIndex + 1); + if (rowEl) { this.navigateDown(currentRowEl, rowIndex, 0); } } else { - const cell = this.getCellElementByVisibleIndex(rowIndex, visibleColumnIndex); + const cell = this.getCellElementByVisibleIndex(rowIndex, visibleColumnIndex, isSummaryRow); if (cell) { if (this.grid.rowEditable && this.isRowInEditMode(rowIndex)) { this.moveNextEditable(cell, rowIndex, visibleColumnIndex); return; } - this.onKeydownArrowRight(cell, rowIndex, visibleColumnIndex); + this.onKeydownArrowRight(cell, rowIndex, visibleColumnIndex, isSummaryRow); } } } @@ -425,7 +458,7 @@ export class IgxGridNavigationService { } } - public performShiftTabKey(currentRowEl, rowIndex, visibleColumnIndex) { + public performShiftTabKey(currentRowEl, rowIndex, visibleColumnIndex, isSummary = false) { if (visibleColumnIndex === 0) { if (this.isRowInEditMode(rowIndex)) { this.grid.rowEditTabs.last.element.nativeElement.focus(); @@ -438,31 +471,30 @@ export class IgxGridNavigationService { this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex); } } else { - const cell = currentRowEl.querySelector(`igx-grid-cell[data-visibleIndex="${visibleColumnIndex}"]`); + const cell = this.getCellElementByVisibleIndex(rowIndex, visibleColumnIndex, isSummary); if (cell) { if (this.grid.rowEditable && this.isRowInEditMode(rowIndex)) { this.movePreviousEditable( rowIndex, visibleColumnIndex); return; } - this.onKeydownArrowLeft(cell, rowIndex, visibleColumnIndex); + this.onKeydownArrowLeft(cell, rowIndex, visibleColumnIndex, isSummary); } } } - private performHorizontalScrollToCell(rowIndex, visibleColumnIndex) { + private performHorizontalScrollToCell(rowIndex, visibleColumnIndex, isSummary = false) { const unpinnedIndex = this.getColumnUnpinnedIndex(visibleColumnIndex); this.grid.parentVirtDir.onChunkLoad .pipe(first()) .subscribe(() => { - this.getCellElementByVisibleIndex(rowIndex, visibleColumnIndex).focus(); + this.getCellElementByVisibleIndex(rowIndex, visibleColumnIndex, isSummary).focus(); }); this.horizontalScroll(rowIndex).scrollTo(unpinnedIndex); } - private getRowByIndex(index) { - return this.isTreeGrid ? this.grid.nativeElement.querySelector( - `igx-tree-grid-row[data-rowindex="${index}"]`) : - this.grid.nativeElement.querySelector( - `igx-grid-row[data-rowindex="${index}"]`); + + private getRowByIndex(index, tag) { + return this.grid.nativeElement.querySelector( + `${tag}[data-rowindex="${index}"]`); } private getAllRows() { diff --git a/projects/igniteui-angular/src/lib/grids/grid-summary.component.html b/projects/igniteui-angular/src/lib/grids/grid-summary.component.html deleted file mode 100644 index d0da05407b7..00000000000 --- a/projects/igniteui-angular/src/lib/grids/grid-summary.component.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
- {{ translateSummary(summary) }} - - {{ column.dataType === 'number' ? (summary.summaryResult | igxdecimal) : column.dataType === 'date' ? (summary.summaryResult | igxdate) : (summary.summaryResult) }} - -
-
-
diff --git a/projects/igniteui-angular/src/lib/grids/grid-summary.component.ts b/projects/igniteui-angular/src/lib/grids/grid-summary.component.ts deleted file mode 100644 index d21d194bb0c..00000000000 --- a/projects/igniteui-angular/src/lib/grids/grid-summary.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, - Component, DoCheck, HostBinding, Input -} from '@angular/core'; -import { DataType } from '../data-operations/data-util'; -import { GridBaseAPIService } from './api.service'; -import { IgxColumnComponent } from './column.component'; -import { IgxGridBaseComponent } from './grid-base.component'; -import { IgxSummaryResult } from './grid-summary'; -/** - *@hidden - */ -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - preserveWhitespaces: false, - selector: 'igx-grid-summary', - templateUrl: './grid-summary.component.html' -}) -export class IgxGridSummaryComponent implements DoCheck { - - fieldName: string; - - @Input() - public column: IgxColumnComponent; - - @Input() - public gridID: string; - - @HostBinding('class.igx-grid-summary--fw') - get widthPersistenceClass(): boolean { - return this.column.width !== null; - } - - @HostBinding('class.igx-grid-summary--pinned') - get isPinned() { - return this.column.pinned; - } - - @HostBinding('class.igx-grid-summary--pinned-last') - get isLastPinned() { - const pinnedCols = this.gridAPI.get(this.gridID).pinnedColumns; - if (pinnedCols.length === 0) { - return false; - } else { - return pinnedCols.indexOf(this.column) === pinnedCols.length - 1; - } - } - - @HostBinding('class.igx-grid-summary--empty') - get emptyClass(): boolean { - return !this.column.hasSummary; - } - - @HostBinding('style.min-width') - @HostBinding('style.flex-basis') - get width() { - return this.column.width; - } - - @HostBinding('class.igx-grid-summary--compact') - get compactCSS() { - return this.gridAPI.get(this.gridID).isCompact(); - } - - @HostBinding('class.igx-grid-summary--cosy') - get cosyCSS() { - return this.gridAPI.get(this.gridID).isCosy(); - } - - @HostBinding('class.igx-grid-summary') - get defaultCSS() { - return this.gridAPI.get(this.gridID).isComfortable(); - } - - get dataType(): DataType { - return this.column.dataType; - } - public summaryItemHeight; - public itemClass = 'igx-grid-summary__item'; - - constructor(public gridAPI: GridBaseAPIService, public cdr: ChangeDetectorRef) { } - - ngDoCheck() { - this.summaryItemHeight = this.gridAPI.get(this.gridID).defaultRowHeight; - this.cdr.detectChanges(); - } - - public translateSummary(summary: IgxSummaryResult): string { - return this.gridAPI.get(this.gridID).resourceStrings[`igx_grid_summary_${summary.key}`] || summary.label; - } - - get resolveSummaries(): any[] { - if (this.fieldName) { - const field = this.fieldName; - this.fieldName = null; - this.gridAPI.set_summary_by_column_name(this.gridID, field); - if (this.column.field === field) { - return this.gridAPI.get_summaries(this.gridID).get(field); - } else { - return this.gridAPI.get_summaries(this.gridID).get(this.column.field); - } - } else { - this.gridAPI.set_summary_by_column_name(this.gridID, this.column.field); - return this.gridAPI.get_summaries(this.gridID).get(this.column.field); - } - } -} diff --git a/projects/igniteui-angular/src/lib/grids/grid/column-group.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/column-group.spec.ts index 47e648ab9e1..9d13000f372 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/column-group.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/column-group.spec.ts @@ -1207,36 +1207,6 @@ describe('IgxGrid - multi-column headers', () => { testGroupsAndColumns(18, 11); })); - it('summaries - verify summaries when there are grouped columns', (async () => { - const fixture = TestBed.createComponent(ColumnGroupFourLevelTestComponent); - fixture.detectChanges(); - const grid = fixture.componentInstance.grid; - - // Verify columns and groups - testGroupsAndColumns(18, 11); - - const allColumns = grid.columnList; - allColumns.forEach((col) => { - if (!col.columnGroup) { - col.hasSummary = true; - } - }); - await wait(); - fixture.detectChanges(); - - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - expect(summaries.length).toBe(7); - let index = 0; - grid.visibleColumns.forEach((col) => { - if (!col.columnGroup && index < 7) { - expect(col.hasSummary).toBeTruthy(); - const labels = summaries[index].queryAll(By.css('.igx-grid-summary__label')); - expect(labels.length).toBe(1); - expect(labels[0].nativeElement.innerText).toBe('Count'); - index++; - } - }); - })); it('grouping - verify grouping when there are grouped columns', () => { const fixture = TestBed.createComponent(ColumnGroupGroupingTestComponent); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-api.service.ts index 69bfde99546..750c39b43ce 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-api.service.ts @@ -137,4 +137,21 @@ export class IgxGridAPIService extends GridBaseAPIService { }); } + public get_groupBy_record_id(gRow: IGroupByRecord): string { + let recordId = '{ '; + const hierrarchy = DataUtil.getHierarchy(gRow); + + for (let i = 0; i < hierrarchy.length; i++) { + const groupByKey = hierrarchy[i]; + recordId += `'${groupByKey.fieldName}': '${groupByKey.value}'`; + + if (i < hierrarchy.length - 1) { + recordId += ', '; + } + } + recordId += ' }'; + + return recordId; + } + } diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering.spec.ts index 8691c133d0f..3b636ea814f 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering.spec.ts @@ -10,6 +10,7 @@ import { IgxStringFilteringOperand, IgxNumberFilteringOperand, IgxBooleanFilteringOperand, IgxDateFilteringOperand, IgxFilteringOperand, FilteringExpressionsTree } from '../../../public_api'; import { configureTestSuite } from '../../test-utils/configure-suite'; import { IgxChipComponent } from '../../chips'; +import { HelperUtils } from '../../test-utils/helper-utils.spec'; const FILTERING_TOGGLE_CLASS = 'igx-filtering__toggle'; const FILTERING_TOGGLE_FILTERED_CLASS = 'igx-filtering__toggle--filtered'; @@ -574,10 +575,8 @@ describe('IgxGrid - Filtering actions', () => { expect(grid.rowList.length).toEqual(1); - const summariesReleaseDate = fix.debugElement.queryAll(By.css('.igx-grid-summary'))[0]; - const count = summariesReleaseDate.query(By.css('[title=\'Count\']')).nativeElement.nextSibling.innerText; - - expect(count).toBe('1'); + const summaryRow = fix.debugElement.query(By.css('igx-grid-summary-row')); + HelperUtils.verifyColumnSummaries(summaryRow, 0, ['Count'], ['1']); })); }); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts index b84738bdf3c..a6423f65eec 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-summary.spec.ts @@ -10,632 +10,839 @@ import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { GridFunctions } from '../../test-utils/grid-functions.spec'; import { configureTestSuite } from '../../test-utils/configure-suite'; +import { + ProductsComponent, + VirtualSummaryColumnComponent, + SummaryColumnComponent, + FilteringComponent, + SummarieGroupByComponent +} from '../../test-utils/grid-samples.spec'; +import { HelperUtils } from '../../test-utils/helper-utils.spec'; +import { SampleTestData } from '../../test-utils/sample-test-data.spec'; +import { IgxNumberFilteringOperand, SortingDirection } from 'igniteui-angular'; +import { ColumnGroupFourLevelTestComponent } from './column-group.spec'; describe('IgxGrid - Summaries', () => { configureTestSuite(); const SUMMARY_CLASS = '.igx-grid-summary'; - const SUMMARY_LABEL_CLASS = '.igx-grid-summary__label'; - const SUMMARY_VALUE_CLASS = '.igx-grid-summary__result'; const ITEM_CLASS = 'igx-grid-summary__item'; - const HIDDEN_ITEM_CLASS = 'igx-grid-summary__item--inactive'; + const SUMMARY_ROW = 'igx-grid-summary-row'; + const SUMARRY_CELL = 'igx-grid-summary-cell'; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ - NoActiveSummariesComponent, + ProductsComponent, SummaryColumnComponent, CustomSummariesComponent, VirtualSummaryColumnComponent, - SummaryColumnsWithIdenticalWidthsComponent + SummaryColumnsWithIdenticalWidthsComponent, + FilteringComponent, + ColumnGroupFourLevelTestComponent, + SummarieGroupByComponent ], imports: [BrowserAnimationsModule, IgxGridModule.forRoot(), NoopAnimationsModule] }) .compileComponents(); })); - it('should not have summary if no summary is active ', () => { - const fixture = TestBed.createComponent(NoActiveSummariesComponent); - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css(SUMMARY_CLASS))).toBeNull(); - }); - it('should enableSummaries through grid API ', () => { - const fixture = TestBed.createComponent(NoActiveSummariesComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - expect(grid.hasSummarizedColumns).toBe(false); - let tFoot = fixture.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; - expect(tFoot < grid.defaultRowHeight).toBe(true); - - grid.enableSummaries([{ fieldName: 'ProductName' }, { fieldName: 'ProductID' }]); - fixture.detectChanges(); - - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - let summaryLength = 0; - summaries.forEach((summary) => { - if (summary.children.length > 0) { - summaryLength++; - } + describe('Base tests: ', () => { + it('should not have summary if no summary is active ', () => { + const fixture = TestBed.createComponent(ProductsComponent); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css(SUMMARY_CLASS))).toBeNull(); }); - expect(grid.hasSummarizedColumns).toBe(true); - - tFoot = fixture.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; - expect(summaryLength).toBe(2); - expect(grid.getColumnByName('ProductID').hasSummary).toBe(true); - expect(grid.getColumnByName('ProductName').hasSummary).toBe(true); - expect(grid.getColumnByName('OrderDate').hasSummary).toBe(false); - - const expectedLength = GridFunctions.calcMaxSummaryHeight(grid.columnList, summaries, grid.defaultRowHeight); - expect(tFoot >= expectedLength).toBe(true); - }); + it('should enableSummaries through grid API ', () => { + const fixture = TestBed.createComponent(ProductsComponent); + fixture.detectChanges(); - it('should disableSummaries through grid API ', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); + const grid = fixture.componentInstance.grid; + expect(grid.hasSummarizedColumns).toBe(false); + let tFoot = fixture.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; + expect(tFoot < grid.defaultRowHeight).toBe(true); - const grid = fixture.componentInstance.grid1; - const summariedColumns = []; - grid.columnList.forEach((col) => { - if (col.hasSummary) { - summariedColumns.push(col.field); - } - }); - grid.disableSummaries(summariedColumns); - fixture.detectChanges(); + grid.enableSummaries([{ fieldName: 'ProductName' }, { fieldName: 'ProductID' }]); + fixture.detectChanges(); - expect(fixture.debugElement.query(By.css(SUMMARY_CLASS))).toBeNull(); - expect(grid.hasSummarizedColumns).toBe(false); - }); - it('should have summary per each column that \'hasSummary\'= true', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); + const summaryRow = fixture.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 3, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 4, [], []); - expect(fixture.debugElement.query(By.css(SUMMARY_CLASS))).toBeDefined(); + expect(grid.getColumnByName('ProductID').hasSummary).toBe(true); + expect(grid.getColumnByName('ProductName').hasSummary).toBe(true); + expect(grid.getColumnByName('OrderDate').hasSummary).toBe(false); - let summaries = 0; - const summariedColumns = fixture.componentInstance.grid1.columnList.filter((col) => col.hasSummary === true).length; - summaries = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS)).filter((summary) => summary.children.length > 0).length; - expect(summaries).toBe(summariedColumns); - }); - it('should have count summary for string and boolean data types', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const sum = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - - let index = 0; - sum.columnList.forEach((col) => { - if (col.hasSummary && (col.dataType === 'string' || col.dataType === 'boolean')) { - const labels = summaries[index].queryAll(By.css(SUMMARY_LABEL_CLASS)); - expect(labels.length).toBe(1); - expect(labels[0].nativeElement.innerText).toBe('Count'); - } - index++; - }); - }); - it('should have count, min, max, avg and sum summary for numeric data types', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const sum = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - - let index = 0; - sum.columnList.forEach((col) => { - if (col.hasSummary && (col.dataType === 'number')) { - const labels = summaries[index].queryAll(By.css(SUMMARY_LABEL_CLASS)); - expect(labels.length).toBe(5); - expect(labels[0].nativeElement.innerText).toBe('Count'); - expect(labels[1].nativeElement.innerText).toBe('Min'); - expect(labels[2].nativeElement.innerText).toBe('Max'); - expect(labels[3].nativeElement.innerText).toBe('Sum'); - expect(labels[4].nativeElement.innerText).toBe('Avg'); - } - index++; - }); - }); - it('should have count, earliest and latest summary for \'date\' data types', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const sum = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - - let index = 0; - sum.columnList.forEach((col) => { - if (col.hasSummary && (col.dataType === 'date')) { - const labels = summaries[index].queryAll(By.css(SUMMARY_LABEL_CLASS)); - expect(labels.length).toBe(3); - expect(labels[0].nativeElement.innerText).toBe('Count'); - expect(labels[1].nativeElement.innerText).toBe('Earliest'); - expect(labels[2].nativeElement.innerText).toBe('Latest'); - } - index++; - }); - }); - it('should summary function stay active when is clicked on it\'s label', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const summary = fixture.debugElement.queryAll(By.css('igx-grid-summary'))[3]; - const min: DebugElement = summary.query(By.css('[title=\'Min\']')); - - expect(min.parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); - min.triggerEventHandler('click', null); - fixture.detectChanges(); - - expect(min.parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); - expect(summary.query(By.css('[title=\'Count\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); - expect(summary.query(By.css('[title=\'Max\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); - expect(summary.query(By.css('[title=\'Sum\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); - expect(summary.query(By.css('[title=\'Avg\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); - }); - it('should recalculate summary functions onRowAdded', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS)); - - let countValue; - summaries.forEach((summary) => { - const countLabel = summary.query(By.css('[title=\'Count\']')); - if (countLabel) { - countValue = countLabel.nativeElement.nextSibling.innerText; - expect(+countValue).toBe(grid.rowList.length); - } + tFoot = fixture.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; + expect(tFoot).toEqual(grid.defaultRowHeight + 1); }); - grid.addRow({ - ProductID: 11, ProductName: 'Belgian Chocolate', InStock: true, UnitsInStock: 99000, OrderDate: new Date('2018-03-01') - }); - fixture.detectChanges(); - - let updatedValue; - summaries.forEach((summary) => { - const countLabel = summary.query(By.css('[title=\'Count\']')); - if (countLabel) { - updatedValue = countLabel.nativeElement.nextSibling.innerText; - expect(+updatedValue).toBe(grid.rowList.length); - } - }); - }); - it('should recalculate summary functions onRowDeleted', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS)); - - let countValue; - summaries.forEach((summary) => { - const countLabel = summary.query(By.css('[title=\'Count\']')); - if (countLabel) { - countValue = countLabel.nativeElement.nextSibling.innerText; - expect(+countValue).toBe(grid.rowList.length); - } + it(`should recalculate grid sizes correctly when the column is outside of the viewport`, (async () => { + const fixture = TestBed.createComponent(ProductsComponent); + fixture.detectChanges(); + + const grid = fixture.componentInstance.grid; + grid.width = '300px'; + await wait(100); + fixture.detectChanges(); + + grid.getColumnByName('UnitsInStock').hasSummary = true; + await wait(30); + fixture.detectChanges(); + + const tFoot = fixture.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; + expect(tFoot).toEqual(5 * grid.defaultRowHeight + 1); + expect(fixture.debugElement.query(By.css(SUMMARY_CLASS))).toBeDefined(); + })); + + xit('should have correct summaries when there are null and undefined values', fakeAsync(() => { + const fixture = TestBed.createComponent(FilteringComponent); + fixture.detectChanges(); + + const grid = fixture.componentInstance.grid; + grid.getColumnByName('ProductName').hasSummary = true; + grid.getColumnByName('Downloads').hasSummary = true; + grid.getColumnByName('Released').hasSummary = true; + grid.getColumnByName('ReleaseDate').hasSummary = true; + tick(100); + fixture.detectChanges(); + + const summaryRow = fixture.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['8']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['8', '1', '1,000', '2,204', '275.5']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count'], ['8']); + const options = { year: 'numeric', month: 'short', day: 'numeric' }; + const earliest = SampleTestData.timeGenerator.timedelta(SampleTestData.today, 'month', -1).toLocaleString('us', options); + const latest = SampleTestData.timeGenerator.timedelta(SampleTestData.today, 'month', 1).toLocaleString('us', options); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Earliest', 'Latest'], ['8', earliest, latest]); + })); + + it('should properly render custom summaries', () => { + const fixture = TestBed.createComponent(CustomSummariesComponent); + const gridComp = fixture.componentInstance.grid1; + fixture.detectChanges(); + + const summaryRow = fixture.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['10', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Earliest'], ['5/17/1990']); + + gridComp.filter('UnitsInStock', '0', IgxNumberFilteringOperand.instance().condition('lessThan'), true); + fixture.detectChanges(); + + const filterResult = gridComp.rowList.length; + expect(filterResult).toEqual(0); + + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['0', '', '']); }); - grid.deleteRow(1); - fixture.detectChanges(); + it(`Should update summary section when the column is outside of the + viewport and have identical width with others`, (async () => { + const fixture = TestBed.createComponent(ProductsComponent); + fixture.detectChanges(); + + const grid = fixture.componentInstance.grid; + grid.getColumnByName('UnitsInStock').hasSummary = true; + grid.width = '300px'; + await wait(100); + fixture.detectChanges(); + + grid.addRow({ + ProductID: 11, ProductName: 'Belgian Chocolate', InStock: true, UnitsInStock: 99000, OrderDate: new Date('2018-03-01') + }); + await wait(30); + fixture.detectChanges(); + GridFunctions.scrollLeft(grid, 600); + await wait(30); + fixture.detectChanges(); + + const summaryRow = fixture.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], + ['11', '0', '99,000', '138,004', '12,545.818']); + })); + + xit('When we have data which is undefined and enable summary per defined column, error should not be thrown', () => { + const fixture = TestBed.createComponent(ProductsComponent); + fixture.detectChanges(); + + const grid = fixture.componentInstance.grid; + const idColumn = grid.getColumnByName('ProductID'); + expect(grid.data.length > 0).toEqual(true); + + fixture.componentInstance.data = undefined; + fixture.detectChanges(); + + expect(grid.data).toEqual(undefined); + expect(() => { + grid.enableSummaries(idColumn.field); + fixture.detectChanges(); + }).not.toThrow(); + }); - let updatedValue; - summaries.forEach((summary) => { - const countLabel = summary.query(By.css('[title=\'Count\']')); - if (countLabel) { - updatedValue = countLabel.nativeElement.nextSibling.innerText; - expect(+updatedValue).toBe(grid.rowList.length); + xit('should change custom summaries at runtime', fakeAsync(() => { + const fixture = TestBed.createComponent(CustomSummariesComponent); + const grid = fixture.componentInstance.grid1; + fixture.detectChanges(); + + const summaryRow = fixture.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['10', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Earliest'], ['5/17/1990']); + grid.getColumnByName('UnitsInStock').summaries = fixture.componentInstance.dealsSummaryMinMax; + tick(100); + fixture.detectChanges(); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Min', 'Max'], ['0', '20,000']); + })); + + it('should render correct data after hiding one bigger and then one smaller summary when scrolled to the bottom', (async () => { + const fixture = TestBed.createComponent(VirtualSummaryColumnComponent); + fixture.detectChanges(); + + const grid = fixture.componentInstance.grid; + let rowsRendered; + let tbody; + let expectedRowLenght; + let firstCellsText; + + fixture.componentInstance.scrollTop(10000); + await wait(100); + fixture.detectChanges(); + + rowsRendered = fixture.nativeElement.querySelectorAll('igx-grid-row'); + tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; + expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; + expect(rowsRendered.length).toEqual(expectedRowLenght); + + grid.disableSummaries(['ProductName', 'InStock', 'UnitsInStock']); + await wait(50); + fixture.detectChanges(); + + rowsRendered = Array.from(fixture.nativeElement.querySelectorAll('igx-grid-row')); + tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; + expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; + + firstCellsText = rowsRendered.map((item) => { + return item.querySelectorAll('igx-grid-cell')[0].textContent.trim(); + }); + expect(rowsRendered.length).toEqual(expectedRowLenght); + let expectedFirstCellNum = grid.data.length - expectedRowLenght + 1; + + for (let i = 0; i < rowsRendered.length - 1; i++) { + expect(firstCellsText[i]).toEqual((expectedFirstCellNum + i).toString()); } - }); - }); - it('should recalculate summary functions on updateRow', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS)); - - const productNameCell = grid.getCellByColumn(0, 'ProductName'); - const unitsInStockCell = grid.getCellByColumn(0, 'UnitsInStock'); - let countValue; - summaries.forEach((summary) => { - const countLabel = summary.query(By.css('[title=\'Count\']')); - if (countLabel) { - countValue = countLabel.nativeElement.nextSibling.innerText; - expect(+countValue).toBe(grid.rowList.length); + + grid.disableSummaries(['OrderDate']); + await wait(50); + fixture.detectChanges(); + + rowsRendered = Array.from(fixture.nativeElement.querySelectorAll('igx-grid-row')); + tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; + expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; + + firstCellsText = rowsRendered.map((item) => { + return item.querySelectorAll('igx-grid-cell')[0].textContent.trim(); + }); + expect(rowsRendered.length).toEqual(expectedRowLenght); + expectedFirstCellNum = grid.data.length - expectedRowLenght + 1; + for (let i = 0; i < rowsRendered.length - 1; i++) { + expect(firstCellsText[i]).toEqual((expectedFirstCellNum + i).toString()); } + })); + + describe('', () => { + let fix; + let grid: IgxGridComponent; + beforeEach(() => { + fix = TestBed.createComponent(SummaryColumnComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + }); + + it('should disableSummaries through grid API ', () => { + const summariedColumns = []; + grid.columnList.forEach((col) => { + if (col.hasSummary) { + summariedColumns.push(col.field); + } + }); + grid.disableSummaries(summariedColumns); + fix.detectChanges(); + + expect(fix.debugElement.query(By.css(SUMMARY_CLASS))).toBeNull(); + expect(grid.hasSummarizedColumns).toBe(false); + }); + + it('should change summary operand through grid API ', (async () => { + grid.enableSummaries([{ fieldName: 'UnitsInStock', customSummary: fix.componentInstance.dealsSummaryMinMax }]); + // grid.recalculateSummaries(); + await wait(200); + fix.detectChanges(); + + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Min', 'Max'], ['0', '20,000']); + })); + + it('should have summary per each column that \'hasSummary\'= true', () => { + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], []); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], []); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], []); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Earliest', 'Latest'], []); + }); + + it('should have count summary for string and boolean data types', () => { + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + grid.columnList.forEach((col) => { + if (col.hasSummary && (col.dataType === 'string' || col.dataType === 'boolean')) { + HelperUtils.verifyColumnSummaries(summaryRow, col.visibleIndex, ['Count'], []); + } + }); + }); + + it('should have count, min, max, avg and sum summary for numeric data types', () => { + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + grid.columnList.forEach((col) => { + if (col.hasSummary && (col.dataType === 'number')) { + HelperUtils.verifyColumnSummaries(summaryRow, col.visibleIndex, ['Count', 'Min', 'Max', 'Sum', 'Avg'], []); + } + }); + }); + + it('should have count, earliest and latest summary for \'date\' data types', () => { + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + grid.columnList.forEach((col) => { + if (col.hasSummary && (col.dataType === 'date')) { + HelperUtils.verifyColumnSummaries(summaryRow, col.visibleIndex, ['Count', 'Earliest', 'Latest'], []); + } + }); + }); + + it('should summary function stay active when is clicked on it\'s label', () => { + const summary = fix.debugElement.queryAll(By.css('igx-grid-summary-cell'))[3]; + const min: DebugElement = summary.query(By.css('[title=\'Min\']')); + + expect(min.parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); + min.triggerEventHandler('click', null); + fix.detectChanges(); + + expect(min.parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); + expect(summary.query(By.css('[title=\'Count\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); + expect(summary.query(By.css('[title=\'Max\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); + expect(summary.query(By.css('[title=\'Sum\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); + expect(summary.query(By.css('[title=\'Avg\']')).parent.nativeElement.classList.contains(ITEM_CLASS)).toBeTruthy(); + }); + + it('should calculate summaries for \'number\' dataType or return if no data is provided', () => { + const summaryClass = fix.componentInstance.numberSummary; + + const summaries = summaryClass.operate(fix.componentInstance.data.map((x) => x['UnitsInStock'])); + expect(summaries[0].summaryResult).toBe(10); + expect(summaries[1].summaryResult).toBe(0); + expect(summaries[2].summaryResult).toBe(20000); + expect(summaries[3].summaryResult).toBe(39004); + expect(summaries[4].summaryResult).toBe(3900.4); + + const emptySummaries = summaryClass.operate(); + expect(emptySummaries[0].summaryResult).toBe(0); + expect(typeof emptySummaries[1].summaryResult).not.toEqual(undefined); + expect(typeof emptySummaries[2].summaryResult).not.toEqual(undefined); + expect(typeof emptySummaries[3].summaryResult).not.toEqual(undefined); + expect(typeof emptySummaries[4].summaryResult).not.toEqual(undefined); + + expect(typeof emptySummaries[1].summaryResult).not.toEqual(null); + expect(typeof emptySummaries[2].summaryResult).not.toEqual(null); + expect(typeof emptySummaries[3].summaryResult).not.toEqual(null); + expect(typeof emptySummaries[4].summaryResult).not.toEqual(null); + + expect(emptySummaries[1].summaryResult.length === 0).toBeTruthy(); + expect(emptySummaries[2].summaryResult.length === 0).toBeTruthy(); + expect(emptySummaries[3].summaryResult.length === 0).toBeTruthy(); + expect(emptySummaries[4].summaryResult.length === 0).toBeTruthy(); + }); + + + it('should calculate summaries for \'date\' dataType or return if no data is provided', () => { + const summaryClass = fix.componentInstance.dateSummary; + + const summaries = summaryClass.operate(fix.componentInstance.data.map((x) => x['OrderDate'])); + expect(summaries[0].summaryResult).toBe(10); + expect(summaries[1].summaryResult.toLocaleDateString()).toBe('5/17/1990'); + expect(summaries[2].summaryResult.toLocaleDateString()).toBe('12/25/2025'); + + const emptySummaries = summaryClass.operate([]); + expect(emptySummaries[0].summaryResult).toBe(0); + expect(emptySummaries[1].summaryResult).toBe(undefined); + expect(emptySummaries[2].summaryResult).toBe(undefined); + }); + + it('should calc tfoot height according number of summary functions', () => { + const summaries = fix.debugElement.queryAll(By.css('igx-grid-summary-cell')); + const footerRow = fix.debugElement.query(By.css('.igx-grid__tfoot')).query(By.css('.igx-grid__summaries')) + .nativeElement.getBoundingClientRect().height; + const tfootSize = +footerRow; + + const expectedHeight = GridFunctions.calcMaxSummaryHeight(grid.columnList, summaries, grid.defaultRowHeight); + + expect(tfootSize).toBe(expectedHeight); + }); + + it('should be able to change \'hasSummary\' property runtime and to recalculate grid sizes correctly', fakeAsync(() => { + grid.columnList.forEach((col) => { + if (col.field !== 'ProductID') { + expect(grid.getColumnByName(col.field).hasSummary).toBe(true); + } + }); + grid.getColumnByName('UnitsInStock').hasSummary = false; + tick(100); + fix.detectChanges(); + + expect(grid.getColumnByName('UnitsInStock').hasSummary).toBe(false); + + const summaries = fix.debugElement.queryAll(By.css(SUMARRY_CELL)).filter((el) => + el.nativeElement.classList.contains('igx-grid-summary--empty') === false); + const tfootSize = +fix.debugElement.query(By.css('.igx-grid__summaries')) + .nativeElement.getBoundingClientRect().height; + const expectedHeight = GridFunctions.calcMaxSummaryHeight(grid.columnList, summaries, grid.defaultRowHeight); + expect(tfootSize).toBe(expectedHeight); + + grid.getColumnByName('ProductName').hasSummary = false; + grid.getColumnByName('InStock').hasSummary = false; + grid.getColumnByName('OrderDate').hasSummary = false; + fix.detectChanges(); + tick(100); + expect(fix.debugElement.query(By.css('.igx-grid__summaries'))).toBeNull(); + expect(grid.hasSummarizedColumns).toBe(false); + })); }); - - expect(productNameCell.value).toBe('Chai'); - expect(unitsInStockCell.value).toBe(2760); - - grid.updateRow({ - ProductID: 1, ProductName: 'Spearmint', InStock: true, UnitsInStock: 1, OrderDate: new Date('2005-03-21') - }, 1); - fixture.detectChanges(); - - expect(+countValue).toBe(grid.rowList.length); - expect(productNameCell.value).toBe('Spearmint'); - expect(unitsInStockCell.value).toBe(1); }); - it('should recalculate summary functions on cell update', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - const oldMaxValue = '20,000'; - const newMaxValue = '99,000'; - const grid = fixture.componentInstance.grid1; - const summariesUnitOfStock = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS))[3]; - const unitsInStockCell = grid.getCellByColumn(0, 'UnitsInStock'); + describe('Integration Scenarious: ', () => { + describe('', () => { + let fix; + let grid: IgxGridComponent; + beforeEach(() => { + fix = TestBed.createComponent(SummaryColumnComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + }); + + it('Filtering: should calculate summaries only over filteredData', () => { + grid.filter('UnitsInStock', 0, IgxNumberFilteringOperand.instance().condition('equals'), true); + fix.detectChanges(); + + let filterResult = grid.rowList.length; + expect(filterResult).toEqual(3); + + let summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['3']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['3']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['3', '0', '0', '0', '0']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Earliest', 'Latest'], ['3', 'Jul 27, 2001', 'Oct 11, 2007']); + + grid.filter('ProductID', 0, IgxNumberFilteringOperand.instance().condition('equals'), true); + fix.detectChanges(); + + filterResult = grid.rowList.length; + expect(filterResult).toEqual(0); + + summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['0']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['0']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['0', '', '', '', '']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Earliest', 'Latest'], ['0', '', '']); + + grid.clearFilter(); + fix.detectChanges(); + + filterResult = grid.rowList.length; + expect(filterResult).toEqual(10); + + summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '20,000', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['10', 'May 17, 1990', 'Dec 25, 2025']); + }); + + it('Moving: should move summaries when move colomn', () => { + const colUnitsInStock = grid.getColumnByName('UnitsInStock'); + const colProductID = grid.getColumnByName('ProductID'); + colUnitsInStock.movable = true; + fix.detectChanges(); + + grid.moveColumn(colUnitsInStock, colProductID); + fix.detectChanges(); + + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '20,000', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 1, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['10', 'May 17, 1990', 'Dec 25, 2025']); + }); + + it('Hiding: should hide summary row when a column which has summary is hidded', fakeAsync(() => { + grid.getColumnByName('ProductName').hasSummary = false; + grid.getColumnByName('InStock').hasSummary = false; + grid.getColumnByName('OrderDate').hasSummary = false; + // grid.recalculateSummaries(); + fix.detectChanges(); + tick(100); + + let summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 2, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '20,000', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, [], []); + + grid.getColumnByName('UnitsInStock').hidden = true; + tick(); + fix.detectChanges(); + + + let summaryArea = fix.debugElement.query(By.css('.igx-grid__summaries')); + expect(summaryArea).toBeNull(); + expect(grid.hasSummarizedColumns).toBe(false); + + grid.getColumnByName('UnitsInStock').hidden = false; + tick(); + fix.detectChanges(); + summaryArea = fix.debugElement.query(By.css('.igx-grid__summaries')); + expect(summaryArea).toBeDefined(); + expect(grid.hasSummarizedColumns).toBe(true); + summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 2, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '20,000', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, [], []); + })); + + it('Hiding: should recalculate summary area after column with enabled summary is hidden', fakeAsync(() => { + let tFoot = fix.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; + expect(tFoot).toEqual(5 * grid.defaultRowHeight + 1); + + grid.getColumnByName('UnitsInStock').hidden = true; + tick(); + fix.detectChanges(); + + tFoot = fix.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; + expect(tFoot).toEqual(3 * grid.defaultRowHeight + 1); + expect(grid.hasSummarizedColumns).toBe(true); + + let summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Earliest', 'Latest'], ['10', 'May 17, 1990', 'Dec 25, 2025']); + + grid.getColumnByName('UnitsInStock').hidden = false; + tick(); + fix.detectChanges(); + + expect(grid.hasSummarizedColumns).toBe(true); + tFoot = fix.debugElement.query(By.css('.igx-grid__tfoot')).nativeElement.getBoundingClientRect().height; + expect(tFoot).toEqual(5 * grid.defaultRowHeight + 1); + summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '20,000', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['10', 'May 17, 1990', 'Dec 25, 2025']); + })); + + it('CRUD: should recalculate summary functions onRowAdded', () => { + grid.addRow({ + ProductID: 11, ProductName: 'Belgian Chocolate', InStock: true, UnitsInStock: 99000, OrderDate: new Date('2018-03-01') + }); + fix.detectChanges(); + + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['11']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['11']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['11', '0', '99,000', '138,004', '12,545.818']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['11', 'May 17, 1990', 'Dec 25, 2025']); + }); + + it('CRUD: should recalculate summary functions onRowDeleted', () => { + grid.deleteRow(9); + fix.detectChanges(); + + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['9']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['9']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['9', '0', '20,000', '32,006', '3,556.222']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['9', 'May 17, 1990', 'Mar 1, 2018']); + }); + + it('CRUD: should recalculate summary functions on updateRow', () => { + const productNameCell = grid.getCellByColumn(0, 'ProductName'); + const unitsInStockCell = grid.getCellByColumn(0, 'UnitsInStock'); + + expect(productNameCell.value).toBe('Chai'); + expect(unitsInStockCell.value).toBe(2760); + + grid.updateRow({ + ProductID: 1, ProductName: 'Spearmint', InStock: true, UnitsInStock: 510000, OrderDate: new Date('1984-03-21') + }, 1); + fix.detectChanges(); + + expect(productNameCell.value).toBe('Spearmint'); + expect(unitsInStockCell.value).toBe(510000); + + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '510,000', '546,244', '54,624.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['10', 'Mar 21, 1984', 'Dec 25, 2025']); + }); + + it('CRUD: should recalculate summary functions on cell update', () => { + const unitsInStockCell = grid.getCellByColumn(0, 'UnitsInStock'); + unitsInStockCell.update(99000); + fix.detectChanges(); + + let summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '99,000', '135,244', '13,524.4']); + + unitsInStockCell.update(-12); + fix.detectChanges(); + summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '-12', '20,000', '36,232', '3,623.2']); + }); + + it('Pinning: should display all active summaries after column pinning', () => { + grid.pinColumn('UnitsInStock'); + grid.pinColumn('ProductID'); + fix.detectChanges(); + + const summaryRow = fix.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, + ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['10', '0', '20,000', '39,004', '3,900.4']); + HelperUtils.verifyColumnSummaries(summaryRow, 1, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count'], ['10']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, + ['Count', 'Earliest', 'Latest'], ['10', 'May 17, 1990', 'Dec 25, 2025']); + }); + }); - let maxValue = summariesUnitOfStock.query(By.css('[title=\'Max\']')).nativeElement.nextSibling.innerText; - expect(maxValue).toBe(oldMaxValue); - unitsInStockCell.update(99000); - fixture.detectChanges(); + it('MCH - verify summaries when there are grouped columns', (async () => { + const fixture = TestBed.createComponent(ColumnGroupFourLevelTestComponent); + fixture.detectChanges(); + const grid = fixture.componentInstance.grid; - maxValue = summariesUnitOfStock.query(By.css('[title=\'Max\']')).nativeElement.nextSibling.innerText; - expect(maxValue).toBe(newMaxValue); + // Verify columns and groups + expect(document.querySelectorAll('igx-grid-header-group').length).toEqual(18); + expect(document.querySelectorAll('.igx-grid__th').length).toEqual(11); + const allColumns = grid.columnList; + allColumns.forEach((col) => { + if (!col.columnGroup) { + col.hasSummary = true; + } + }); + await wait(); + fixture.detectChanges(); + + const summaryRow = fixture.debugElement.query(By.css(SUMMARY_ROW)); + HelperUtils.verifyColumnSummaries(summaryRow, 0, ['Count'], ['27']); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['27']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['27']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count'], ['27']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['27']); + HelperUtils.verifyColumnSummaries(summaryRow, 5, ['Count'], ['27']); + HelperUtils.verifyColumnSummaries(summaryRow, 6, ['Count'], ['27']); + })); }); - it('should display all active summaries after column pinning', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - const grid = fixture.componentInstance.grid1; - const summariedColumns = grid.columnList.filter((col) => col.hasSummary === true).length; - let displayedSummaries = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS)) - .filter((summary) => summary.children.length > 0).length; - expect(displayedSummaries).toBe(summariedColumns); - - grid.pinColumn('UnitsInStock'); - grid.pinColumn('ProductID'); - fixture.detectChanges(); - - displayedSummaries = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS)).filter((summary) => summary.children.length > 0).length; - expect(displayedSummaries).toBe(summariedColumns); + describe('Grouping tests: ', () => { + let fix; + let grid; + beforeEach(() => { + fix = TestBed.createComponent(SummarieGroupByComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + grid.groupBy({ + fieldName: 'ParentID', dir: SortingDirection.Asc, ignoreCase: false + }); - }); - it('should calc tfoot height according number of summary functions', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - const grid = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - const footerRow = fixture.debugElement.query(By.css('.igx-grid__tfoot')).query(By.css('.igx-grid__summaries')) - .nativeElement.getBoundingClientRect().height; - const tfootSize = +footerRow; - - const expectedHeight = GridFunctions.calcMaxSummaryHeight(grid.columnList, summaries, grid.defaultRowHeight); - - expect(tfootSize).toBe(expectedHeight); - }); - it('should calculate summaries for \'number\' dataType or return if no data is provided', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summaryClass = fixture.componentInstance.numberSummary; - - const summaries = summaryClass.operate(fixture.componentInstance.data.map((x) => x['UnitsInStock'])); - expect(summaries[0].summaryResult).toBe(10); - expect(summaries[1].summaryResult).toBe(0); - expect(summaries[2].summaryResult).toBe(20000); - expect(summaries[3].summaryResult).toBe(39004); - expect(summaries[4].summaryResult).toBe(3900.4); - - const emptySummaries = summaryClass.operate(); - expect(emptySummaries[0].summaryResult).toBe(0); - expect(typeof emptySummaries[1].summaryResult).not.toEqual(undefined); - expect(typeof emptySummaries[2].summaryResult).not.toEqual(undefined); - expect(typeof emptySummaries[3].summaryResult).not.toEqual(undefined); - expect(typeof emptySummaries[4].summaryResult).not.toEqual(undefined); - - expect(typeof emptySummaries[1].summaryResult).not.toEqual(null); - expect(typeof emptySummaries[2].summaryResult).not.toEqual(null); - expect(typeof emptySummaries[3].summaryResult).not.toEqual(null); - expect(typeof emptySummaries[4].summaryResult).not.toEqual(null); - - expect(emptySummaries[1].summaryResult.length === 0).toBeTruthy(); - expect(emptySummaries[2].summaryResult.length === 0).toBeTruthy(); - expect(emptySummaries[3].summaryResult.length === 0).toBeTruthy(); - expect(emptySummaries[4].summaryResult.length === 0).toBeTruthy(); - }); - it('should calculate summaries for \'date\' dataType or return if no data is provided', () => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summaryClass = fixture.componentInstance.dateSummary; - - const summaries = summaryClass.operate(fixture.componentInstance.data.map((x) => x['OrderDate'])); - expect(summaries[0].summaryResult).toBe(10); - expect(summaries[1].summaryResult.toLocaleDateString()).toBe('5/17/1990'); - expect(summaries[2].summaryResult.toLocaleDateString()).toBe('12/25/2025'); - - const emptySummaries = summaryClass.operate([]); - expect(emptySummaries[0].summaryResult).toBe(0); - expect(emptySummaries[1].summaryResult).toBe(undefined); - expect(emptySummaries[2].summaryResult).toBe(undefined); - }); - it('should calculate summaries only over filteredData', fakeAsync(() => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')); - const colChips = GridFunctions.getFilterChipsForColumn('UnitsInStock', fixture); - - colChips[0].nativeElement.click(); - fixture.detectChanges(); - - GridFunctions.filterBy('Equals', '0', fixture); - fixture.detectChanges(); - - GridFunctions.closeFilterRow(fixture); - fixture.detectChanges(); - - const filterResult = grid.rowList.length; - expect(filterResult).toEqual(3); - let index = 0; - grid.columnList.forEach((col) => { - if (col.hasSummary) { - const values = summaries[index].queryAll(By.css(SUMMARY_VALUE_CLASS)); - expect(+values[0].nativeElement.innerText).toBe(filterResult); - if (col.field === 'UnitsInStock') { - expect(values[1].nativeElement.innerText).toBe('0'); - expect(values[2].nativeElement.innerText).toBe('0'); - } - } - index++; + fix.detectChanges(); }); - })); - - it('When we have data which is undefined and enable summary per defined column, error should not be thrown', () => { - const fix = TestBed.createComponent(NoActiveSummariesComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.grid1; - const idColumn = grid.getColumnByName('ProductID'); - expect(grid.data.length > 0).toEqual(true); - - fix.componentInstance.data = undefined; - fix.detectChanges(); - - expect(grid.data).toEqual(undefined); - expect(() => { - grid.enableSummaries(idColumn.field); + it('should render correct summaries when there is grouped colomn', () => { + verifyBaseSummaries(fix); + verifySummariesForParentID17(fix, 3); + const groupRows = grid.groupsRowList.toArray(); + groupRows[0].toggle(); fix.detectChanges(); - }).not.toThrow(); - }); - - it('should render correct data after hiding all summaries when scrolled to the bottom', (async () => { - const fixture = TestBed.createComponent(VirtualSummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summariedColumns = ['ProductName', 'InStock', 'UnitsInStock', 'OrderDate']; - let rowsRendered; - let tbody; - let expectedRowLenght; - - fixture.componentInstance.scrollTop(10000); - await wait(200); - fixture.detectChanges(); - - rowsRendered = fixture.nativeElement.querySelectorAll('igx-grid-row'); - tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; - expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; - - expect(rowsRendered.length).toEqual(expectedRowLenght); - grid.disableSummaries(summariedColumns); - fixture.detectChanges(); - await wait(50); - fixture.detectChanges(); - - rowsRendered = Array.from(fixture.nativeElement.querySelectorAll('igx-grid-row')); - tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; - expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; - - fixture.detectChanges(); - const firstCells = rowsRendered.map((item) => { - return item.querySelectorAll('igx-grid-cell')[0]; + verifyBaseSummaries(fix); + verifySummariesForParentID19(fix, 3); }); - expect(rowsRendered.length).toEqual(expectedRowLenght); - const expectedFirstCellNum = grid.data.length - expectedRowLenght + 1; - for (let i = 0; i < rowsRendered.length - 1; i++) { - expect(firstCells[i].textContent.trim()).toEqual((expectedFirstCellNum + i).toString()); - } - })); - - it('should render correct data after hiding one bigger and then one smaller summary when scrolled to the bottom', (async () => { - const fixture = TestBed.createComponent(VirtualSummaryColumnComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid1; - const summariedColumns = ['ProductName', 'InStock', 'UnitsInStock', 'OrderDate']; - let rowsRendered; - let tbody; - let expectedRowLenght; - let firstCellsText; - - fixture.componentInstance.scrollTop(10000); - await wait(100); - fixture.detectChanges(); - - rowsRendered = fixture.nativeElement.querySelectorAll('igx-grid-row'); - tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; - expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; - expect(rowsRendered.length).toEqual(expectedRowLenght); + it('should be able to enable/disable summaries at runtime', () => { + grid.getColumnByName('Age').hasSummary = false; + grid.getColumnByName('ParentID').hasSummary = false; + fix.detectChanges(); - grid.disableSummaries(['ProductName', 'InStock', 'UnitsInStock']); - await wait(50); - fixture.detectChanges(); + HelperUtils.verifyVisbleSummariesHeight(fix, 3, grid.defaultRowHeight ); + + let summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, [], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 3, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + HelperUtils.verifyColumnSummaries(summary, 5, ['Count'], []); + }); + + // Disable all summaries + grid.getColumnByName('Name').hasSummary = false; + grid.getColumnByName('HireDate').hasSummary = false; + grid.getColumnByName('OnPTO').hasSummary = false; + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(0); - rowsRendered = Array.from(fixture.nativeElement.querySelectorAll('igx-grid-row')); - tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; - expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; + grid.getColumnByName('Name').hasSummary = true; + fix.detectChanges(); - firstCellsText = rowsRendered.map((item) => { - return item.querySelectorAll('igx-grid-cell')[0].textContent.trim(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + HelperUtils.verifyVisbleSummariesHeight(fix, 1, grid.defaultRowHeight ); + summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, [], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 3, [], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + HelperUtils.verifyColumnSummaries(summary, 5, [], []); + }); }); - expect(rowsRendered.length).toEqual(expectedRowLenght); - let expectedFirstCellNum = grid.data.length - expectedRowLenght + 1; - for (let i = 0; i < rowsRendered.length - 1; i++) { - expect(firstCellsText[i]).toEqual((expectedFirstCellNum + i).toString()); - } - - grid.disableSummaries(['OrderDate']); - await wait(50); - fixture.detectChanges(); - - rowsRendered = Array.from(fixture.nativeElement.querySelectorAll('igx-grid-row')); - tbody = grid.nativeElement.querySelector('.igx-grid__tbody').getBoundingClientRect().height; - expectedRowLenght = Math.ceil(parseFloat(tbody) / grid.defaultRowHeight) + 1; + it('should show/hide summaries when expand/collapse group row', () => { + grid.disableSummaries([{ fieldName: 'Age' }, { fieldName: 'ParentID' }, { fieldName: 'HireDate' }]); + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); - firstCellsText = rowsRendered.map((item) => { - return item.querySelectorAll('igx-grid-cell')[0].textContent.trim(); - }); - expect(rowsRendered.length).toEqual(expectedRowLenght); - expectedFirstCellNum = grid.data.length - expectedRowLenght + 1; - for (let i = 0; i < rowsRendered.length - 1; i++) { - expect(firstCellsText[i]).toEqual((expectedFirstCellNum + i).toString()); - } - })); + const groupRows = grid.groupsRowList.toArray(); + groupRows[0].toggle(); + fix.detectChanges(); - it(`Should update summary section when the column is outside of the - viewport and have identical width with others`, (async () => { - const fix = TestBed.createComponent(SummaryColumnsWithIdenticalWidthsComponent); - fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(4); - const grid = fix.componentInstance.grid1; - const gridApi = (grid as any)._gridAPI; - let summaries = gridApi.get_summaries(grid.id); + grid.toggleAllGroupRows(); + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); - let getCountResSummary = summaries.get('UnitsInStock').find((k) => k.key === 'count').summaryResult; - expect(getCountResSummary).toEqual(fix.componentInstance.data.length); + groupRows[0].toggle(); + fix.detectChanges(); - grid.addRow({ - ProductID: 11, ProductName: 'Belgian Chocolate', InStock: true, UnitsInStock: 99000, OrderDate: new Date('2018-03-01') - }); - fix.detectChanges(); - GridFunctions.scrollLeft(grid, 400); - await wait(100); - fix.detectChanges(); - - summaries = gridApi.get_summaries(grid.id); - getCountResSummary = summaries.get('UnitsInStock').find((k) => k.key === 'count').summaryResult; - expect(getCountResSummary).toEqual(fix.componentInstance.data.length); - })); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); - it('should be able to change \'hasSummary\' property runtime and to recalculate grid sizes correctly', fakeAsync(() => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - const grid = fixture.componentInstance.grid1; - grid.columnList.forEach((col) => { - if (col.field !== 'ProductID') { - expect(grid.getColumnByName(col.field).hasSummary).toBe(true); - } + grid.toggleAllGroupRows(); + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); }); - grid.getColumnByName('UnitsInStock').hasSummary = false; - grid.recalculateSummaries(); - tick(100); - fixture.detectChanges(); - expect(grid.getColumnByName('UnitsInStock').hasSummary).toBe(false); - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')).filter((el) => - el.nativeElement.classList.contains('igx-grid-summary--empty') === false); - const tfootSize = +fixture.debugElement.query(By.css('.igx-grid__summaries')) - .nativeElement.getBoundingClientRect().height; - const expectedHeight = GridFunctions.calcMaxSummaryHeight(grid.columnList, summaries, grid.defaultRowHeight); - expect(tfootSize).toBe(expectedHeight); - - grid.getColumnByName('ProductName').hasSummary = false; - grid.getColumnByName('InStock').hasSummary = false; - fixture.componentInstance.hasSummary = false; - grid.recalculateSummaries(); - fixture.detectChanges(); - tick(100); - expect(fixture.debugElement.query(By.css('.igx-grid__summaries'))).toBeNull(); - expect(grid.hasSummarizedColumns).toBe(false); - })); - it('should recalculate summary area after column with enabled summary is hidden', fakeAsync(() => { - const fixture = TestBed.createComponent(SummaryColumnComponent); - fixture.detectChanges(); - const grid = fixture.componentInstance.grid1; - - grid.getColumnByName('ProductName').hasSummary = false; - grid.getColumnByName('InStock').hasSummary = false; - fixture.componentInstance.hasSummary = false; - grid.recalculateSummaries(); - fixture.detectChanges(); - tick(100); - const summaries = fixture.debugElement.queryAll(By.css('igx-grid-summary')).filter((el) => - el.nativeElement.classList.contains('igx-grid-summary--empty') === false); - const tfootSize = +fixture.debugElement.query(By.css('.igx-grid__summaries')) - .nativeElement.getBoundingClientRect().height; - const expectedHeight = GridFunctions.calcMaxSummaryHeight(grid.columnList, summaries, grid.defaultRowHeight); - expect(tfootSize).toBe(expectedHeight); - expect(grid.hasSummarizedColumns).toBe(true); - - grid.getColumnByName('UnitsInStock').hidden = true; - tick(); - fixture.detectChanges(); - - let summaryArea = fixture.debugElement.query(By.css('.igx-grid__summaries')); - expect(summaryArea).toBeNull(); - expect(grid.hasSummarizedColumns).toBe(false); - - grid.enableSummaries('InStock'); - fixture.detectChanges(); - summaryArea = fixture.debugElement.query(By.css('.igx-grid__summaries')); - expect(grid.hasSummarizedColumns).toBe(true); - expect(summaryArea).toBeDefined(); - - grid.disableSummaries('InStock'); - fixture.detectChanges(); - summaryArea = fixture.debugElement.query(By.css('.igx-grid__summaries')); - expect(grid.hasSummarizedColumns).toBe(false); - expect(summaryArea).toBeNull(); - - grid.getColumnByName('UnitsInStock').hidden = false; - tick(); - fixture.detectChanges(); - summaryArea = fixture.debugElement.query(By.css('.igx-grid__summaries')); - expect(summaryArea).toBeDefined(); - expect(grid.hasSummarizedColumns).toBe(true); - })); + it('should be able to enable/disable summaries at runtime', () => { + grid.getColumnByName('Age').hasSummary = false; + grid.getColumnByName('ParentID').hasSummary = false; + fix.detectChanges(); - it('should properly render custom summaries', fakeAsync(() => { - const fixture = TestBed.createComponent(CustomSummariesComponent); - fixture.detectChanges(); + HelperUtils.verifyVisbleSummariesHeight(fix, 3, grid.defaultRowHeight ); + + let summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, [], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 3, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + HelperUtils.verifyColumnSummaries(summary, 5, ['Count'], []); + }); + + // Disable all summaries + grid.getColumnByName('Name').hasSummary = false; + grid.getColumnByName('HireDate').hasSummary = false; + grid.getColumnByName('OnPTO').hasSummary = false; + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(0); - const grid = fixture.componentInstance.grid1; - const summariesUnitOfStock = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS))[3]; - const summariesOrderDate = fixture.debugElement.queryAll(By.css(SUMMARY_CLASS))[4]; + grid.getColumnByName('Name').hasSummary = true; + fix.detectChanges(); - const maxValue = summariesUnitOfStock.query(By.css('[title=\'Sum\']')).nativeElement.nextSibling.innerText; - const earliest = summariesOrderDate.query(By.css('[title=\'Earliest\']')).nativeElement.nextSibling.innerText; - expect(earliest).toBe('5/17/1990'); - expect(maxValue).toBe('39,004'); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + HelperUtils.verifyVisbleSummariesHeight(fix, 1, grid.defaultRowHeight ); + summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, [], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 3, [], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + HelperUtils.verifyColumnSummaries(summary, 5, [], []); + }); + }); - const colChips = GridFunctions.getFilterChipsForColumn('UnitsInStock', fixture); - colChips[0].nativeElement.click(); - fixture.detectChanges(); - GridFunctions.filterBy('Less Than', '0', fixture); - GridFunctions.closeFilterRow(fixture); + }); - const filterResult = grid.rowList.length; - expect(filterResult).toEqual(0); + function verifyBaseSummaries(fixture) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['8', '17', '847', '2,188', '273.5']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['8']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Earliest', 'Latest'], ['8', 'Dec 18, 2007', 'Dec 9, 2017']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['8', '25', '50', '293', '36.625']); + HelperUtils.verifyColumnSummaries(summaryRow, 5, ['Count'], ['8']); + } - const countValue = summariesUnitOfStock.query(By.css('[title=\'Count\']')).nativeElement.nextSibling.innerText; - expect(countValue).toBe('0'); - })); + function verifySummariesForParentID19(fixture, vissibleIndex) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, vissibleIndex); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '19', '19', '19', '19']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Earliest', 'Latest'], ['1', 'May 4, 2014', 'May 4, 2014']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '44', '44', '44', '44']); + HelperUtils.verifyColumnSummaries(summaryRow, 5, ['Count'], ['1']); + } + function verifySummariesForParentID17(fixture, vissibleIndex) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, vissibleIndex); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '17', '17', '34', '17']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Earliest', 'Latest'], ['2', 'Dec 18, 2007', 'Mar 19, 2016']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '27', '50', '77', '38.5']); + HelperUtils.verifyColumnSummaries(summaryRow, 5, ['Count'], ['2']); + } }); @Component({ @@ -654,152 +861,12 @@ describe('IgxGrid - Summaries', () => { ` }) -export class SummaryColumnsWithIdenticalWidthsComponent { - - @ViewChild('grid1', { read: IgxGridComponent }) - public grid1: IgxGridComponent; - - public data = [ - { ProductID: 1, ProductName: 'Chai', InStock: true, UnitsInStock: 2760, OrderDate: '2005-03-21' }, - { ProductID: 2, ProductName: 'Aniseed Syrup', InStock: false, UnitsInStock: 198, OrderDate: '2008-01-15' }, - { ProductID: 3, ProductName: 'Chef Antons Cajun Seasoning', InStock: true, UnitsInStock: 52, OrderDate: '2010-11-20' }, - { ProductID: 4, ProductName: 'Grandmas Boysenberry Spread', InStock: false, UnitsInStock: 0, OrderDate: '2007-10-11' }, - { ProductID: 5, ProductName: 'Uncle Bobs Dried Pears', InStock: false, UnitsInStock: 0, OrderDate: '2001-07-27' }, - { ProductID: 6, ProductName: 'Northwoods Cranberry Sauce', InStock: true, UnitsInStock: 1098, OrderDate: '1990-05-17' }, - { ProductID: 7, ProductName: 'Queso Cabrales', InStock: false, UnitsInStock: 0, OrderDate: '2005-03-03' }, - { ProductID: 8, ProductName: 'Tofu', InStock: true, UnitsInStock: 7898, OrderDate: '2017-09-09' }, - { ProductID: 9, ProductName: 'Teatime Chocolate Biscuits', InStock: true, UnitsInStock: 6998, OrderDate: '2025-12-25' }, - { ProductID: 10, ProductName: 'Chocolate', InStock: true, UnitsInStock: 20000, OrderDate: '2018-03-01' } - ]; -} - - -@Component({ - template: ` - - - - - - - - - - - - - ` -}) -export class NoActiveSummariesComponent { +export class SummaryColumnsWithIdenticalWidthsComponent { @ViewChild('grid1', { read: IgxGridComponent }) public grid1: IgxGridComponent; - public data = [ - { ProductID: 1, ProductName: 'Chai', InStock: true, UnitsInStock: 2760, OrderDate: '2005-03-21' }, - { ProductID: 2, ProductName: 'Aniseed Syrup', InStock: false, UnitsInStock: 198, OrderDate: '2008-01-15' }, - { ProductID: 3, ProductName: 'Chef Antons Cajun Seasoning', InStock: true, UnitsInStock: 52, OrderDate: '2010-11-20' }, - { ProductID: 4, ProductName: 'Grandmas Boysenberry Spread', InStock: false, UnitsInStock: 0, OrderDate: '2007-10-11' }, - { ProductID: 5, ProductName: 'Uncle Bobs Dried Pears', InStock: false, UnitsInStock: 0, OrderDate: '2001-07-27' }, - { ProductID: 6, ProductName: 'Northwoods Cranberry Sauce', InStock: true, UnitsInStock: 1098, OrderDate: '1990-05-17' }, - { ProductID: 7, ProductName: 'Queso Cabrales', InStock: false, UnitsInStock: 0, OrderDate: '2005-03-03' }, - { ProductID: 8, ProductName: 'Tofu', InStock: true, UnitsInStock: 7898, OrderDate: '2017-09-09' }, - { ProductID: 9, ProductName: 'Teatime Chocolate Biscuits', InStock: true, UnitsInStock: 6998, OrderDate: '2025-12-25' }, - { ProductID: 10, ProductName: 'Chocolate', InStock: true, UnitsInStock: 20000, OrderDate: '2018-03-01' } - ]; -} - -@Component({ - template: ` - - - - - - - - - - - - - ` -}) -export class SummaryColumnComponent { - - public data = [ - { ProductID: 1, ProductName: 'Chai', InStock: true, UnitsInStock: 2760, OrderDate: new Date('2005-03-21') }, - { ProductID: 2, ProductName: 'Aniseed Syrup', InStock: false, UnitsInStock: 198, OrderDate: new Date('2008-01-15') }, - { ProductID: 3, ProductName: 'Chef Antons Cajun Seasoning', InStock: true, UnitsInStock: 52, OrderDate: new Date('2010-11-20') }, - { ProductID: 4, ProductName: 'Grandmas Boysenberry Spread', InStock: false, UnitsInStock: 0, OrderDate: new Date('2007-10-11') }, - { ProductID: 5, ProductName: 'Uncle Bobs Dried Pears', InStock: false, UnitsInStock: 0, OrderDate: new Date('2001-07-27') }, - { ProductID: 6, ProductName: 'Northwoods Cranberry Sauce', InStock: true, UnitsInStock: 1098, OrderDate: new Date('1990-05-17') }, - { ProductID: 7, ProductName: 'Queso Cabrales', InStock: false, UnitsInStock: 0, OrderDate: new Date('2005-03-03') }, - { ProductID: 8, ProductName: 'Tofu', InStock: true, UnitsInStock: 7898, OrderDate: new Date('2017-09-09') }, - { ProductID: 9, ProductName: 'Teatime Chocolate Biscuits', InStock: true, UnitsInStock: 6998, OrderDate: new Date('2025-12-25') }, - { ProductID: 10, ProductName: 'Chocolate', InStock: true, UnitsInStock: 20000, OrderDate: new Date('2018-03-01') } - ]; - @ViewChild('grid1', { read: IgxGridComponent }) - public grid1: IgxGridComponent; - public hasSummary = true; - - public numberSummary = new IgxNumberSummaryOperand(); - public dateSummary = new IgxDateSummaryOperand(); -} - -@Component({ - template: ` - - - - - - - - - - - - - ` -}) -export class VirtualSummaryColumnComponent { - - public data = [ - { ProductID: 1, ProductName: 'Chai', InStock: true, UnitsInStock: 2760, OrderDate: new Date('2005-03-21') }, - { ProductID: 2, ProductName: 'Aniseed Syrup', InStock: false, UnitsInStock: 198, OrderDate: new Date('2008-01-15') }, - { ProductID: 3, ProductName: 'Chef Antons Cajun Seasoning', InStock: true, UnitsInStock: 52, OrderDate: new Date('2010-11-20') }, - { ProductID: 4, ProductName: 'Grandmas Boysenberry Spread', InStock: false, UnitsInStock: 0, OrderDate: new Date('2007-10-11') }, - { ProductID: 5, ProductName: 'Uncle Bobs Dried Pears', InStock: false, UnitsInStock: 0, OrderDate: new Date('2001-07-27') }, - { ProductID: 6, ProductName: 'Northwoods Cranberry Sauce', InStock: true, UnitsInStock: 1098, OrderDate: new Date('1990-05-17') }, - { ProductID: 7, ProductName: 'Queso Cabrales', InStock: false, UnitsInStock: 0, OrderDate: new Date('2005-03-03') }, - { ProductID: 8, ProductName: 'Tofu', InStock: true, UnitsInStock: 7898, OrderDate: new Date('2017-09-09') }, - { ProductID: 9, ProductName: 'Teatime Chocolate Biscuits', InStock: true, UnitsInStock: 6998, OrderDate: new Date('2025-12-25') }, - { ProductID: 10, ProductName: 'Pie', InStock: true, UnitsInStock: 1000, OrderDate: new Date('2017-05-07') }, - { ProductID: 11, ProductName: 'Pasta', InStock: false, UnitsInStock: 198, OrderDate: new Date('2001-02-15') }, - { ProductID: 12, ProductName: 'Krusty krab\'s burger', InStock: true, UnitsInStock: 52, OrderDate: new Date('2012-09-25') }, - { ProductID: 13, ProductName: 'Lasagna', InStock: false, UnitsInStock: 0, OrderDate: new Date('2015-02-09') }, - { ProductID: 14, ProductName: 'Uncle Bobs Dried Pears', InStock: false, UnitsInStock: 0, OrderDate: new Date('2008-03-17') }, - { ProductID: 15, ProductName: 'Cheese', InStock: true, UnitsInStock: 1098, OrderDate: new Date('1990-11-27') }, - { ProductID: 16, ProductName: 'Devil\'s Hot Chilli Sauce', InStock: false, UnitsInStock: 0, OrderDate: new Date('2012-08-14') }, - { ProductID: 17, ProductName: 'Parmesan', InStock: true, UnitsInStock: 4898, OrderDate: new Date('2017-09-09') }, - { ProductID: 18, ProductName: 'Steaks', InStock: true, UnitsInStock: 3098, OrderDate: new Date('2025-12-25') }, - { ProductID: 19, ProductName: 'Biscuits', InStock: true, UnitsInStock: 10570, OrderDate: new Date('2018-03-01') } - ]; - - @ViewChild('grid1', { read: IgxGridComponent }) - public grid1: IgxGridComponent; - - public width = '800px'; - public height = '600px'; - - public numberSummary = new IgxNumberSummaryOperand(); - public dateSummary = new IgxDateSummaryOperand(); - - public scrollTop(newTop: number) { - const vScrollbar = this.grid1.verticalScrollContainer.getVerticalScroll(); - vScrollbar.scrollTop = newTop; - } + public data = SampleTestData.foodProductData(); } class DealsSummary extends IgxNumberSummaryOperand { @@ -813,7 +880,7 @@ class DealsSummary extends IgxNumberSummaryOperand { const summaryResult = obj.summaryResult; // apply formatting to float numbers if (Number(summaryResult) === summaryResult) { - obj.summaryResult = summaryResult.toLocaleString('en-us', {maximumFractionDigits: 2}); + obj.summaryResult = summaryResult.toLocaleString('en-us', { maximumFractionDigits: 2 }); } return obj; } @@ -822,6 +889,23 @@ class DealsSummary extends IgxNumberSummaryOperand { } } +class DealsSummaryMinMax extends IgxNumberSummaryOperand { + constructor() { + super(); + } + + public operate(summaries?: any[]): IgxSummaryResult[] { + const result = super.operate(summaries); + result.push({ + key: 'test', + label: 'Test', + summaryResult: summaries.filter(rec => rec > 10 && rec < 30).length + }); + + return result; + } +} + class EarliestSummary extends IgxDateSummaryOperand { constructor() { super(); @@ -855,22 +939,12 @@ class EarliestSummary extends IgxDateSummaryOperand { ` }) -export class CustomSummariesComponent { - public data = [ - { ProductID: 1, ProductName: 'Chai', InStock: true, UnitsInStock: 2760, OrderDate: new Date('2005-03-21') }, - { ProductID: 2, ProductName: 'Aniseed Syrup', InStock: false, UnitsInStock: 198, OrderDate: new Date('2008-01-15') }, - { ProductID: 3, ProductName: 'Chef Antons Cajun Seasoning', InStock: true, UnitsInStock: 52, OrderDate: new Date('2010-11-20') }, - { ProductID: 4, ProductName: 'Grandmas Boysenberry Spread', InStock: false, UnitsInStock: 0, OrderDate: new Date('2007-10-11') }, - { ProductID: 5, ProductName: 'Uncle Bobs Dried Pears', InStock: false, UnitsInStock: 0, OrderDate: new Date('2001-07-27') }, - { ProductID: 6, ProductName: 'Northwoods Cranberry Sauce', InStock: true, UnitsInStock: 1098, OrderDate: new Date('1990-05-17') }, - { ProductID: 7, ProductName: 'Queso Cabrales', InStock: false, UnitsInStock: 0, OrderDate: new Date('2005-03-03') }, - { ProductID: 8, ProductName: 'Tofu', InStock: true, UnitsInStock: 7898, OrderDate: new Date('2017-09-09') }, - { ProductID: 9, ProductName: 'Teatime Chocolate Biscuits', InStock: true, UnitsInStock: 6998, OrderDate: new Date('2025-12-25') }, - { ProductID: 10, ProductName: 'Chocolate', InStock: true, UnitsInStock: 20000, OrderDate: new Date('2018-03-01') } - ]; +export class CustomSummariesComponent { + public data = SampleTestData.foodProductData(); @ViewChild('grid1', { read: IgxGridComponent }) public grid1: IgxGridComponent; public dealsSummary = DealsSummary; + public dealsSummaryMinMax = DealsSummaryMinMax; public earliest = EarliestSummary; } diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index 296827b24f6..3a65273b1c7 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -74,7 +74,8 @@ | gridSort:sortingExpressions:pipeTrigger | gridPreGroupBy:groupingExpressions:groupingExpansionState:groupsExpanded:id:pipeTrigger | gridPaging:page:perPage:id:pipeTrigger - | gridPostGroupBy:groupingExpressions:groupingExpansionState:groupsExpanded:id:groupsRecords:pipeTrigger" + | gridPostGroupBy:groupingExpressions:groupingExpansionState:groupsExpanded:id:groupsRecords:pipeTrigger + | gridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:summaryPipeTrigger" let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForContainerSize]='calcHeight' [igxForItemSize]="rowHeight" #verticalScrollContainer (onChunkPreload)="dataLoading($event)"> @@ -85,7 +86,11 @@ - + + + + + @@ -94,24 +99,8 @@
-
- -
-
- -
-
- - - - - - -
+ +
@@ -145,13 +134,16 @@
+ You have {{ rowChangesCount }} changes in this row + +
diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts index 8b9fb8df079..6138ce8ba4f 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts @@ -2247,7 +2247,7 @@ describe('IgxGrid Component Tests', () => { grid.recalculateSummaries(); // get the summaries for a particular column - const summaries = targetCell.gridAPI.get_summaries(targetCell.gridID); + const summaries = targetCell.gridAPI.get_summary_data(targetCell.gridID); const earliestDate = summaries.get('OrderDate')[1].summaryResult.toLocaleDateString(); expect(earliestDate).toEqual(newDate); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index 8ccb1f33af6..ece859db917 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -25,6 +25,7 @@ import { takeUntil } from 'rxjs/operators'; import { IgxFilteringService } from '../filtering/grid-filtering.service'; import { IGroupingExpression } from '../../data-operations/grouping-expression.interface'; import { IgxColumnResizingService } from '../grid-column-resizing.service'; +import { IgxGridSummaryService } from '../summaries/grid-summary.service'; let NEXT_ID = 0; @@ -56,7 +57,7 @@ export interface IGroupingDoneEventArgs { @Component({ changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, - providers: [IgxGridNavigationService, + providers: [IgxGridNavigationService, IgxGridSummaryService, { provide: GridBaseAPIService, useClass: IgxGridAPIService }, { provide: IgxGridBaseComponent, useExisting: forwardRef(() => IgxGridComponent) }, IgxFilteringService, IgxColumnResizingService @@ -124,9 +125,10 @@ export class IgxGridComponent extends IgxGridBaseComponent implements OnInit, Do viewRef: ViewContainerRef, navigation: IgxGridNavigationService, filteringService: IgxFilteringService, + summaryService: IgxGridSummaryService, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { super(gridAPI, selection, _transactions, elementRef, zone, document, cdr, resolver, differs, viewRef, navigation, - filteringService, _displayDensityOptions); + filteringService, summaryService, _displayDensityOptions); this._gridAPI = gridAPI; } @@ -628,7 +630,7 @@ export class IgxGridComponent extends IgxGridBaseComponent implements OnInit, Do public getContext(rowData): any { return { $implicit: rowData, - templateID: this.isGroupByRecord(rowData) ? 'groupRow' : 'dataRow' + templateID: this.isGroupByRecord(rowData) ? 'groupRow' : this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow' }; } @@ -843,7 +845,10 @@ export class IgxGridComponent extends IgxGridBaseComponent implements OnInit, Do public ngOnInit() { super.ngOnInit(); - this.onGroupingDone.pipe(takeUntil(this.destroy$)).subscribe(() => this.endEdit(true)); + this.onGroupingDone.pipe(takeUntil(this.destroy$)).subscribe((args) => { + this.endEdit(true); + this.summaryService.updateSummaryCache(args); + }); } public ngDoCheck(): void { diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts index 7d65ea9fefb..7960c56a467 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts @@ -1541,35 +1541,6 @@ describe('IgxGrid - GroupBy', () => { } })); - // GroupBy + Summaries - it('should take into account only the data records when calculating summaries.', fakeAsync(() => { - const fix = TestBed.createComponent(DefaultGridComponent); - const grid = fix.componentInstance.instance; - fix.componentInstance.width = '1200px'; - tick(); - grid.columnWidth = '200px'; - tick(); - fix.detectChanges(); - grid.groupBy({ - fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false - }); - fix.detectChanges(); - - grid.enableSummaries([{ fieldName: 'ProductName' }]); - tick(); - fix.detectChanges(); - - expect(grid.hasSummarizedColumns).toBe(true); - - const summaries = fix.debugElement.queryAll(By.css('igx-grid-summary')); - const labels = summaries[2].queryAll(By.css(SUMMARY_LABEL_CLASS)); - const values = summaries[2].queryAll(By.css(SUMMARY_VALUE_CLASS)); - expect(labels.length).toBe(1); - expect(labels[0].nativeElement.innerText).toBe('Count'); - expect(values.length).toBe(1); - expect(values[0].nativeElement.innerText).toBe('8'); - })); - // GroupBy + Hiding it('should retain same size for group row after a column is hidden.', fakeAsync(() => { const fix = TestBed.createComponent(DefaultGridComponent); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.module.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.module.ts index 616cc7d2e57..50d43cb168c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.module.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.module.ts @@ -18,6 +18,7 @@ import { IgxGridRowComponent } from './grid-row.component'; import { IgxChipsModule } from '../../chips/chips.module'; import { IgxGridCommonModule } from '../grid-common.module'; import { DeprecateMethod } from '../../core/deprecateDecorators'; +import { IgxGridSummaryPipe } from './grid.summary.pipe'; /** * @hidden @@ -33,7 +34,8 @@ import { DeprecateMethod } from '../../core/deprecateDecorators'; IgxGridPostGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, - IgxGridFilteringPipe + IgxGridFilteringPipe, + IgxGridSummaryPipe ], exports: [ IgxGridComponent, diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.search.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.search.spec.ts index cc2ca6f15cd..f62fe23d019 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.search.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.search.spec.ts @@ -887,7 +887,7 @@ describe('IgxGrid - search API', () => { expect(highlight).toBe(spans[4]); }); - it('Should be able to react to changes in grouping', async () => { + xit('Should be able to react to changes in grouping', async () => { grid.groupBy({ fieldName: 'JobTitle', dir: SortingDirection.Asc, @@ -943,7 +943,7 @@ describe('IgxGrid - search API', () => { expect(highlight !== null).toBeTruthy(); }); - it('Should be able to navigate through highlights with grouping and paging enabled', async () => { + xit('Should be able to navigate through highlights with grouping and paging enabled', async () => { grid.groupBy({ fieldName: 'JobTitle', dir: SortingDirection.Asc, @@ -988,7 +988,7 @@ describe('IgxGrid - search API', () => { expect(grid.page).toBe(1); }); - it('Should be able to properly handle perPage changes with gouping and paging', async () => { + xit('Should be able to properly handle perPage changes with gouping and paging', async () => { grid.groupBy({ fieldName: 'JobTitle', dir: SortingDirection.Asc, @@ -1069,7 +1069,7 @@ describe('IgxGrid - search API', () => { expect(grid.isExpandedGroup(grid.groupsRecords[0])).toBeTruthy(); }); - it('Should be able to properly handle navigating through collapsed rows with paging', async () => { + xit('Should be able to properly handle navigating through collapsed rows with paging', async () => { grid.groupBy({ fieldName: 'JobTitle', dir: SortingDirection.Asc, diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts new file mode 100644 index 00000000000..2e3b5dd87a1 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.summary.pipe.ts @@ -0,0 +1,103 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IgxGridAPIService } from './grid-api.service'; +import { GridBaseAPIService } from '../api.service'; +import { IgxGridBaseComponent, GridSummaryPosition, GridSummaryCalculationMode } from '../grid-base.component'; +import { IgxGridComponent } from './grid.component'; +import { IgxSummaryResult, ISummaryRecord } from '../summaries/grid-summary'; +import { IGroupByRecord } from '../../data-operations/groupby-record.interface'; +import { DataUtil } from '../../data-operations/data-util'; + +/** @hidden */ +@Pipe({ + name: 'gridSummary', + pure: true +}) +export class IgxGridSummaryPipe implements PipeTransform { + private gridAPI: IgxGridAPIService; + + constructor(gridAPI: GridBaseAPIService) { + this.gridAPI = gridAPI; + } + + public transform(flatData: any[], + hasSummary: boolean, + summaryCalculationMode: GridSummaryCalculationMode, + summaryPosition: GridSummaryPosition, + id: string, pipeTrigger: number): any[] { + + if (!flatData || !hasSummary || summaryCalculationMode === GridSummaryCalculationMode.rootLevelOnly) { + return flatData; + } + + return this.addSummaryRows(id, flatData, summaryPosition); + } + + private addSummaryRows(gridId: string, collection: any[], summaryPosition: GridSummaryPosition): any[] { + const recordsWithSummary = []; + const lastChildMap = new Map(); + const grid: IgxGridComponent = this.gridAPI.get(gridId); + + for (let i = 0; i < collection.length; i++) { + const record = collection[i]; + recordsWithSummary.push(record); + + let recordId; + let groupByRecord: IGroupByRecord = null; + + if (grid.isGroupByRecord(record)) { + groupByRecord = record as IGroupByRecord; + recordId = this.gridAPI.get_groupBy_record_id(groupByRecord); + } else { + recordId = this.gridAPI.get_row_id(gridId, record); + } + + if (summaryPosition === GridSummaryPosition.bottom && lastChildMap.has(recordId)) { + const groupRecords = lastChildMap.get(recordId); + + for (let j = 0; j < groupRecords.length; j++) { + const groupRecord = groupRecords[j]; + const groupRecordId = this.gridAPI.get_groupBy_record_id(groupRecord); + const summaries = grid.summaryService.calculateSummaries(groupRecordId, groupRecord.records); + const summaryRecord: ISummaryRecord = { + summaries: summaries + }; + recordsWithSummary.push(summaryRecord); + } + } + + if (groupByRecord === null || !grid.isExpandedGroup(groupByRecord)) { + continue; + } + + if (summaryPosition === GridSummaryPosition.top) { + const summaries = grid.summaryService.calculateSummaries(recordId, groupByRecord.records); + const summaryRecord: ISummaryRecord = { + summaries: summaries + }; + recordsWithSummary.push(summaryRecord); + } else if (summaryPosition === GridSummaryPosition.bottom) { + let lastChild = groupByRecord; + + while (lastChild.groups && lastChild.groups.length > 0 && grid.isExpandedGroup(lastChild)) { + lastChild = lastChild.groups[lastChild.groups.length - 1]; + } + + let lastChildId; + if (grid.isExpandedGroup(lastChild)) { + lastChildId = this.gridAPI.get_row_id(gridId, lastChild.records[lastChild.records.length - 1]); + } else { + lastChildId = this.gridAPI.get_groupBy_record_id(lastChild); + } + + let groupRecords = lastChildMap.get(lastChildId); + if (!groupRecords) { + groupRecords = []; + lastChildMap.set(lastChildId, groupRecords); + } + groupRecords.unshift(groupByRecord); + } + } + + return recordsWithSummary; + } +} diff --git a/projects/igniteui-angular/src/lib/grids/index.ts b/projects/igniteui-angular/src/lib/grids/index.ts index 3233741910d..01bdff3a80d 100644 --- a/projects/igniteui-angular/src/lib/grids/index.ts +++ b/projects/igniteui-angular/src/lib/grids/index.ts @@ -4,7 +4,7 @@ export * from './row.component'; export * from './column.component'; export * from './grid-base.component'; export * from './grid.common'; -export * from './grid-summary'; +export * from './summaries/grid-summary'; export * from './grid-common.module'; export { ColumnDisplayOrder } from './column-chooser-base'; export { IColumnVisibilityChangedEventArgs } from './column-hiding-item.directive'; diff --git a/projects/igniteui-angular/src/lib/grids/summaries/grid-root-summary.pipe.ts b/projects/igniteui-angular/src/lib/grids/summaries/grid-root-summary.pipe.ts new file mode 100644 index 00000000000..97cd20fb6bb --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/summaries/grid-root-summary.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { GridBaseAPIService } from '../api.service'; +import { IgxGridBaseComponent } from '../grid-base.component'; + +@Pipe({ + name: 'igxGridSummaryDataPipe', + pure: true +}) +export class IgxSummaryDataPipe implements PipeTransform { + + constructor(private gridAPI: GridBaseAPIService) { } + + transform(id: string, trigger: boolean = false) { + const summaryService = this.gridAPI.get(id).summaryService; + return summaryService.calculateSummaries( + summaryService.rootSummaryID, + this.gridAPI.get_summary_data(id) + ); + } +} diff --git a/projects/igniteui-angular/src/lib/grids/summaries/grid-summary.service.ts b/projects/igniteui-angular/src/lib/grids/summaries/grid-summary.service.ts new file mode 100644 index 00000000000..bcab4d211eb --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/summaries/grid-summary.service.ts @@ -0,0 +1,188 @@ +import { Injectable} from '@angular/core'; +import { IgxSummaryResult } from './grid-summary'; + +/** @hidden */ +@Injectable() +export class IgxGridSummaryService { + protected summaryCacheMap: Map> = new Map>(); + public grid; + public rootSummaryID = 'igxGridRootSummary'; + public summaryHeight = 0; + public maxSummariesLenght = 0; + public groupingExpressions = []; + public retriggerRootPipe = false; + + public clearSummaryCache(args?) { + if (!args) { + this.summaryCacheMap.clear(); + if (this.grid.rootSummariesEnabled) { + this.retriggerRootPipe = !this.retriggerRootPipe; + } + return; + } + if (args.data) { + let rowID = args.rowID; + if (!args.rowID) { + rowID = this.grid.primaryKey ? args.data[this.grid.primaryKey] : args.data; + } + this.removeSummaries(rowID); + } + if (args.rowID) { + const columnName = args.cellID ? this.grid.columnList.find(col => col.index === args.cellID.columnID).field : undefined; + this.removeSummaries(args.rowID, columnName); + } + } + + public removeSummaries(rowID, columnName?) { + if (this.summaryCacheMap.size === 0) { return; } + this.deleteSummaryCache(this.rootSummaryID, columnName); + if (this.summaryCacheMap.size === 1 && this.summaryCacheMap.has(this.rootSummaryID)) { return; } + if (this.isTreeGrid) { + this.removeAllTreeGridSummaries(rowID, columnName); + } else { + const summaryIds = this.getSummaryID(rowID, this.grid.groupingExpressions); + summaryIds.forEach(id => { + this.deleteSummaryCache(id, columnName); + }); + } + } + + public removeSummariesCachePerColumn(columnName) { + this.summaryCacheMap.forEach((cache) => { + if (cache.get(columnName)) { + cache.delete(columnName); + } + }); + if (this.grid.rootSummariesEnabled) { this.retriggerRootPipe = !this.retriggerRootPipe; } + } + + public calcMaxSummaryHeight() { + if (this.summaryHeight) { + return this.summaryHeight; + } + if (!this.grid.data) {return this.summaryHeight = 0; } + let maxSummaryLength = 0; + this.grid.columnList.filter((col) => col.hasSummary && !col.hidden).forEach((column) => { + const getCurrentSummaryColumn = column.summaries.operate([]).length; + if (getCurrentSummaryColumn) { + if (maxSummaryLength < getCurrentSummaryColumn) { + maxSummaryLength = getCurrentSummaryColumn; + } + } + }); + this.maxSummariesLenght = maxSummaryLength; + this.summaryHeight = maxSummaryLength * this.grid.defaultRowHeight; + return this.summaryHeight; + } + + public calculateSummaries(rowID, data) { + let rowSummaries = this.summaryCacheMap.get(rowID); + if (!rowSummaries) { + rowSummaries = new Map(); + this.summaryCacheMap.set(rowID, rowSummaries); + } + if (!this.hasSummarizedColumns || !data) {return rowSummaries; } + this.grid.columnList.filter(col => col.hasSummary).forEach((column) => { + if (!rowSummaries.get(column.field)) { + const columnValues = data.map(record => record[column.field]); + rowSummaries.set(column.field, + column.summaries.operate(columnValues)); + } + }); + return rowSummaries; + } + + public resetSummaryHeight() { + this.summaryHeight = 0; + if (this.grid.rootSummariesEnabled) { + this.retriggerRootPipe = !this.retriggerRootPipe; + } + } + + public updateSummaryCache(groupingArgs) { + if (this.summaryCacheMap.size === 0 || !this.hasSummarizedColumns) { return; } + if (this.groupingExpressions.length === 0) { + this.groupingExpressions = groupingArgs.expressions.map(record => record.fieldName); + return; + } + if (groupingArgs.length === 0) { + this.groupingExpressions = []; + this.clearSummaryCache(); + return; + } + this.compareGroupingExpressions(this.groupingExpressions, groupingArgs); + this.groupingExpressions = groupingArgs.expressions.map(record => record.fieldName); + } + + public get hasSummarizedColumns(): boolean { + const summarizedColumns = this.grid.columnList.filter(col => col.hasSummary && !col.hidden); + return summarizedColumns.length > 0; + } + + private deleteSummaryCache(id, columnName) { + if (this.summaryCacheMap.get(id)) { + if (columnName && this.summaryCacheMap.get(id).get(columnName)) { + this.summaryCacheMap.get(id).delete(columnName); + } else { + this.summaryCacheMap.delete(id); + } + if (id === this.rootSummaryID && this.grid.rootSummariesEnabled) { + this.retriggerRootPipe = !this.retriggerRootPipe; + } + } + } + + private getSummaryID(rowID, groupingExpressions) { + if (groupingExpressions.length === 0) { return []; } + const summaryIDs = []; + const rowData = this.grid.primaryKey ? this.grid.getRowByKey(rowID).rowData : rowID; + let id = '{ '; + groupingExpressions.forEach(expr => { + id += `'${expr.fieldName}': '${rowData[expr.fieldName]}'`; + summaryIDs.push(id.concat(' }')); + id += ', '; + }); + return summaryIDs; + } + + private removeAllTreeGridSummaries(rowID, columnName?) { + let row = this.grid.records.get(rowID); + if (!row) { return; } + row = row.children ? row : row.parent; + while (row) { + rowID = row.rowID; + this.deleteSummaryCache(rowID, columnName); + row = row.parent; + } + } + + private compareGroupingExpressions(current, groupingArgs) { + const newExpressions = groupingArgs.expressions.map(record => record.fieldName); + const removedCols = groupingArgs.ungroupedColumns; + if (current.length <= newExpressions.length) { + const newExpr = newExpressions.slice(0, current.length).toString(); + if (current.toString() !== newExpr) { + this.clearSummaryCache(); + } + return; + } + if (current.length > newExpressions.length) { + const currExpr = current.slice(0, newExpressions.length).toString(); + if (currExpr !== newExpressions.toString()) { + this.clearSummaryCache(); + return; + } + removedCols.map(col => col.field).forEach(colName => { + this.summaryCacheMap.forEach((cache, id) => { + if (id.indexOf(colName) !== -1) { + this.summaryCacheMap.delete(id); + }}); + }); + } + } + + private get isTreeGrid() { + return this.grid.nativeElement.tagName.toLowerCase() === 'igx-tree-grid'; + } + +} diff --git a/projects/igniteui-angular/src/lib/grids/grid-summary.ts b/projects/igniteui-angular/src/lib/grids/summaries/grid-summary.ts similarity index 97% rename from projects/igniteui-angular/src/lib/grids/grid-summary.ts rename to projects/igniteui-angular/src/lib/grids/summaries/grid-summary.ts index 7fe74d6adfc..9275a78e038 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-summary.ts +++ b/projects/igniteui-angular/src/lib/grids/summaries/grid-summary.ts @@ -1,5 +1,3 @@ -import { DataUtil } from '../data-operations/data-util'; -import { ISortingExpression, SortingDirection } from '../data-operations/sorting-expression.interface'; export interface ISummaryExpression { fieldName: string; customSummary?: any; @@ -10,6 +8,11 @@ export interface IgxSummaryResult { summaryResult: any; } +export interface ISummaryRecord { + summaries: Map; + cellIndentation?: number; +} + export class IgxSummaryOperand { /** * Counts all the records in the data source. diff --git a/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html new file mode 100644 index 00000000000..6b429e970ff --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.html @@ -0,0 +1,19 @@ + + +
+ + +
+ +
+ chevron_right +
+
+ + {{ summary.label }} + + {{ columnDatatype === 'number' ? (summary.summaryResult | igxdecimal) : columnDatatype === 'date' ? (summary.summaryResult | igxdate) : (summary.summaryResult) }} + +
+
+
\ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts new file mode 100644 index 00000000000..7ab49a61fdd --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts @@ -0,0 +1,174 @@ +import { Component, Input, HostBinding, HostListener, ChangeDetectionStrategy, ElementRef } from '@angular/core'; +import { IgxSummaryResult } from './grid-summary'; +import { IgxColumnComponent } from '../column.component'; +import { DisplayDensity } from '../../core/density'; +import { DataType } from '../../data-operations/data-util'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + preserveWhitespaces: false, + selector: 'igx-grid-summary-cell', + templateUrl: './summary-cell.component.html' +}) +export class IgxSummaryCellComponent { + + @Input() + public summaryResults: IgxSummaryResult[]; + + @Input() + public column: IgxColumnComponent; + + @Input() + public firstCellIndentation = 0; + + @Input() + public hasSummary = false; + + @Input() + public density; + + constructor(private element: ElementRef) { + } + + @HostBinding('class') + get styleClasses(): string { + const defaultClasses = ['igx-grid-summary--cell']; + const classList = { + 'igx-grid-summary': this.density === DisplayDensity.comfortable, + 'igx-grid-summary--fw': this.column.width !== null, + 'igx-grid-summary--empty': !this.column.hasSummary, + 'igx-grid-summary--compact': this.density === DisplayDensity.compact, + 'igx-grid-summary--cosy': this.density === DisplayDensity.cosy, + 'igx-grid-summary--pinned': this.column.pinned, + 'igx-grid-summary--pinned-last': this.column.isLastPinned + }; + Object.entries(classList).forEach(([className, value]) => { + if (value) { + defaultClasses.push(className); + } + }); + return defaultClasses.join(' '); + } + + @Input() + @HostBinding('attr.data-rowIndex') + public rowIndex: number; + + @HostBinding('attr.data-visibleIndex') + get visibleColumnIndex(): number { + return this.column.visibleIndex; + } + + @HostBinding('attr.tabindex') + public tabindex = 0; + + @HostBinding('attr.aria-describedby') + public get describeby() { + return `Summary_${this.column.field}`; + } + + get nativeElement(): any { + return this.element.nativeElement; + } + + @HostListener('keydown', ['$event']) + dispatchEvent(event: KeyboardEvent) { + const key = event.key.toLowerCase(); + if (!this.isKeySupportedInCell(key)) { return; } + const shift = event.shiftKey; + const ctrl = event.ctrlKey; + event.preventDefault(); + event.stopPropagation(); + if (this.rowIndex === 0 && + this.grid.unpinnedColumns[this.grid.unpinnedColumns.length - 1].visibleIndex === this.visibleColumnIndex) { + return; + + } + if (ctrl && (key === 'arrowup' || key === 'up' || key === 'down' || key === 'arrowdown')) { return; } + const row = this.getRowElementByIndex(this.rowIndex); + switch (key) { + case 'tab': + if (shift) { + this.grid.navigation.performShiftTabKey(row, this.rowIndex, this.visibleColumnIndex, true); + break; + } + this.grid.navigation.performTab(row, this.rowIndex, this.visibleColumnIndex, true); + break; + case 'arrowleft': + case 'home': + case 'left': + if (ctrl || key === 'home') { + this.grid.navigation.onKeydownHome(this.rowIndex, true); + break; + } + this.grid.navigation.onKeydownArrowLeft(this.nativeElement, this.rowIndex, this.visibleColumnIndex, true); + break; + case 'end': + case 'arrowright': + case 'right': + if (ctrl || key === 'end') { + this.grid.navigation.onKeydownEnd(this.rowIndex, true); + break; + } + this.grid.navigation.onKeydownArrowRight(this.nativeElement, this.rowIndex, this.visibleColumnIndex, true); + break; + case 'arrowup': + case 'up': + this.grid.navigation.navigateUp(row, this.rowIndex, this.visibleColumnIndex); + break; + case 'arrowdown': + case 'down': + this.grid.navigation.navigateDown(row, this.rowIndex, this.visibleColumnIndex); + break; + } + } + + @HostBinding('style.min-width') + @HostBinding('style.max-width') + @HostBinding('style.flex-basis') + get width() { + const hasVerticalScroll = !this.grid.verticalScrollContainer.dc.instance.notVirtual; + const colWidth = this.column.width; + const isPercentageWidth = colWidth && typeof colWidth === 'string' && colWidth.indexOf('%') !== -1; + + if (colWidth && !isPercentageWidth) { + let cellWidth = this.isLastUnpinned && hasVerticalScroll ? + parseInt(colWidth, 10) - 18 + '' : colWidth; + + if (typeof cellWidth !== 'string' || cellWidth.endsWith('px') === false) { + cellWidth += 'px'; + } + + return cellWidth; + } else { + return colWidth; + } + } + + get isLastUnpinned() { + const unpinnedColumns = this.grid.unpinnedColumns; + return unpinnedColumns[unpinnedColumns.length - 1] === this.column; + } + + get columnDatatype(): DataType { + return this.column.dataType; + } + + get itemHeight() { + return this.column.grid.defaultRowHeight; + } + + private get grid() { + return (this.column.grid as any); + } + + private getRowElementByIndex(rowIndex) { + return this.grid.nativeElement.querySelector(`igx-grid-summary-row[data-rowindex="${rowIndex}"]`); + } + + private isKeySupportedInCell(key) { + return ['down', 'up', 'left', 'right', 'arrowdown', 'arrowup', 'arrowleft', 'arrowright', + 'home', 'end', 'tab'].indexOf(key) !== -1; + + } +} diff --git a/projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.html b/projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.html new file mode 100644 index 00000000000..6081768bf16 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.html @@ -0,0 +1,18 @@ + + +
+
+ +
+
+ + + + + + +
diff --git a/projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.ts b/projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.ts new file mode 100644 index 00000000000..a8d49aa99aa --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/summaries/summary-row.component.ts @@ -0,0 +1,111 @@ +import { Component, Input, + ViewChildren, QueryList, + HostBinding, ViewChild, + ElementRef, + ChangeDetectionStrategy, + ChangeDetectorRef, + DoCheck} from '@angular/core'; +import { IgxSummaryResult } from './grid-summary'; +import { IgxSummaryCellComponent } from './summary-cell.component'; +import { IgxGridForOfDirective } from '../../directives/for-of/for_of.directive'; +import { GridBaseAPIService } from '../api.service'; +import { IgxGridBaseComponent } from '../grid-base.component'; +import { IgxColumnComponent } from '../column.component'; +import { DisplayDensity } from '../../core/density'; + + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + preserveWhitespaces: false, + selector: 'igx-grid-summary-row', + templateUrl: './summary-row.component.html' +}) +export class IgxSummaryRowComponent implements DoCheck { + + @Input() + public summaries: Map; + + @Input() + public gridID; + + @Input() + public index: number; + + @Input() + public indentation = 0; + + @Input() + public firstCellIndentation = -1; + + @HostBinding('attr.data-rowIndex') + get dataRowIndex() { + return this.index; + } + + get minHeight() { + return this.grid.summaryService.calcMaxSummaryHeight(); + } + + @ViewChildren(IgxSummaryCellComponent, { read: IgxSummaryCellComponent }) + public summaryCells: QueryList; + + /** + * @hidden + */ + @ViewChild('igxDirRef', { read: IgxGridForOfDirective }) + public virtDirRow: IgxGridForOfDirective; + + constructor(public gridAPI: GridBaseAPIService, + public element: ElementRef, + public cdr: ChangeDetectorRef) {} + + public ngDoCheck() { + this.cdr.detectChanges(); + } + + public get grid() { + return this.gridAPI.get(this.gridID); + } + public get nativeElement() { + return this.element.nativeElement; + } + + // TO DO: to be refactored when displayDensity refactoring is merged + get gridDensity(): string { + if (this.grid.isCosy()) { + return DisplayDensity.cosy; + } else if (this.grid.isCompact()) { + return DisplayDensity.compact; + } else { + return DisplayDensity.comfortable; + } + } + + public getColumnSummaries(columnName) { + if (!this.summaries.get(columnName)) { + return []; + } + return this.summaries.get(columnName); + + } + /** + * @hidden + */ + public notGroups(columns) { + return columns.filter(c => !c.columnGroup); + } + + /** + * @hidden + */ + public get pinnedColumns(): IgxColumnComponent[] { + return this.grid.pinnedColumns; + } + + /** + * @hidden + */ + public get unpinnedColumns(): IgxColumnComponent[] { + return this.grid.unpinnedColumns; + } +} diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index 82bc73c8877..5d40a660714 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -15,6 +15,13 @@ export class IgxTreeGridAPIService extends GridBaseAPIService row.isFilteredOutParent === undefined || row.isFilteredOutParent === false) + .map(rec => rec.data); + return data; + } + public expand_row(id: string, rowID: any) { const grid = this.get(id); const expandedStates = grid.expansionStates; diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts new file mode 100644 index 00000000000..ab2f39b42ec --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-summaries.spec.ts @@ -0,0 +1,740 @@ +import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { IgxTreeGridModule } from './index'; +import { + IgxTreeGridSummariesComponent, + IgxTreeGridSummariesKeyComponent, + IgxTreeGridCustomSummariesComponent +} from '../../test-utils/tree-grid-components.spec'; +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { HelperUtils } from '../../test-utils/helper-utils.spec'; +import { wait } from '../../test-utils/ui-interactions.spec'; +import { IgxNumberFilteringOperand } from 'igniteui-angular'; + +describe('IgxTreeGrid - Summaries', () => { + configureTestSuite(); + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxTreeGridSummariesComponent, + IgxTreeGridSummariesKeyComponent, + IgxTreeGridCustomSummariesComponent + ], + imports: [ + BrowserAnimationsModule, + IgxTreeGridModule] + }) + .compileComponents(); + })); + + describe('', () => { + let fix; + let treeGrid; + beforeEach(() => { + fix = TestBed.createComponent(IgxTreeGridSummariesKeyComponent); + fix.detectChanges(); + treeGrid = fix.componentInstance.treeGrid; + }); + + it('should render summaries for all the rows when have parentKey', () => { + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + // Expand second row and verify summaries + treeGrid.toggleRow(treeGrid.getRowByIndex(1).rowID); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + verifySummaryForRow847(fix, 4); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + + // Expand child row and verify summaries + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + + verifyTreeBaseSummaries(fix); + verifySummaryForRow663(fix, 5); + verifySummaryForRow847(fix, 6); + }); + + it('should render summaries on top when position is top ', () => { + treeGrid.summaryPosition = 'top'; + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + // Expand first row and verify summaries + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + verifySummaryForRow147(fix, 1); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + + // Expand second row and verify summaries + treeGrid.toggleRow(treeGrid.getRowByIndex(5).rowID); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + verifySummaryForRow847(fix, 6); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + + // Expand first row child and verify summaries + treeGrid.toggleRow(treeGrid.getRowByIndex(4).rowID); + fix.detectChanges(); + + verifySummaryForRow317(fix, 5); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(4); + }); + + it('should be able to change summaryPosition at runtime', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + expect(HelperUtils.getAllVisbleSummariesRowIndexes(fix)).toEqual([0, 6, 7, 12, 13]); + + treeGrid.summaryPosition = 'top'; + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + + expect(HelperUtils.getAllVisbleSummariesRowIndexes(fix)).toEqual([0, 1, 5, 9, 12]); + + treeGrid.summaryPosition = 'bottom'; + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + + expect(HelperUtils.getAllVisbleSummariesRowIndexes(fix)).toEqual([0, 6, 7, 12, 13]); + }); + + it('should be able to change summaryCalculationMode at runtime', async () => { + treeGrid.expandAll(); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + + expect(HelperUtils.getAllVisbleSummariesRowIndexes(fix)).toEqual([0, 6, 7, 12, 13]); + + treeGrid.summaryCalculationMode = 'rootLevelOnly'; + fix.detectChanges(); + await wait(50); + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + + treeGrid.summaryCalculationMode = 'childLevelsOnly'; + fix.detectChanges(); + await wait(50); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + expect(HelperUtils.getAllVisbleSummariesRowIndexes(fix)).toEqual([6, 7, 12, 13, 16]); + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + expect(summaryRow).toBeNull(); + + treeGrid.summaryCalculationMode = 'rootAndChildLevels'; + fix.detectChanges(); + await wait(50); + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(5); + expect(HelperUtils.getAllVisbleSummariesRowIndexes(fix)).toEqual([0, 6, 7, 12, 13]); + }); + + it('should be able to enable/disable summaries at runtime', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + treeGrid.getColumnByName('Age').hasSummary = false; + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 3); + + let summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 3, [], []); + HelperUtils.verifyColumnSummaries(summary, 4, ['Count'], []); + }); + + // Disable all summaries + treeGrid.getColumnByName('Name').hasSummary = false; + treeGrid.getColumnByName('HireDate').hasSummary = false; + treeGrid.getColumnByName('OnPTO').hasSummary = false; + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(0); + + treeGrid.collapseAll(); + fix.detectChanges(); + + treeGrid.getColumnByName('Name').hasSummary = true; + fix.detectChanges(); + + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 2, [], []); + HelperUtils.verifyColumnSummaries(summary, 3, [], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + }); + HelperUtils.verifyVisbleSummariesHeight(fix, 1); + }); + + xit('should be able to enable/disable summaries with API', () => { + treeGrid.disableSummaries([{ fieldName: 'Age' }, { fieldName: 'HireDate' }]); + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 1); + + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + let summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 2, [], []); + HelperUtils.verifyColumnSummaries(summary, 3, [], []); + HelperUtils.verifyColumnSummaries(summary, 4, ['Count'], []); + }); + + HelperUtils.verifyVisbleSummariesHeight(fix, 1); + + treeGrid.disableSummaries('Name'); + treeGrid.disableSummaries('OnPTO'); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(0); + + treeGrid.enableSummaries('HireDate'); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + + HelperUtils.verifyVisbleSummariesHeight(fix, 3); + + summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, [], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 3, [], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + }); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 4); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['3', 'Jul 19, 2009', 'Sep 18, 2014']); + + treeGrid.enableSummaries([{ fieldName: 'Age' }, { fieldName: 'ID' }]); + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 5); + + summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, ['Count', 'Min', 'Max', 'Sum', 'Avg'], []); + HelperUtils.verifyColumnSummaries(summary, 1, [], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], []); + HelperUtils.verifyColumnSummaries(summary, 4, [], []); + }); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 4); + HelperUtils.verifyColumnSummaries(summaryRow, 0, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['3', '29', '43', '103', '34.333']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 0, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['4', '19', '847', '207', '51.75']); + }); + + xit('should be able to change summary operant at runtime', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 5); + + treeGrid.getColumnByName('Age').summaries = fix.componentInstance.ageSummaryTest; + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 6); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 7); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg', 'Test'], ['3', '29', '43', '103', '34.333', '2']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 6); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg', 'Test'], ['2', '35', '44', '79', '39.5', '1']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 3, + ['Count', 'Min', 'Max', 'Sum', 'Avg', 'Test'], ['4', '42', '61', '207', '51.75', '0']); + }); + + it('should be able to change summary operant with API', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 5); + + treeGrid.enableSummaries([{ fieldName: 'Age', customSummary: fix.componentInstance.ageSummary }]); + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 3); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 7); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['3', '103', '34.33']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 6); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['2', '79', '39.5']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['4', '207', '51.75']); + }); + + it('Hiding: should render correct summaries when show/hide a colomn', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + treeGrid.getColumnByName('Age').hidden = true; + fix.detectChanges(); + + let summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, ['Count'], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 3, ['Count'], []); + }); + + HelperUtils.verifyVisbleSummariesHeight(fix, 3); + + treeGrid.getColumnByName('Name').hidden = true; + treeGrid.getColumnByName('HireDate').hidden = true; + treeGrid.getColumnByName('OnPTO').hidden = true; + fix.detectChanges(); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(0); + + treeGrid.getColumnByName('HireDate').hidden = false; + treeGrid.getColumnByName('OnPTO').hidden = false; + fix.detectChanges(); + + summaries = HelperUtils.getAllVisbleSummaries(fix); + summaries.forEach(summary => { + HelperUtils.verifyColumnSummaries(summary, 0, [], []); + HelperUtils.verifyColumnSummaries(summary, 1, ['Count', 'Earliest', 'Latest'], []); + HelperUtils.verifyColumnSummaries(summary, 2, ['Count'], []); + }); + + HelperUtils.verifyVisbleSummariesHeight(fix, 3); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 7); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['3']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 6); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['2']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count'], ['4']); + }); + + it('Filtering: should render correct summaries when filter and found only childs', () => { + treeGrid.filter('ID', 12, IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo')); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 2); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['1', 'Dec 18, 2007', 'Dec 18, 2007']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '50', '50', '50', '50']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + verifySummaryIsEmpty(summaryRow); + }); + + it('Filtering: should render correct summaries when filter and no results are found', () => { + treeGrid.filter('ID', 0, IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo')); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + verifySummaryIsEmpty(summaryRow); + }); + + it('Filtering: should render correct summaries when filter', () => { + treeGrid.filter('ID', 17, IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo')); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 5); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['1', 'Dec 18, 2007', 'Dec 18, 2007']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '50', '50', '50', '50']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 2); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['1', 'May 4, 2014', 'May 4, 2014']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '44', '44', '44', '44']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '61', '61', '61', '61']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['1', 'Feb 1, 2010', 'Feb 1, 2010']); + }); + + it('Paging: should render correct summaries when paging is enable and position is buttom', () => { + treeGrid.paging = true; + treeGrid.perPage = 4; + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + verifyTreeBaseSummaries(fix); + + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + verifyTreeBaseSummaries(fix); + verifySummaryForRow147(fix, 4); + + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + + treeGrid.page = 1; + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + verifyTreeBaseSummaries(fix); + verifySummaryForRow147(fix, 3); + verifySummaryForRow317(fix, 2); + }); + + it('Paging: should render correct summaries when paging is enable and position is top', () => { + treeGrid.paging = true; + treeGrid.perPage = 4; + treeGrid.summaryPosition = 'top'; + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + verifyTreeBaseSummaries(fix); + + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + verifyTreeBaseSummaries(fix); + verifySummaryForRow147(fix, 1); + + treeGrid.toggleRow(treeGrid.getRowByIndex(4).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + verifySummaryForRow317(fix, 5); + verifySummaryForRow147(fix, 1); + + treeGrid.page = 1; + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + verifyTreeBaseSummaries(fix); + + treeGrid.toggleRow(treeGrid.getRowByIndex(2).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + verifySummaryForRow847(fix, 3); + }); + + it('CRUD: Add root node', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + const newRow = { + ID: 777, + ParentID: -1, + Name: 'New Employee', + HireDate: new Date(2019, 3, 3), + Age: 19 + }; + treeGrid.addRow(newRow); + fix.detectChanges(); + + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['5']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['5', 'Apr 20, 2008', 'Apr 3, 2019']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['5', '19', '61', '226', '45.2']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['5']); + + verifySummaryForRow147(fix, 7); + }); + + xit('CRUD: Add child node', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + const newRow = { + ID: 777, + ParentID: 147, + Name: 'New Employee', + HireDate: new Date(2019, 3, 3), + Age: 19 + }; + treeGrid.addRow(newRow); + fix.detectChanges(); + + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 8); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['4']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['4', 'Jul 19, 2009', 'Apr 3, 2019']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['4']); + + verifyTreeBaseSummaries(fix); + }); + + it('CRUD: delete root node', () => { + treeGrid.expandAll(); + fix.detectChanges(); + + treeGrid.deleteRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['3']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['3', 'Feb 1, 2010', 'Feb 22, 2014']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['3', '42', '61', '152', '50.667']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['3']); + + verifySummaryForRow847(fix, 5); + }); + + it('CRUD: delete all root nodes', () => { + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + treeGrid.toggleRow(treeGrid.getRowByIndex(5).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + + treeGrid.deleteRow(treeGrid.getRowByIndex(5).rowID); + fix.detectChanges(); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['3']); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + + treeGrid.deleteRow(treeGrid.getRowByIndex(5).rowID); + fix.detectChanges(); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['2']); + + treeGrid.deleteRow(treeGrid.getRowByIndex(5).rowID); + fix.detectChanges(); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + + treeGrid.deleteRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + verifySummaryIsEmpty(summaryRow); + }); + + it('CRUD: delete child node', () => { + treeGrid.toggleRow(treeGrid.getRowByIndex(0).rowID); + fix.detectChanges(); + + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + + treeGrid.deleteRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + verifyTreeBaseSummaries(fix); + + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 3); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['2']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['2', 'Jul 19, 2009', 'Jul 3, 2011']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '29', '43', '72', '36']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['2']); + + treeGrid.deleteRow(treeGrid.getRowByIndex(2).rowID); + fix.detectChanges(); + + treeGrid.deleteRow(treeGrid.getRowByIndex(1).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + verifyTreeBaseSummaries(fix); + }); + + it('CRUD: Update root node', () => { + const newRow = { + ID: 147, + ParentID: -1, + Name: 'New Employee', + HireDate: new Date(2019, 3, 3), + Age: 19 + }; + treeGrid.getRowByKey(147).update(newRow); + fix.detectChanges(); + + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['4']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['4', 'Feb 1, 2010', 'Apr 3, 2019']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['4', '19', '61', '171', '42.75']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['4']); + }); + + it('CRUD: Update child node', () => { + treeGrid.toggleRow(treeGrid.getRowByIndex(1).rowID); + fix.detectChanges(); + + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + const newRow = { + ID: 663, + ParentID: 847, + Name: 'New Employee', + HireDate: new Date(2019, 3, 3), + Age: 19 + }; + treeGrid.getRowByKey(663).update(newRow); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 6); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['2']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['2', 'May 4, 2014', 'Apr 3, 2019']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '19', '44', '63', '31.5']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['2']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 5); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['1', 'Apr 22, 2010', 'Apr 22, 2010']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '39', '39', '39', '39']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['1']); + }); + }); + + it('should render correct custom summaries', () => { + const fix = TestBed.createComponent(IgxTreeGridCustomSummariesComponent); + fix.detectChanges(); + const treeGrid = fix.componentInstance.treeGrid; + treeGrid.expandAll(); + fix.detectChanges(); + + HelperUtils.verifyVisbleSummariesHeight(fix, 3); + + let summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 7); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['3', '103', '34.33']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 6); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['2', '79', '39.5']); + + summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fix, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Sum', 'Avg'], ['4', '207', '51.75']); + }); + + it('should render summaries for all the rows', () => { + const fix = TestBed.createComponent(IgxTreeGridSummariesComponent); + fix.detectChanges(); + const treeGrid = fix.componentInstance.treeGrid; + + verifyTreeBaseSummaries(fix); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(1); + + treeGrid.toggleRow(treeGrid.getRowByIndex(1).rowID); + fix.detectChanges(); + + verifyTreeBaseSummaries(fix); + verifySummaryForRow847(fix, 4); + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(2); + + treeGrid.toggleRow(treeGrid.getRowByIndex(3).rowID); + fix.detectChanges(); + + expect(HelperUtils.getAllVisbleSummariesLength(fix)).toEqual(3); + + verifyTreeBaseSummaries(fix); + verifySummaryForRow663(fix, 5); + verifySummaryForRow847(fix, 6); + }); + + function verifySummaryForRow147(fixture, vissibleIndex) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, vissibleIndex); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['3']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['3', 'Jul 19, 2009', 'Sep 18, 2014']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['3', '29', '43', '103', '34.333']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['3']); + } + + function verifySummaryForRow317(fixture, vissibleIndex) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, vissibleIndex); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['2']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['2', 'Nov 11, 2009', 'Oct 17, 2015']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '35', '44', '79', '39.5']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['2']); + } + + function verifySummaryForRow847(fixture, vissibleIndex) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, vissibleIndex); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['2']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['2', 'May 4, 2014', 'Dec 9, 2017']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['2', '25', '44', '69', '34.5']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['2']); + } + + function verifySummaryForRow663(fixture, vissibleIndex) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, vissibleIndex); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['1']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['1', 'Apr 22, 2010', 'Apr 22, 2010']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['1', '39', '39', '39', '39']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['1']); + } + + function verifySummaryIsEmpty(summaryRow) { + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['0']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['0', '', '']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['0', '', '', '', '']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['0']); + } + + function verifyTreeBaseSummaries(fixture) { + const summaryRow = HelperUtils.getSummaryRowByDataRowIndex(fixture, 0); + HelperUtils.verifyColumnSummaries(summaryRow, 0, [], []); + HelperUtils.verifyColumnSummaries(summaryRow, 1, ['Count'], ['4']); + HelperUtils.verifyColumnSummaries(summaryRow, 2, ['Count', 'Earliest', 'Latest'], ['4', 'Apr 20, 2008', 'Feb 22, 2014']); + HelperUtils.verifyColumnSummaries(summaryRow, 3, ['Count', 'Min', 'Max', 'Sum', 'Avg'], ['4', '42', '61', '207', '51.75']); + HelperUtils.verifyColumnSummaries(summaryRow, 4, ['Count'], ['4']); + } +}); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html index db1a34a5f35..5e9165a74fc 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html @@ -52,7 +52,8 @@ | treeGridFiltering:filteringExpressionsTree:id:pipeTrigger | treeGridSorting:sortingExpressions:id:pipeTrigger | treeGridFlattening:id:expansionDepth:expansionStates:pipeTrigger - | treeGridPaging:page:perPage:id:pipeTrigger" + | treeGridPaging:page:perPage:id:pipeTrigger + | treeGridSummary:hasSummarizedColumns:summaryCalculationMode:summaryPosition:id:summaryPipeTrigger" let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForContainerSize]='calcHeight' [igxForItemSize]="rowHeight" #verticalScrollContainer (onChunkPreload)="dataLoading($event)"> @@ -60,8 +61,12 @@ + + + + - + @@ -69,22 +74,8 @@
- - + +
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 470a70aaec2..feda02f2e61 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -28,6 +28,8 @@ import { IgxGridNavigationService } from '../grid-navigation.service'; import { mergeObjects } from '../../core/utils'; import { IgxHierarchicalTransactionService } from '../../services'; import { IgxFilteringService } from '../filtering/grid-filtering.service'; +import { IgxSummaryResult } from '../summaries/grid-summary'; +import { IgxGridSummaryService } from '../summaries/grid-summary.service'; let NEXT_ID = 0; @@ -52,7 +54,7 @@ let NEXT_ID = 0; preserveWhitespaces: false, selector: 'igx-tree-grid', templateUrl: 'tree-grid.component.html', - providers: [IgxGridNavigationService, { provide: GridBaseAPIService, useClass: IgxTreeGridAPIService }, + providers: [IgxGridNavigationService, IgxGridSummaryService, { provide: GridBaseAPIService, useClass: IgxTreeGridAPIService }, { provide: IgxGridBaseComponent, useExisting: forwardRef(() => IgxTreeGridComponent) }, IgxFilteringService] }) export class IgxTreeGridComponent extends IgxGridBaseComponent { @@ -248,23 +250,13 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { viewRef: ViewContainerRef, navigation: IgxGridNavigationService, filteringService: IgxFilteringService, + summaryService: IgxGridSummaryService, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { super(gridAPI, selection, _transactions, elementRef, zone, document, cdr, resolver, differs, viewRef, navigation, - filteringService, _displayDensityOptions); + filteringService, summaryService, _displayDensityOptions); this._gridAPI = gridAPI; } - /** - * @hidden - * Returns if the `IgxTreeGridComponent` has summarized columns. - * ```typescript - * const summarizedGrid = this.grid.hasSummarizedColumns; - * ``` - * @memberof IgxTreeGridComponent - */ - get hasSummarizedColumns(): boolean { - return false; - } private cloneMap(mapIn: Map): Map { const mapCloned: Map = new Map(); @@ -475,13 +467,6 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { return path; } - /** - * @hidden - */ - protected calcMaxSummaryHeight() { - return 0; - } - /** * @hidden */ @@ -521,7 +506,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent { public getContext(rowData): any { return { $implicit: rowData, - templateID: 'dataRow' + templateID: this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow' }; } } diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.module.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.module.ts index 45b4cd153f4..4dc6f8c60ac 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.module.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.module.ts @@ -9,6 +9,7 @@ import { IgxTreeGridHierarchizingPipe } from './tree-grid.pipes'; import { IgxTreeGridFlatteningPipe, IgxTreeGridSortingPipe, IgxTreeGridPagingPipe, IgxTreeGridTransactionPipe } from './tree-grid.pipes'; import { IgxTreeGridCellComponent } from './tree-cell.component'; import { IgxTreeGridFilteringPipe } from './tree-grid.filtering.pipe'; +import { IgxTreeGridSummaryPipe } from './tree-grid.summary.pipe'; @NgModule({ declarations: [ @@ -20,7 +21,8 @@ import { IgxTreeGridFilteringPipe } from './tree-grid.filtering.pipe'; IgxTreeGridSortingPipe, IgxTreeGridFilteringPipe, IgxTreeGridPagingPipe, - IgxTreeGridTransactionPipe + IgxTreeGridTransactionPipe, + IgxTreeGridSummaryPipe ], exports: [ IgxTreeGridComponent, diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts index 2518a66dc27..cb5f044b0e5 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts @@ -6,7 +6,7 @@ import { GridBaseAPIService } from '../api.service'; import { IgxTreeGridComponent } from './tree-grid.component'; import { ISortingExpression } from '../../../public_api'; import { ITreeGridRecord } from './tree-grid.interfaces'; -import { IgxGridBaseComponent } from '../grid'; +import { IgxGridBaseComponent, IgxSummaryResult } from '../grid'; /** *@hidden @@ -138,7 +138,7 @@ export class IgxTreeGridFlatteningPipe implements PipeTransform { expandedLevels: number, expandedStates: Map, pipeTrigger: number): any[] { const grid: IgxTreeGridComponent = this.gridAPI.get(id); - const data: ITreeGridRecord[] = []; + const data: any[] = []; grid.processedRootRecords = collection; grid.processedRecords = new Map(); @@ -148,12 +148,13 @@ export class IgxTreeGridFlatteningPipe implements PipeTransform { return data; } - private getFlatDataRecursive(collection: ITreeGridRecord[], data: ITreeGridRecord[] = [], + private getFlatDataRecursive(collection: ITreeGridRecord[], data: any[], expandedLevels: number, expandedStates: Map, gridID: string, parentExpanded: boolean) { if (!collection || !collection.length) { return; } + const grid: IgxTreeGridComponent = this.gridAPI.get(gridID); for (let i = 0; i < collection.length; i++) { const hierarchicalRecord = collection[i]; @@ -162,10 +163,9 @@ export class IgxTreeGridFlatteningPipe implements PipeTransform { data.push(hierarchicalRecord); } - const grid: IgxTreeGridComponent = this.gridAPI.get(gridID); - hierarchicalRecord.expanded = this.gridAPI.get_row_expansion_state(gridID, hierarchicalRecord.rowID, hierarchicalRecord.level); + this.updateNonProcessedRecordExpansion(grid, hierarchicalRecord); grid.processedRecords.set(hierarchicalRecord.rowID, hierarchicalRecord); @@ -224,7 +224,7 @@ export class IgxTreeGridPagingPipe implements PipeTransform { } public transform(collection: ITreeGridRecord[], page = 0, perPage = 15, id: string, pipeTrigger: number): ITreeGridRecord[] { - const grid: IgxTreeGridComponent = this.gridAPI.get(id) as IgxTreeGridComponent; + const grid = this.gridAPI.get(id); if (!grid.paging) { return collection; } diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts new file mode 100644 index 00000000000..2007c18e4d9 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.summary.pipe.ts @@ -0,0 +1,79 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IgxTreeGridAPIService } from './tree-grid-api.service'; +import { GridBaseAPIService } from '../api.service'; +import { IgxGridBaseComponent, GridSummaryPosition, GridSummaryCalculationMode } from '../grid-base.component'; +import { ITreeGridRecord } from './tree-grid.interfaces'; +import { IgxTreeGridComponent } from './tree-grid.component'; +import { IgxSummaryResult, ISummaryRecord } from '../summaries/grid-summary'; + +/** @hidden */ +@Pipe({ + name: 'treeGridSummary', + pure: true +}) +export class IgxTreeGridSummaryPipe implements PipeTransform { + private gridAPI: IgxTreeGridAPIService; + + constructor(gridAPI: GridBaseAPIService) { + this.gridAPI = gridAPI; + } + + public transform(flatData: ITreeGridRecord[], + hasSummary: boolean, + summaryCalculationMode: GridSummaryCalculationMode, + summaryPosition: GridSummaryPosition, + id: string, pipeTrigger: number): any[] { + const grid: IgxTreeGridComponent = this.gridAPI.get(id); + + if (!flatData || !hasSummary || summaryCalculationMode === GridSummaryCalculationMode.rootLevelOnly) { + return flatData; + } + + return this.addSummaryRows(grid, flatData, summaryPosition); + } + + private addSummaryRows(grid: IgxTreeGridComponent, collection: ITreeGridRecord[], summaryPosition: GridSummaryPosition): any[] { + const recordsWithSummary = []; + + for (let i = 0; i < collection.length; i++) { + const record = collection[i]; + recordsWithSummary.push(record); + + const isExpanded = record.children && record.children.length > 0 && record.expanded; + + if (summaryPosition === GridSummaryPosition.bottom && !isExpanded) { + let childRecord = record; + let parent = record.parent; + + while (parent) { + const children = parent.children; + + if (children[children.length - 1] === childRecord ) { + const childData = children.filter(r => !r.isFilteredOutParent).map(r => r.data); + const summaries = grid.summaryService.calculateSummaries(parent.rowID, childData); + const summaryRecord: ISummaryRecord = { + summaries: summaries, + cellIndentation: parent.level + 1 + }; + recordsWithSummary.push(summaryRecord); + + childRecord = parent; + parent = childRecord.parent; + } else { + break; + } + } + } else if (summaryPosition === GridSummaryPosition.top && isExpanded) { + const childData = record.children.map(r => r.data); + const summaries = grid.summaryService.calculateSummaries(record.rowID, childData); + const summaryRecord: ISummaryRecord = { + summaries: summaries, + cellIndentation: record.level + 1 + }; + recordsWithSummary.push(summaryRecord); + } + } + return recordsWithSummary; + } + +} diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts index d217fc4d5dd..275b492aa74 100644 --- a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts @@ -1,6 +1,6 @@ import { Component, TemplateRef, ViewChild, Input } from '@angular/core'; import { IgxGridCellComponent } from '../grids/cell.component'; -import { IgxDateSummaryOperand, IgxNumberSummaryOperand } from '../grids/grid-summary'; +import { IgxDateSummaryOperand, IgxNumberSummaryOperand, IgxSummaryResult } from '../grids/summaries/grid-summary'; import { IGridCellEventArgs, IGridEditEventArgs } from '../grids/grid-base.component'; import { BasicGridComponent, BasicGridSearchComponent, GridAutoGenerateComponent, GridNxMComponent, GridWithSizeComponent, PagingComponent } from './grid-base-components.spec'; @@ -206,7 +206,39 @@ export class ScrollsComponent extends BasicGridComponent { export class SummariesComponent extends BasicGridComponent { data = SampleTestData.foodProductData(); } -/* Maybe add SummaryColumnComponent? */ + +class DealsSummaryMinMax extends IgxNumberSummaryOperand { + constructor() { + super(); + } + + public operate(summaries?: any[]): IgxSummaryResult[] { + const result = super.operate(summaries).filter((obj) => { + if (obj.key === 'min' || obj.key === 'max') { + const summaryResult = obj.summaryResult; + // apply formatting to float numbers + if (Number(summaryResult) === summaryResult) { + obj.summaryResult = summaryResult.toLocaleString('en-us', { maximumFractionDigits: 2 }); + } + return obj; + } + }); + return result; + } +} +@Component({ + template: GridTemplateStrings.declareGrid( + ` [primaryKey]="'ProductID'" [allowFiltering]="true"`, + '', ColumnDefinitions.productDefaultSummaries) +}) +export class SummaryColumnComponent extends BasicGridComponent { + data = SampleTestData.foodProductData(); + public hasSummary = true; + + public numberSummary = new IgxNumberSummaryOperand(); + public dateSummary = new IgxDateSummaryOperand(); + public dealsSummaryMinMax = DealsSummaryMinMax; +} @Component({ template: GridTemplateStrings.declareGrid( @@ -234,7 +266,6 @@ export class VirtualSummaryColumnComponent extends BasicGridComponent { } -/* NoActiveSummariesComponent */ @Component({ template: GridTemplateStrings.declareBasicGridWithColumns(ColumnDefinitions.productBasic) }) @@ -709,3 +740,52 @@ export class GridWithAvatarComponent extends GridWithSizeComponent { data = SampleTestData.personAvatarData(); height = '500px'; } + + +@Component({ + template: `${GridTemplateStrings.declareGrid(`height="1000px" width="900px" [primaryKey]="'ID'"`, '', + ColumnDefinitions.summariesGoupByColumns)}` +}) +export class SummarieGroupByComponent extends BasicGridComponent { + public data = SampleTestData.employeeGroupByData(); + public calculationMode = 'rootAndChildLevels'; + public ageSummary = AgeSummary; + public ageSummaryTest = AgeSummaryTest; +} + +class AgeSummary extends IgxNumberSummaryOperand { + constructor() { + super(); + } + + public operate(summaries?: any[]): IgxSummaryResult[] { + const result = super.operate(summaries).filter((obj) => { + if (obj.key === 'average' || obj.key === 'sum' || obj.key === 'count') { + const summaryResult = obj.summaryResult; + // apply formatting to float numbers + if (Number(summaryResult) === summaryResult) { + obj.summaryResult = summaryResult.toLocaleString('en-us', { maximumFractionDigits: 2 }); + } + return obj; + } + }); + return result; + } +} + +class AgeSummaryTest extends IgxNumberSummaryOperand { + constructor() { + super(); + } + + public operate(summaries?: any[]): IgxSummaryResult[] { + const result = super.operate(summaries); + result.push({ + key: 'test', + label: 'Test', + summaryResult: summaries.filter(rec => rec > 10 && rec < 40).length + }); + + return result; + } +} diff --git a/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts b/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts index fff6e5dd401..4d041aaf9d1 100644 --- a/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts @@ -9,7 +9,7 @@ export class HelperUtils { public static getCheckboxElement(name: string, element: DebugElement, fix) { const checkboxElements = element.queryAll(By.css('igx-checkbox')); const chkElement = checkboxElements.find((el) => - (el.context as IgxCheckboxComponent).placeholderLabel.nativeElement.innerText === name); + (el.context as IgxCheckboxComponent).placeholderLabel.nativeElement.innerText === name); return chkElement; } @@ -38,7 +38,6 @@ export class HelperUtils { expect(chkInput.checked).toBe(isChecked); } - public static clearOverlay() { const overlays = document.getElementsByClassName('igx-overlay') as HTMLCollectionOf; Array.from(overlays).forEach(element => { @@ -52,46 +51,98 @@ export class HelperUtils { grid: IgxGridComponent, rowStartIndex: number, rowEndIndex: number, - colIndex?: number) => new Promise(async(resolve, reject) => { - const dir = rowStartIndex > rowEndIndex ? 'ArrowUp' : 'ArrowDown'; - const row = grid.getRowByIndex(rowStartIndex); - const cIndx = colIndex || 0; - const colKey = grid.columnList.toArray()[cIndx].field; - let nextRow = dir === 'ArrowUp' ? grid.getRowByIndex(rowStartIndex - 1) : grid.getRowByIndex(rowStartIndex + 1); - const elem = row instanceof IgxGridGroupByRowComponent ? - row : grid.getCellByColumn(row.index, colKey); - if (rowStartIndex === rowEndIndex) { - if (!elem.focused) { - elem.nativeElement.focus(); + colIndex?: number) => new Promise(async (resolve, reject) => { + const dir = rowStartIndex > rowEndIndex ? 'ArrowUp' : 'ArrowDown'; + const row = grid.getRowByIndex(rowStartIndex); + const cIndx = colIndex || 0; + const colKey = grid.columnList.toArray()[cIndx].field; + let nextRow = dir === 'ArrowUp' ? grid.getRowByIndex(rowStartIndex - 1) : grid.getRowByIndex(rowStartIndex + 1); + const elem = row instanceof IgxGridGroupByRowComponent ? + row : grid.getCellByColumn(row.index, colKey); + if (rowStartIndex === rowEndIndex) { + if (!elem.focused) { + elem.nativeElement.focus(); + } + resolve(); + return; } - resolve(); - return; - } - const keyboardEvent = new KeyboardEvent('keydown', { - code: dir, - key: dir - }); + const keyboardEvent = new KeyboardEvent('keydown', { + code: dir, + key: dir + }); - if (dir === 'ArrowDown') { - elem.nativeElement.dispatchEvent(keyboardEvent); - } else { - elem.nativeElement.dispatchEvent(keyboardEvent); - } + if (dir === 'ArrowDown') { + elem.nativeElement.dispatchEvent(keyboardEvent); + } else { + elem.nativeElement.dispatchEvent(keyboardEvent); + } - if (nextRow) { - await wait(10); - HelperUtils.navigateVerticallyToIndex(grid, nextRow.index, rowEndIndex, colIndex) - .then(() => { resolve(); }); - } else { - // else wait for chunk to load. - grid.verticalScrollContainer.onChunkLoad.pipe(take(1)).subscribe({ - next: async() => { - nextRow = dir === 'ArrowUp' ? grid.getRowByIndex(rowStartIndex - 1) : grid.getRowByIndex(rowStartIndex + 1); - HelperUtils.navigateVerticallyToIndex(grid, nextRow.index, rowEndIndex, colIndex) + if (nextRow) { + await wait(10); + HelperUtils.navigateVerticallyToIndex(grid, nextRow.index, rowEndIndex, colIndex) .then(() => { resolve(); }); + } else { + // else wait for chunk to load. + grid.verticalScrollContainer.onChunkLoad.pipe(take(1)).subscribe({ + next: async () => { + nextRow = dir === 'ArrowUp' ? grid.getRowByIndex(rowStartIndex - 1) : grid.getRowByIndex(rowStartIndex + 1); + HelperUtils.navigateVerticallyToIndex(grid, nextRow.index, rowEndIndex, colIndex) + .then(() => { resolve(); }); + } + }); + } + }) + + public static verifyColumnSummaries(summaryRow: DebugElement, summaryIndex: number, summaryLabels, summaryResults) { + const summary = summaryRow.query(By.css('igx-grid-summary-cell[data-visibleindex="' + summaryIndex + '"]')); + expect(summary).toBeDefined(); + const summaryItems = summary.queryAll(By.css('.igx-grid-summary__item')); + if (summaryLabels.length === 0) { + expect(summary.nativeElement.classList.contains('igx-grid-summary--empty')).toBeTruthy(); + expect(summaryItems.length).toBe(0); + } else { + expect(summary.nativeElement.classList.contains('igx-grid-summary--empty')).toBeFalsy(); + expect(summaryItems.length).toEqual(summaryLabels.length); + if (summaryItems.length === summaryLabels.length) { + for (let i = 0; i < summaryLabels.length; i++) { + const summaryItem = summaryItems[i]; + const summaryLabel = summaryItem.query(By.css('.igx-grid-summary__label')); + expect(summaryLabels[i]).toEqual(summaryLabel.nativeElement.textContent.trim()); + if (summaryResults.length > 0) { + const summaryResult = summaryItem.query(By.css('.igx-grid-summary__result')); + expect(summaryResults[i]).toEqual(summaryResult.nativeElement.textContent.trim()); + } } - }); + } } + } + + public static getSummaryRowByDataRowIndex(fix, rowIndex: number) { + return fix.debugElement.query(By.css('igx-grid-summary-row[data-rowindex="' + rowIndex + '"]')); + } - }) + public static getAllVisbleSummariesLength(fix) { + return HelperUtils.getAllVisbleSummaries(fix).length; + } + + public static getAllVisbleSummariesRowIndexes(fix) { + const summaries = HelperUtils.getAllVisbleSummaries(fix); + const rowIndexes = []; + summaries.forEach(summary => { + rowIndexes.push(Number(summary.attributes['data-rowIndex'])); + }); + return rowIndexes.sort((a: number, b: number) => a - b); + } + + public static getAllVisbleSummaries(fix) { + return fix.debugElement.queryAll(By.css('igx-grid-summary-row')); + } + + public static verifyVisbleSummariesHeight(fix, summariesRows, rowHeight = 50) { + const visibleSummaries = HelperUtils.getAllVisbleSummaries(fix); + visibleSummaries.forEach(summary => { + expect(summary.nativeElement.getBoundingClientRect().height).toBeGreaterThanOrEqual(summariesRows * rowHeight); + expect(summary.nativeElement.getBoundingClientRect().height).toBeLessThanOrEqual(summariesRows * rowHeight + 1); + }); + } } diff --git a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts index a0d5d36a59f..6f83eb33420 100644 --- a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts @@ -5,8 +5,8 @@ import { ValueData } from '../services/excel/test-data.service.spec'; export class SampleTestData { - private static timeGenerator: Calendar = new Calendar(); - private static today: Date = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0); + public static timeGenerator: Calendar = new Calendar(); + public static today: Date = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0); // tslint:disable:quotemark public static stringArray = () => ([ @@ -46,14 +46,14 @@ export class SampleTestData { /* Fields: index: number, value: number; 2 items. */ public static numberDataTwoFields = () => ([ - { index: 1, value: 1}, - { index: 2, value: 2} + { index: 1, value: 1 }, + { index: 2, value: 2 } ]) /* Fields: index: number, value: number, other: number, another: number; 2 items. */ public static numberDataFourFields = () => ([ - { index: 1, value: 1, other: 1, another: 1}, - { index: 2, value: 2, other: 2, another: 2} + { index: 1, value: 1, other: 1, another: 1 }, + { index: 2, value: 2, other: 2, another: 2 } ]) /* Fields: Number: number, String: string, Boolean: boolean; Date: date; 3 items. */ @@ -152,7 +152,7 @@ export class SampleTestData { public static personIDNameRegionData = () => ([ { ID: 2, Name: "Jane", LastName: "Brown", Region: "AD" }, { ID: 1, Name: "Brad", LastName: "Williams", Region: "BD" }, - { ID: 6, Name: "Rick", LastName: "Jones", Region: "ACD"}, + { ID: 6, Name: "Rick", LastName: "Jones", Region: "ACD" }, { ID: 7, Name: "Rick", LastName: "BRown", Region: "DD" }, { ID: 5, Name: "ALex", LastName: "Smith", Region: "MlDs" }, { ID: 4, Name: "Alex", LastName: "Wilson", Region: "DC" }, @@ -446,7 +446,7 @@ export class SampleTestData { Released: null }, { - Downloads: 0, + Downloads: 1, ID: 7, ProductName: null, ReleaseDate: SampleTestData.timeGenerator.timedelta(SampleTestData.today, "month", 1), @@ -608,7 +608,7 @@ export class SampleTestData { const data = []; for (let i = 0; i < rowsCount; i++) { const obj = {}; - for (let j = 0; j < cols.length; j++) { + for (let j = 0; j < cols.length; j++) { const col = cols[j].field; obj[col] = 10 * i * j; } @@ -1095,33 +1095,258 @@ export class SampleTestData { } ]) - public static employeePrimaryForeignKeyTreeData = () => ([ + public static employeeTreeDataDisplayOrder = () => ([ { ID: 1, ParentID: -1, Name: 'Casey Houston', JobTitle: 'Vice President', Age: 32 }, { ID: 2, ParentID: 1, Name: 'Gilberto Todd', JobTitle: 'Director', Age: 41 }, { ID: 3, ParentID: 2, Name: 'Tanya Bennett', JobTitle: 'Director', Age: 29 }, + { ID: 7, ParentID: 2, Name: 'Debra Morton', JobTitle: 'Associate Software Developer', Age: 35 }, { ID: 4, ParentID: 1, Name: 'Jack Simon', JobTitle: 'Software Developer', Age: 33 }, { ID: 6, ParentID: -1, Name: 'Erma Walsh', JobTitle: 'CEO', Age: 52 }, - { ID: 7, ParentID: 2, Name: 'Debra Morton', JobTitle: 'Associate Software Developer', Age: 35 }, - { ID: 9, ParentID: 10, Name: 'Leslie Hansen', JobTitle: 'Associate Software Developer', Age: 44 }, - { ID: 10, ParentID: -1, Name: 'Eduardo Ramirez', JobTitle: 'Manager', Age: 53 } + { ID: 10, ParentID: -1, Name: 'Eduardo Ramirez', JobTitle: 'Manager', Age: 53 }, + { ID: 9, ParentID: 10, Name: 'Leslie Hansen', JobTitle: 'Associate Software Developer', Age: 44 } ]) - public static employeeTreeDataDisplayOrder = () => ([ + public static employeePrimaryForeignKeyTreeData = () => ([ { ID: 1, ParentID: -1, Name: 'Casey Houston', JobTitle: 'Vice President', Age: 32 }, { ID: 2, ParentID: 1, Name: 'Gilberto Todd', JobTitle: 'Director', Age: 41 }, { ID: 3, ParentID: 2, Name: 'Tanya Bennett', JobTitle: 'Director', Age: 29 }, - { ID: 7, ParentID: 2, Name: 'Debra Morton', JobTitle: 'Associate Software Developer', Age: 35 }, { ID: 4, ParentID: 1, Name: 'Jack Simon', JobTitle: 'Software Developer', Age: 33 }, { ID: 6, ParentID: -1, Name: 'Erma Walsh', JobTitle: 'CEO', Age: 52 }, - { ID: 10, ParentID: -1, Name: 'Eduardo Ramirez', JobTitle: 'Manager', Age: 53 }, - { ID: 9, ParentID: 10, Name: 'Leslie Hansen', JobTitle: 'Associate Software Developer', Age: 44 } + { ID: 7, ParentID: 2, Name: 'Debra Morton', JobTitle: 'Associate Software Developer', Age: 35 }, + { ID: 9, ParentID: 10, Name: 'Leslie Hansen', JobTitle: 'Associate Software Developer', Age: 44 }, + { ID: 10, ParentID: -1, Name: 'Eduardo Ramirez', JobTitle: 'Manager', Age: 53 } + ]) + + public static employeeTreeDataPrimaryForeignKey = () => ([ + { + ID: 147, + ParentID: -1, + Name: 'John Winchester', + HireDate: new Date(2008, 3, 20), + Age: 55, + OnPTO: false + }, + { + ID: 475, + ParentID: 147, + Name: 'Michael Langdon', + HireDate: new Date(2011, 6, 3), + Age: 43, + OnPTO: false, + Employees: null + }, + { + ID: 957, + ParentID: 147, + Name: 'Thomas Hardy', + HireDate: new Date(2009, 6, 19), + Age: 29, + OnPTO: true, + Employees: undefined + }, + { + ID: 317, + ParentID: 147, + Name: 'Monica Reyes', + HireDate: new Date(2014, 8, 18), + Age: 31, + OnPTO: false + }, + { + ID: 711, + ParentID: 317, + Name: 'Roland Mendel', + HireDate: new Date(2015, 9, 17), + Age: 35, + OnPTO: true, + }, + { + ID: 998, + ParentID: 317, + Name: 'Sven Ottlieb', + HireDate: new Date(2009, 10, 11), + Age: 44, + OnPTO: false, + }, + { + ID: 847, + ParentID: -1, + Name: 'Ana Sanders', + HireDate: new Date(2014, 1, 22), + Age: 42, + OnPTO: false + }, + { + ID: 225, + ParentID: 847, + Name: 'Laurence Johnson', + HireDate: new Date(2014, 4, 4), + OnPTO: true, + Age: 44, + }, + { + ID: 663, + ParentID: 847, + Name: 'Elizabeth Richards', + HireDate: new Date(2017, 11, 9), + Age: 25, + OnPTO: false + }, + + { + ID: 141, + ParentID: 663, + Name: 'Trevor Ashworth', + HireDate: new Date(2010, 3, 22), + OnPTO: false, + Age: 39 + }, + { + ID: 19, + ParentID: -1, + Name: 'Victoria Lincoln', + HireDate: new Date(2014, 1, 22), + Age: 49, + OnPTO: false + }, + { + ID: 15, + ParentID: 19, + Name: 'Antonio Moreno', + HireDate: new Date(2014, 4, 4), + Age: 44, + OnPTO: true, + Employees: [] + }, + { + ID: 17, + ParentID: -1, + Name: 'Yang Wang', + HireDate: new Date(2010, 1, 1), + Age: 61, + OnPTO: false + }, + { + ID: 12, + ParentID: 17, + Name: 'Pedro Afonso', + HireDate: new Date(2007, 11, 18), + Age: 50, + OnPTO: false + }, + { + ID: 101, + ParentID: 12, + Name: 'Patricio Simpson', + HireDate: new Date(2017, 11, 9), + Age: 25, + OnPTO: false, + Employees: [] + }, + { + ID: 99, + ParentID: 12, + Name: 'Francisco Chang', + HireDate: new Date(2010, 3, 22), + OnPTO: true, + Age: 39 + }, + { + ID: 299, + ParentID: 12, + Name: 'Peter Lewis', + HireDate: new Date(2018, 3, 18), + OnPTO: false, + Age: 25 + }, + { + ID: 101, + ParentID: 17, + Name: 'Casey Harper', + HireDate: new Date(2016, 2, 19), + OnPTO: false, + Age: 27 + } ]) + + public static employeeGroupByData = () => ([ + { + ID: 475, + ParentID: 147, + Name: 'Michael Langdon', + HireDate: new Date(2011, 6, 3), + Age: 43, + OnPTO: false, + Employees: null + }, + { + ID: 957, + ParentID: 147, + Name: 'Thomas Hardy', + HireDate: new Date(2009, 6, 19), + Age: 29, + OnPTO: true, + Employees: undefined + }, + { + ID: 317, + ParentID: 147, + Name: 'Monica Reyes', + HireDate: new Date(2014, 8, 18), + Age: 31, + OnPTO: false + }, + { + ID: 225, + ParentID: 847, + Name: 'Laurence Johnson', + HireDate: new Date(2014, 4, 4), + OnPTO: true, + Age: 44, + }, + { + ID: 663, + ParentID: 847, + Name: 'Elizabeth Richards', + HireDate: new Date(2017, 11, 9), + Age: 25, + OnPTO: false + }, + + { + ID: 15, + ParentID: 19, + Name: 'Antonio Moreno', + HireDate: new Date(2014, 4, 4), + Age: 44, + OnPTO: true, + Employees: [] + }, + { + ID: 12, + ParentID: 17, + Name: 'Pedro Afonso', + HireDate: new Date(2007, 11, 18), + Age: 50, + OnPTO: false + }, + { + ID: 101, + ParentID: 17, + Name: 'Casey Harper', + HireDate: new Date(2016, 2, 19), + OnPTO: false, + Age: 27 + } + ]) + + /** * Generates simple array of primitve values * @param rows Number of items to add to the array * @param type The type of the items */ - public static generateListOfPrimitiveValues(rows: number, type: Number|String|Boolean): any[] { + public static generateListOfPrimitiveValues(rows: number, type: Number | String | Boolean): any[] { const data: any[] = []; for (let row = 0; row < rows; row++) { if (type === 'Number') { @@ -1153,7 +1378,7 @@ export class SampleTestData { } } - // tslint:enable:quotemark +// tslint:enable:quotemark export class DataParent { public today: Date = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0); diff --git a/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts b/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts index 4659d8b60c7..b8098c00029 100644 --- a/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts @@ -390,6 +390,15 @@ export class ColumnDefinitions { `; + + public static summariesGoupByColumns = ` + + + + + + + `; } export class EventSubscriptions { diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts index 6c3dc147d23..f74d1130cb5 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts @@ -1,6 +1,7 @@ import { Component, ViewChild } from '@angular/core'; import { IgxTreeGridComponent } from '../grids/tree-grid/tree-grid.component'; import { SampleTestData } from './sample-test-data.spec'; +import { IgxNumberSummaryOperand, IgxSummaryResult } from '../grids'; import { IgxTransactionService, IgxHierarchicalTransactionService } from '../../public_api'; import { IgxGridTransaction } from '../grids/grid-base.component'; @@ -260,6 +261,83 @@ export class IgxTreeGridMultiColHeadersComponent { @ViewChild(IgxTreeGridComponent) public treeGrid: IgxTreeGridComponent; public data = SampleTestData.employeeSmallTreeData(); } + + +@Component({ + template: ` + + + + + + + + ` +}) +export class IgxTreeGridSummariesComponent { + @ViewChild(IgxTreeGridComponent) public treeGrid: IgxTreeGridComponent; + public data = SampleTestData.employeeTreeData(); + public ageSummary = AgeSummary; + public ageSummaryTest = AgeSummaryTest; +} + +@Component({ + template: ` + + + + + + + + ` +}) +export class IgxTreeGridSummariesKeyComponent { + @ViewChild(IgxTreeGridComponent) public treeGrid: IgxTreeGridComponent; + public data = SampleTestData.employeeTreeDataPrimaryForeignKey(); + public calculationMode = 'rootAndChildLevels'; + public ageSummary = AgeSummary; + public ageSummaryTest = AgeSummaryTest; +} + +class AgeSummary extends IgxNumberSummaryOperand { + constructor() { + super(); + } + + public operate(summaries?: any[]): IgxSummaryResult[] { + const result = super.operate(summaries).filter((obj) => { + if (obj.key === 'average' || obj.key === 'sum' || obj.key === 'count') { + const summaryResult = obj.summaryResult; + // apply formatting to float numbers + if (Number(summaryResult) === summaryResult) { + obj.summaryResult = summaryResult.toLocaleString('en-us', { maximumFractionDigits: 2 }); + } + return obj; + } + }); + return result; + } +} + +class AgeSummaryTest extends IgxNumberSummaryOperand { + constructor() { + super(); + } + + public operate(summaries?: any[]): IgxSummaryResult[] { + const result = super.operate(summaries); + result.push({ + key: 'test', + label: 'Test', + summaryResult: summaries.filter(rec => rec > 10 && rec < 40).length + }); + + return result; + } +} + @Component({ template: ` @@ -279,7 +357,24 @@ export class IgxTreeGridRowEditingTransactionComponent { @Component({ template: ` - + + + + + + + ` +}) +export class IgxTreeGridCustomSummariesComponent { + @ViewChild(IgxTreeGridComponent) public treeGrid: IgxTreeGridComponent; + public data = SampleTestData.employeeTreeData(); + public ageSummary = AgeSummary; + public ageSummaryTest = AgeSummaryTest; +} + +@Component({ + template: ` diff --git a/src/app/grid-cellEditing/grid-cellEditing.component.html b/src/app/grid-cellEditing/grid-cellEditing.component.html index 3e2387f965f..47c3b26215a 100644 --- a/src/app/grid-cellEditing/grid-cellEditing.component.html +++ b/src/app/grid-cellEditing/grid-cellEditing.component.html @@ -13,11 +13,11 @@

Grid with primary key ProductID

- + - + @@ -33,6 +33,7 @@

Grid with primary key ProductID

+

Grid without PK

diff --git a/src/app/grid-cellEditing/grid-cellEditing.component.ts b/src/app/grid-cellEditing/grid-cellEditing.component.ts index af07f1db291..e7ebb1fac0f 100644 --- a/src/app/grid-cellEditing/grid-cellEditing.component.ts +++ b/src/app/grid-cellEditing/grid-cellEditing.component.ts @@ -49,6 +49,14 @@ export class GridCellEditingComponent { }); } + enDisSummaries() { + if (this.gridWithPK.getColumnByName('ReorderLevel').hasSummary) { + this.gridWithPK.disableSummaries([{ fieldName: 'ReorderLevel' }]); + } else { + this.gridWithPK.enableSummaries([{ fieldName: 'ReorderLevel' }]); + } + } + public deleteRow(event, rowID) { event.stopPropagation(); const row = this.gridWithPK.getRowByKey(rowID); diff --git a/src/app/grid-column-moving/grid-column-moving.sample.html b/src/app/grid-column-moving/grid-column-moving.sample.html index 2b1ce8971cc..bd913fad11a 100644 --- a/src/app/grid-column-moving/grid-column-moving.sample.html +++ b/src/app/grid-column-moving/grid-column-moving.sample.html @@ -15,7 +15,7 @@ [rowSelectable]="true" [paging]="false" [width]="'900px'" - [height]="'500px'"> + [height]="'800px'"> diff --git a/src/app/grid-column-moving/grid-column-moving.sample.ts b/src/app/grid-column-moving/grid-column-moving.sample.ts index b7e23ff4e07..8b3b04dc2c0 100644 --- a/src/app/grid-column-moving/grid-column-moving.sample.ts +++ b/src/app/grid-column-moving/grid-column-moving.sample.ts @@ -18,7 +18,7 @@ export class GridColumnMovingSampleComponent implements OnInit { public ngOnInit(): void { this.columns = [ - { field: 'ID', width: 40, resizable: true, movable: true }, + { field: 'ID', width: 80, resizable: true, movable: true }, { field: 'CompanyName', width: 150, resizable: true, movable: true, type: 'string'}, { field: 'ContactName', width: 150, resizable: true, movable: true, type: 'string' }, { field: 'ContactTitle', width: 150, resizable: true, movable: true, type: 'string' }, diff --git a/src/app/grid-groupby/grid-groupby.sample.html b/src/app/grid-groupby/grid-groupby.sample.html index 309564abc22..a4baaf7012a 100644 --- a/src/app/grid-groupby/grid-groupby.sample.html +++ b/src/app/grid-groupby/grid-groupby.sample.html @@ -1,10 +1,13 @@
Toggle Hiding Of Grouped Columns
+
+ +
+ [height]="'700px'" [rowSelectable]='true' [summaryCalculationMode]="summaryMode"> + [hidden]='c.hidden' [sortable]='true' [groupable]='c.groupable' [movable]='true' [pinned]='!!c.pinned' [editable]="true" [filterable]="true" [hasSummary]="true" [dataType]='c.dataType'>