From cc147bfb840c3390a62b89aa33595b250189c82e Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Thu, 10 Jan 2019 12:58:00 +0200 Subject: [PATCH 1/5] feat(igx-grid): Expose column templates as inputs Closes #3562 --- CHANGELOG.md | 15 ++++++ .../src/lib/grids/column.component.ts | 10 ++-- .../src/lib/grids/grid/column.spec.ts | 52 +++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 483b26f85fb..5d0b49b7eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes for each version of this project will be documented in this ### Features - `igx-circular-bar` and `igx-linear-bar` now feature an indeterminate input property. When this property is set to true the indicator will be continually growing and shrinking along the track. - `IgxTimePickerComponent`: in addition to the current dialog interaction mode, now the user can select or edit a time value, using an editable masked input with a dropdown. +- `IgxColumnComponent` now accepts its templates as input properties through the markup. This can reduce the amount of code one needs to write when applying a single template to multiple columns declaratively. The new exposed inputs are: + + `cellTemplate` - the template for the column cells + + `headerTemplate` - the template for the column header + + `cellEditorTemplate` - the template for the column cells when a cell is in edit mode + + ```html + + + + + + + + {{ value }} + + ``` ## 7.1.1 ### Bug Fixes diff --git a/projects/igniteui-angular/src/lib/grids/column.component.ts b/projects/igniteui-angular/src/lib/grids/column.component.ts index 0097d33553c..219b6b51818 100644 --- a/projects/igniteui-angular/src/lib/grids/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/column.component.ts @@ -33,6 +33,7 @@ import { import { IgxGridBaseComponent } from './grid-base.component'; import { FilteringExpressionsTree } from '../data-operations/filtering-expressions-tree'; import { IgxGridFilteringCellComponent } from './filtering/grid-filtering-cell.component'; +import { noop } from 'rxjs'; /** * **Ignite UI for Angular Column** - @@ -579,6 +580,7 @@ export class IgxColumnComponent implements AfterContentInit { * ``` * @memberof IgxColumnComponent */ + @Input('cellTemplate') get bodyTemplate(): TemplateRef { return this._bodyTemplate; } @@ -600,7 +602,7 @@ export class IgxColumnComponent implements AfterContentInit { */ set bodyTemplate(template: TemplateRef) { this._bodyTemplate = template; - this.grid.markForCheck(); + this.grid ? this.grid.cdr.markForCheck() : noop(); } /** * Returns a reference to the header template. @@ -609,6 +611,7 @@ export class IgxColumnComponent implements AfterContentInit { * ``` * @memberof IgxColumnComponent */ + @Input() get headerTemplate(): TemplateRef { return this._headerTemplate; } @@ -630,7 +633,7 @@ export class IgxColumnComponent implements AfterContentInit { */ set headerTemplate(template: TemplateRef) { this._headerTemplate = template; - this.grid.markForCheck(); + this.grid ? this.grid.cdr.markForCheck() : noop(); } /** * Returns a reference to the inline editor template. @@ -639,6 +642,7 @@ export class IgxColumnComponent implements AfterContentInit { * ``` * @memberof IgxColumnComponent */ + @Input('cellEditorTemplate') get inlineEditorTemplate(): TemplateRef { return this._inlineEditorTemplate; } @@ -658,7 +662,7 @@ export class IgxColumnComponent implements AfterContentInit { */ set inlineEditorTemplate(template: TemplateRef) { this._inlineEditorTemplate = template; - this.grid.markForCheck(); + this.grid ? this.grid.cdr.markForCheck() : noop(); } /** * Gets the cells of the column. diff --git a/projects/igniteui-angular/src/lib/grids/grid/column.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/column.spec.ts index 42c22b7d921..5a754219d23 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/column.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/column.spec.ts @@ -20,6 +20,7 @@ describe('IgxGrid - Column properties', () => { declarations: [ ColumnsFromIterableComponent, TemplatedColumnsComponent, + TemplatedInputColumnsComponent, ColumnCellFormatterComponent, ColumnHaederClassesComponent, ColumnHiddenFromMarkupComponent @@ -247,6 +248,26 @@ describe('IgxGrid - Column properties', () => { expect(grid.columns[0].width).toBe('300'); expect(grid.columns[1].width).toBe('300'); }); + + it('should support passing templates through the markup as an input property', () => { + const fixture = TestBed.createComponent(TemplatedInputColumnsComponent); + fixture.detectChanges(); + + const grid = fixture.componentInstance.instance; + + grid.getColumnByName('Name').cells.forEach(c => + expect(c.nativeElement.querySelector('.customCellTemplate')).toBeDefined()); + + grid.headerCellList.forEach(header => + expect(header.elementRef.nativeElement.querySelector('.customHeaderTemplate')).toBeDefined()); + + const cell = grid.getCellByColumn(0, 'ID'); + cell.inEditMode = true; + fixture.detectChanges(); + + expect(cell.nativeElement.querySelector('.customEditorTemplate')).toBeDefined(); + + }); }); @Component({ @@ -284,6 +305,37 @@ export class TemplatedColumnsComponent { public newCellTemplate: TemplateRef; } +@Component({ + template: ` + + + + + + + {{ value }} + + + + {{ column.field }} + + + + {{ value }} + + ` +}) +export class TemplatedInputColumnsComponent { + + data = SampleTestData.personIDNameRegionData(); + columns = Object.keys(this.data[0]); + + @ViewChild(IgxGridComponent, { read: IgxGridComponent }) + public instance: IgxGridComponent; +} + @Component({ template: ` From 53755cbcf50ec5a4f58b682e0771001655eac67e Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Thu, 10 Jan 2019 16:18:44 +0200 Subject: [PATCH 2/5] refactor(*): Address review comments --- .../src/lib/grids/column.component.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/column.component.ts b/projects/igniteui-angular/src/lib/grids/column.component.ts index 219b6b51818..b780f402926 100644 --- a/projects/igniteui-angular/src/lib/grids/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/column.component.ts @@ -33,7 +33,6 @@ import { import { IgxGridBaseComponent } from './grid-base.component'; import { FilteringExpressionsTree } from '../data-operations/filtering-expressions-tree'; import { IgxGridFilteringCellComponent } from './filtering/grid-filtering-cell.component'; -import { noop } from 'rxjs'; /** * **Ignite UI for Angular Column** - @@ -602,7 +601,9 @@ export class IgxColumnComponent implements AfterContentInit { */ set bodyTemplate(template: TemplateRef) { this._bodyTemplate = template; - this.grid ? this.grid.cdr.markForCheck() : noop(); + if (this.grid) { + this.grid.cdr.markForCheck(); + } } /** * Returns a reference to the header template. @@ -633,7 +634,9 @@ export class IgxColumnComponent implements AfterContentInit { */ set headerTemplate(template: TemplateRef) { this._headerTemplate = template; - this.grid ? this.grid.cdr.markForCheck() : noop(); + if (this.grid) { + this.grid.cdr.markForCheck(); + } } /** * Returns a reference to the inline editor template. @@ -662,7 +665,9 @@ export class IgxColumnComponent implements AfterContentInit { */ set inlineEditorTemplate(template: TemplateRef) { this._inlineEditorTemplate = template; - this.grid ? this.grid.cdr.markForCheck() : noop(); + if (this.grid) { + this.grid.cdr.markForCheck(); + } } /** * Gets the cells of the column. From c149128245ed070be778243479fd7a88d0a547e1 Mon Sep 17 00:00:00 2001 From: Stefan Stoyanov <34062094+sstoyanovIG@users.noreply.github.com> Date: Fri, 11 Jan 2019 11:01:06 +0200 Subject: [PATCH 3/5] Make default locale to be 'en' (#3574) --- CHANGELOG.md | 2 +- .../igniteui-angular/src/lib/calendar/calendar.component.ts | 6 +++--- .../src/lib/date-picker/date-picker.component.ts | 4 ++-- projects/igniteui-angular/src/lib/grids/README.md | 2 +- .../igniteui-angular/src/lib/grids/grid-base.component.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0b49b7eba..088e9491748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ All notable changes for each version of this project will be documented in this - Added a new `igxToolbarCustomContent` directive which can be used to mark an `ng-template` which provides a custom content for the IgxGrid's toolbar ([#2983](https://github.com/IgniteUI/igniteui-angular/issues/2983)) - Summary results are now calculated and displayed by default for each row group when 'Group By' feature is enabled. - `clearSummaryCache()` and `recalculateSummaries()` methods are deprecated. The grid will clear the cache and recalculate the summaries automatically when needed. - - `locale` property added. If not set, it returns browser's language. All child components will use it as locale. + - `locale` property added. Default value is `en`. All child components will use it as locale. - **Breaking change** `IgxSummaryOperand.operate()` method is called with empty data in order to calculate the necessary height for the summary row. For custom summary operands, the method should always return an array of `IgxSummaryResult` with proper length. - `IgxIconModule`: - **Breaking change** `igxIconService` is now provided in root (providedIn: 'root') and `IgxIconModule.forRoot()` method is deprecated. diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.component.ts b/projects/igniteui-angular/src/lib/calendar/calendar.component.ts index fab1af6b0dc..5bb9d3abe59 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.component.ts @@ -142,7 +142,7 @@ export class IgxCalendarComponent implements OnInit, ControlValueAccessor { /** * Gets the `locale` of the calendar. - * By default the browser's language is used. + * Default value is `"en"`. * ```typescript * let locale = this.calendar.locale; * ``` @@ -156,7 +156,7 @@ export class IgxCalendarComponent implements OnInit, ControlValueAccessor { /** * Sets the `locale` of the calendar. * Expects a valid BCP 47 language tag. - * By default the browser's language is used. + * Default value is `"en"`. * ```html * * ``` @@ -610,7 +610,7 @@ export class IgxCalendarComponent implements OnInit, ControlValueAccessor { /** *@hidden */ - private _locale = window.navigator.language; + private _locale = 'en'; /** *@hidden */ diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts index da2da2d7ca1..d7fe3b71e6a 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.ts @@ -146,12 +146,12 @@ export class IgxDatePickerComponent implements ControlValueAccessor, EditorProvi public labelVisibility = true; /** - *An @Input property that sets locales. By default the browser's language is used. + *An @Input property that sets locales. Default locale is en. *```html * *``` */ - @Input() public locale: string = window.navigator.language; + @Input() public locale: 'en'; /** *An @Input property that sets on which day the week starts. diff --git a/projects/igniteui-angular/src/lib/grids/README.md b/projects/igniteui-angular/src/lib/grids/README.md index 93f3e07a04a..f57425913c8 100644 --- a/projects/igniteui-angular/src/lib/grids/README.md +++ b/projects/igniteui-angular/src/lib/grids/README.md @@ -186,7 +186,7 @@ Below is the list of all inputs that the developers may set to configure the gri |`transactions`| `TransactionService` | Transaction provider allowing access to all transactions and states of the modified rows. | |`summaryPosition`| GridSummaryPosition | The summary row position for the child levels. The default is top. | |`summaryCalculationMode`| GridSummaryCalculationMode | The summary calculation mode. The default is rootAndChildLevels, which means summaries are calculated for root and child levels. | -|`locale`| string | Determines the locale of the grid. By default returns browser's language. | +|`locale`| string | Determines the locale of the grid. Default value is `en`. | ### Outputs 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 9c2ba27092a..9a00914542d 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -302,7 +302,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements if (this._locale) { return this._locale; } else { - return window.navigator.language; + return 'en'; } } From 5fda0ff8b67cb6dea8a79c0ff5e30f47009913ef Mon Sep 17 00:00:00 2001 From: Galina Edinakova Date: Fri, 11 Jan 2019 11:50:42 +0200 Subject: [PATCH 4/5] fix(igxInput): #3550 Setting required input value updates valid state. (#3564) * test(igxInput): #3550 Test setting req input value updates valid state. * fix(igxInput): #3550 Setting required input value updates valid state. * fix(Input): #3550 Abstracted the validity check to a method. --- .../directives/input/input.directive.spec.ts | 31 +++++++++++++++++++ .../lib/directives/input/input.directive.ts | 11 +++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts index fc3d62b26c4..399cede3c4a 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts @@ -193,6 +193,37 @@ describe('IgxInput', () => { testRequiredValidation(inputElement, fixture); }); + it('Should update style when required input\'s value is set.', () => { + const fixture = TestBed.createComponent(RequiredInputComponent); + fixture.detectChanges(); + + const igxInput = fixture.componentInstance.igxInput; + const inputElement = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement; + + dispatchInputEvent('focus', inputElement, fixture); + dispatchInputEvent('blur', inputElement, fixture); + + const inputGroupElement = fixture.debugElement.query(By.css('igx-input-group')).nativeElement; + expect(inputGroupElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); + expect(igxInput.valid).toBe(IgxInputState.INVALID); + + igxInput.value = 'test'; + fixture.detectChanges(); + + expect(inputGroupElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false); + expect(igxInput.valid).toBe(IgxInputState.VALID); + + + igxInput.value = ''; + fixture.detectChanges(); + + dispatchInputEvent('focus', inputElement, fixture); + dispatchInputEvent('blur', inputElement, fixture); + + expect(inputGroupElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); + expect(igxInput.valid).toBe(IgxInputState.INVALID); + }); + it('Should style required input with two-way databinding correctly.', () => { const fixture = TestBed.createComponent(RequiredTwoWayDataBoundInputComponent); fixture.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts index e522ffe60cd..9a8dcb5b5e5 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts @@ -52,6 +52,7 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { @Input('value') set value(value: any) { this.nativeElement.value = value; + this.checkValidity(); } /** * Gets the `value` propery. @@ -144,9 +145,7 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { */ @HostListener('input') public onInput() { - if (!this.ngControl && this._hasValidators()) { - this._valid = this.nativeElement.checkValidity() ? IgxInputState.VALID : IgxInputState.INVALID; - } + this.checkValidity(); } /** *@hidden @@ -294,4 +293,10 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { public set valid(value: IgxInputState) { this._valid = value; } + + private checkValidity() { + if (!this.ngControl && this._hasValidators) { + this._valid = this.nativeElement.checkValidity() ? IgxInputState.VALID : IgxInputState.INVALID; + } + } } From b36e4b8c307e9e5c799832b1b0778990d9d588ae Mon Sep 17 00:00:00 2001 From: Maria Tsvyatkova Date: Fri, 11 Jan 2019 12:05:22 +0200 Subject: [PATCH 5/5] Adding ng-content to IgxSnackbar - master (#3548) * test(snackbar): add tests for ng-content #3328 * chore(snackbar): blank spaces update #3328 * feat(snackbar): add ng-content #3328 * docs(snackbar): update README #3328 * test(snackbar): update test for ng-content #3328 * feat(snackbar): update ng-content logic and README #3328 * test(snackbar): update tests #3328 * feat(snackbar): update the snackbar template #3328 * test(snackbar): change tests #3328 * feat(snackbar): update template #3328 * chore(snackbar): remove f from a test #3328 --- .../src/lib/snackbar/README.md | 14 +++++ .../src/lib/snackbar/snackbar.component.html | 5 +- .../lib/snackbar/snackbar.component.spec.ts | 62 ++++++++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/snackbar/README.md b/projects/igniteui-angular/src/lib/snackbar/README.md index 5ae63fae2fe..39bad9b6d8a 100644 --- a/projects/igniteui-angular/src/lib/snackbar/README.md +++ b/projects/igniteui-angular/src/lib/snackbar/README.md @@ -40,3 +40,17 @@ You can hide the Snackbar by using `snackbar.hide()` method. By default, the IgxSnackbar will be automatically hidden after 4000 milliseconds. The automatic hiding behavior can be controlled via the following attributes: - `autoHide` - whether the snackbar should be hidden after a certain time interval. - `displayTime` - the time interval in which the snackbar would hide. + + +## Snackbar with custom content + +```html + + + + Custom content + +``` +You can display custom content by adding elements inside the snackbar. diff --git a/projects/igniteui-angular/src/lib/snackbar/snackbar.component.html b/projects/igniteui-angular/src/lib/snackbar/snackbar.component.html index 3ae3e08ea1b..d9da0c044d2 100644 --- a/projects/igniteui-angular/src/lib/snackbar/snackbar.component.html +++ b/projects/igniteui-angular/src/lib/snackbar/snackbar.component.html @@ -1,6 +1,9 @@
- {{ message }} +
+ {{ message }} + +
diff --git a/projects/igniteui-angular/src/lib/snackbar/snackbar.component.spec.ts b/projects/igniteui-angular/src/lib/snackbar/snackbar.component.spec.ts index f62a31ae920..53781531af3 100644 --- a/projects/igniteui-angular/src/lib/snackbar/snackbar.component.spec.ts +++ b/projects/igniteui-angular/src/lib/snackbar/snackbar.component.spec.ts @@ -11,7 +11,8 @@ describe('IgxSnackbar', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ - SnackbarInitializeTestComponent + SnackbarInitializeTestComponent, + SnackbarCustomContentComponent ], imports: [ BrowserAnimationsModule, @@ -35,7 +36,6 @@ describe('IgxSnackbar', () => { expect(snackbar.displayTime).toBe(4000); expect(snackbar.autoHide).toBeTruthy(); expect(snackbar.isVisible).toBeFalsy(); - expect(snackbar.actionText).toBeUndefined(); expect(domSnackbar.id).toContain('igx-snackbar-'); snackbar.id = 'customId'; @@ -67,6 +67,7 @@ describe('IgxSnackbar', () => { snackbar.show(); + fixture.detectChanges(); expect(snackbar.isVisible).toBeTruthy(); expect(snackbar.autoHide).toBeFalsy(); @@ -90,6 +91,53 @@ describe('IgxSnackbar', () => { }); }); +describe('IgxSnackbar with custom content', () => { + configureTestSuite(); + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + SnackbarCustomContentComponent + ], + imports: [ + BrowserAnimationsModule, + IgxSnackbarModule + ] + }).compileComponents(); + })); + + let fixture, domSnackbar, snackbar; + beforeEach(async(() => { + fixture = TestBed.createComponent(SnackbarCustomContentComponent); + fixture.detectChanges(); + snackbar = fixture.componentInstance.snackbar; + domSnackbar = fixture.debugElement.query(By.css('igx-snackbar')).nativeElement; + })); + + it('should display a message, a custom content element and a button', () => { + fixture.componentInstance.text = 'Undo'; + snackbar.message = 'Item shown'; + snackbar.isVisible = true; + fixture.detectChanges(); + + const messageEl = fixture.debugElement.query(By.css('.igx-snackbar__message')); + expect(messageEl.nativeElement.innerText).toContain('Item shown'); + + const customContent = fixture.debugElement.query(By.css('.igx-snackbar__content')); + expect(customContent).toBeTruthy('Custom content is not found'); + + // Verify the message is displayed on the left side of the custom content + const messageElRect = (messageEl.nativeElement).getBoundingClientRect(); + const customContentRect = (customContent.nativeElement).getBoundingClientRect(); + expect(messageElRect.left <= customContentRect.left).toBe(true, 'The message is not on the left of the custom content'); + + // Verify the custom content element is on the left side of the button + const button = fixture.debugElement.query(By.css('.igx-snackbar__button')); + const buttonRect = (button.nativeElement).getBoundingClientRect(); + expect(customContentRect.right <= buttonRect.left).toBe(true, 'The custom element is not on the left of the button'); + expect(messageElRect.right <= buttonRect.left).toBe(true, 'The button is not on the right side of the snackbar content'); + }); +}); + @Component({ template: ` ` @@ -98,3 +146,13 @@ class SnackbarInitializeTestComponent { public text: string; @ViewChild(IgxSnackbarComponent) public snackbar: IgxSnackbarComponent; } + +@Component({ + template: ` + Custom content + ` +}) +class SnackbarCustomContentComponent { + public text: string; + @ViewChild(IgxSnackbarComponent) public snackbar: IgxSnackbarComponent; +}