diff --git a/CHANGELOG.md b/CHANGELOG.md index 483b26f85fb..088e9491748 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 @@ -26,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 7da94dfd4b1..8e9648a6297 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 * * ``` @@ -613,7 +613,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 49140321c0f..72421c1da19 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 @@ -105,12 +105,12 @@ export class IgxDatePickerComponent implements IgxDatePickerBase, ControlValueAc 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/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; + } + } } 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/column.component.ts b/projects/igniteui-angular/src/lib/grids/column.component.ts index 0097d33553c..b780f402926 100644 --- a/projects/igniteui-angular/src/lib/grids/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/column.component.ts @@ -579,6 +579,7 @@ export class IgxColumnComponent implements AfterContentInit { * ``` * @memberof IgxColumnComponent */ + @Input('cellTemplate') get bodyTemplate(): TemplateRef { return this._bodyTemplate; } @@ -600,7 +601,9 @@ export class IgxColumnComponent implements AfterContentInit { */ set bodyTemplate(template: TemplateRef) { this._bodyTemplate = template; - this.grid.markForCheck(); + if (this.grid) { + this.grid.cdr.markForCheck(); + } } /** * Returns a reference to the header template. @@ -609,6 +612,7 @@ export class IgxColumnComponent implements AfterContentInit { * ``` * @memberof IgxColumnComponent */ + @Input() get headerTemplate(): TemplateRef { return this._headerTemplate; } @@ -630,7 +634,9 @@ export class IgxColumnComponent implements AfterContentInit { */ set headerTemplate(template: TemplateRef) { this._headerTemplate = template; - this.grid.markForCheck(); + if (this.grid) { + this.grid.cdr.markForCheck(); + } } /** * Returns a reference to the inline editor template. @@ -639,6 +645,7 @@ export class IgxColumnComponent implements AfterContentInit { * ``` * @memberof IgxColumnComponent */ + @Input('cellEditorTemplate') get inlineEditorTemplate(): TemplateRef { return this._inlineEditorTemplate; } @@ -658,7 +665,9 @@ export class IgxColumnComponent implements AfterContentInit { */ set inlineEditorTemplate(template: TemplateRef) { this._inlineEditorTemplate = template; - this.grid.markForCheck(); + if (this.grid) { + this.grid.cdr.markForCheck(); + } } /** * Gets the cells of the column. 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'; } } 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: ` 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; +}