Skip to content

Commit 0092eb0

Browse files
committed
Merge branch 'master' of https://github.com/IgniteUI/igniteui-angular into ddincheva/rowStyling
2 parents 8cf9644 + eceeb41 commit 0092eb0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1098
-504
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@
22

33
All notable changes for each version of this project will be documented in this file.
44

5+
## 12.1.3
6+
7+
### New Features
8+
- `igxGrid`
9+
- Added `headerStyles` and `headerGroupStyles` inputs to the column component.
10+
Similar to `cellStyles` is exposes a way to bind CSS properties and style the grid headers.
11+
512
## 12.1.2
613
- `igxGrid`
714
- The column formatter callback signature now accepts the row data as an additional argument:
815
```typescript formatter(value: any, rowData?: any)```
916
The `rowData` argument may be `undefined` in remote scenarios/applying the callback on filtering labels
1017
so make sure to check its availability.
1118

19+
- `IgxExcelExporterService`
20+
- Added support for freezing column headers in **Excel**. By default, the column headers would not be frozen but this behavior can be controlled by the `freezeHeaders` option of the IgxExcelExporterOptions object.
21+
1222
## 12.1.0
1323

1424
### New Features

projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export class IgxGridEditingActionsComponent extends IgxGridActionsBaseDirective
149149
console.warn('The grid must use row edit mode to perform row adding! Please set rowEditable to true.');
150150
return;
151151
}
152-
grid.beginAddRowByIndex(context.rowID, context.index, asChild, event);
152+
grid.gridAPI.crudService.enterAddRowMode(context, asChild, event);
153153
this.strip.hide();
154154
}
155155

projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,11 @@ export class IgxCheckboxComponent implements ControlValueAccessor, EditorProvide
454454
this._onTouchedCallback = fn;
455455
}
456456

457+
/** @hidden @internal */
458+
public setDisabledState(isDisabled: boolean) {
459+
this.disabled = isDisabled;
460+
}
461+
457462
/** @hidden @internal */
458463
public getEditElement() {
459464
return this.nativeCheckbox.nativeElement;

projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Component, ViewChild, ViewChildren, QueryList, DebugElement } from '@angular/core';
22
import { TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
3-
import { FormsModule, FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3+
import { FormsModule, FormBuilder, ReactiveFormsModule, Validators, FormControl, FormGroup } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { IgxInputGroupComponent, IgxInputGroupModule } from '../../input-group/input-group.component';
66
import { IgxInputDirective, IgxInputState } from './input.directive';
77
import { configureTestSuite } from '../../test-utils/configure-suite';
88

99
const INPUT_CSS_CLASS = 'igx-input-group__input';
10+
const CSS_CLASS_INPUT_GROUP_LABEL = 'igx-input-group__label';
1011
const TEXTAREA_CSS_CLASS = 'igx-input-group__textarea';
1112

1213
const INPUT_GROUP_FOCUSED_CSS_CLASS = 'igx-input-group--focused';
@@ -35,7 +36,8 @@ describe('IgxInput', () => {
3536
DataBoundDisabledInputWithoutValueComponent,
3637
ReactiveFormComponent,
3738
InputsWithSameNameAttributesComponent,
38-
ToggleRequiredWithNgModelInputComponent
39+
ToggleRequiredWithNgModelInputComponent,
40+
InputReactiveFormComponent
3941
],
4042
imports: [
4143
IgxInputGroupModule,
@@ -521,11 +523,16 @@ describe('IgxInput', () => {
521523
fixture.detectChanges();
522524
const igxInput = fixture.componentInstance.strIgxInput;
523525

526+
expect(igxInput.disabled).toBe(false);
527+
expect(igxInput.inputGroup.disabled).toBe(false);
528+
524529
fixture.componentInstance.form.disable();
525530
expect(igxInput.disabled).toBe(true);
531+
expect(igxInput.inputGroup.disabled).toBe(true);
526532

527533
fixture.componentInstance.form.get('str').enable();
528534
expect(igxInput.disabled).toBe(false);
535+
expect(igxInput.inputGroup.disabled).toBe(false);
529536
}));
530537

531538
it('should style input when required is toggled dynamically.', () => {
@@ -663,6 +670,69 @@ describe('IgxInput', () => {
663670
igxInput.value = 'Test';
664671
expect(igxInput.value).toBe('Test');
665672
});
673+
674+
it('Should properly initialize when used as a reactive form control - without initial validators/toggle validators', fakeAsync(() => {
675+
const fix = TestBed.createComponent(InputReactiveFormComponent);
676+
fix.detectChanges();
677+
// 1) check if label's --required class and its asterisk are applied
678+
const dom = fix.debugElement;
679+
const input = fix.componentInstance.input;
680+
const inputGroup = fix.componentInstance.igxInputGroup.element.nativeElement;
681+
const formGroup: FormGroup = fix.componentInstance.reactiveForm;
682+
683+
// interaction test - expect actual asterisk
684+
// The only way to get a pseudo elements like :before OR :after is to use getComputedStyle(element [, pseudoElt]),
685+
// as these are not in the actual DOM
686+
let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
687+
expect(asterisk).toBe('"*"');
688+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
689+
690+
// 2) check that input group's --invalid class is NOT applied
691+
expect(inputGroup.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
692+
693+
// interaction test - focus&blur, so the --invalid and --required classes are applied
694+
// *Use markAsTouched() instead of user interaction ( calling focus + blur) because:
695+
// Angular handles blur and marks the component as touched, however:
696+
// in order to ensure Angular handles blur prior to our blur handler (where we check touched),
697+
// we have to call blur twice.
698+
fix.debugElement.componentInstance.markAsTouched();
699+
tick();
700+
fix.detectChanges();
701+
702+
expect(inputGroup.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
703+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
704+
705+
// 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared
706+
fix.componentInstance.removeValidators(formGroup);
707+
fix.detectChanges();
708+
tick();
709+
710+
const formReference = fix.componentInstance.reactiveForm.controls.fullName;
711+
// interaction test - expect no asterisk
712+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
713+
expect(formReference).toBeDefined();
714+
expect(input).toBeDefined();
715+
expect(input.nativeElement.value).toEqual('');
716+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toEqual(false);
717+
expect(asterisk).toBe('none');
718+
expect(input.valid).toEqual(IgxInputState.INITIAL);
719+
720+
// interact with the input and expect no changes
721+
input.nativeElement.dispatchEvent(new Event('focus'));
722+
input.nativeElement.dispatchEvent(new Event('blur'));
723+
tick();
724+
fix.detectChanges();
725+
expect(input.valid).toEqual(IgxInputState.INITIAL);
726+
727+
// Re-add all Validators
728+
fix.componentInstance.addValidators(formGroup);
729+
fix.detectChanges();
730+
731+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
732+
// interaction test - expect actual asterisk
733+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
734+
expect(asterisk).toBe('"*"');
735+
}));
666736
});
667737

668738
@Component({
@@ -917,6 +987,67 @@ class ToggleRequiredWithNgModelInputComponent {
917987
public data1 = '';
918988
public isRequired = false;
919989
}
990+
@Component({
991+
template: `
992+
<form [formGroup]="reactiveForm" (ngSubmit)="onSubmitReactive()">
993+
<igx-input-group #igxInputGroup>
994+
<input igxInput #inputReactive name="fullName" type="text" formControlName="fullName" />
995+
<label igxLabel for="fullName">Full Name</label>
996+
<igx-suffix>
997+
<igx-icon>person</igx-icon>
998+
</igx-suffix>
999+
</igx-input-group>
1000+
</form>
1001+
`
1002+
})
1003+
1004+
class InputReactiveFormComponent {
1005+
@ViewChild('igxInputGroup', { static: true }) public igxInputGroup: IgxInputGroupComponent;
1006+
@ViewChild('inputReactive', { read: IgxInputDirective }) public input: IgxInputDirective;
1007+
public reactiveForm: FormGroup;
1008+
1009+
public validationType = {
1010+
fullName: [Validators.required]
1011+
};
1012+
1013+
constructor(fb: FormBuilder) {
1014+
this.reactiveForm = fb.group({
1015+
fullName: new FormControl('', Validators.required)
1016+
});
1017+
}
1018+
public onSubmitReactive() { }
1019+
1020+
public removeValidators(form: FormGroup) {
1021+
for (const key in form.controls) {
1022+
if (form.controls.hasOwnProperty(key)) {
1023+
form.get(key).clearValidators();
1024+
form.get(key).updateValueAndValidity();
1025+
}
1026+
}
1027+
}
1028+
1029+
public addValidators(form: FormGroup) {
1030+
for (const key in form.controls) {
1031+
if (form.controls.hasOwnProperty(key)) {
1032+
form.get(key).setValidators(this.validationType[key]);
1033+
form.get(key).updateValueAndValidity();
1034+
}
1035+
}
1036+
}
1037+
1038+
public markAsTouched() {
1039+
if (!this.reactiveForm.valid) {
1040+
for (const key in this.reactiveForm.controls) {
1041+
if (this.reactiveForm.controls.hasOwnProperty(key)) {
1042+
if (this.reactiveForm.controls[key]) {
1043+
this.reactiveForm.controls[key].markAsTouched();
1044+
this.reactiveForm.controls[key].updateValueAndValidity();
1045+
}
1046+
}
1047+
}
1048+
}
1049+
}
1050+
}
9201051

9211052
const testRequiredValidation = (inputElement, fixture) => {
9221053
dispatchInputEvent('focus', inputElement, fixture);

projects/igniteui-angular/src/lib/directives/input/input.directive.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
174174
* ```
175175
*/
176176
public get disabled() {
177-
if (this.ngControl && this.ngControl.disabled !== null) {
178-
return this.ngControl.disabled;
179-
}
180177
return this._disabled;
181178
}
182179

@@ -268,6 +265,10 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
268265
this.inputGroup.hasPlaceholder = this.nativeElement.hasAttribute(
269266
'placeholder'
270267
);
268+
269+
if (this.ngControl && this.ngControl.disabled !== null) {
270+
this.disabled = this.ngControl.disabled;
271+
}
271272
this.inputGroup.disabled =
272273
this.inputGroup.disabled ||
273274
this.nativeElement.hasAttribute('disabled');
@@ -349,16 +350,23 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
349350
protected updateValidityState() {
350351
if (this.ngControl) {
351352
if (this.ngControl.control.validator || this.ngControl.control.asyncValidator) {
353+
// Run the validation with empty object to check if required is enabled.
354+
const error = this.ngControl.control.validator({} as AbstractControl);
355+
this.inputGroup.isRequired = error && error.required;
352356
if (!this.disabled && (this.ngControl.control.touched || this.ngControl.control.dirty)) {
353357
// the control is not disabled and is touched or dirty
354358
this._valid = this.ngControl.invalid ?
355-
IgxInputState.INVALID : this.focused ? IgxInputState.VALID :
356-
IgxInputState.INITIAL;
359+
IgxInputState.INVALID : this.focused ? IgxInputState.VALID :
360+
IgxInputState.INITIAL;
357361
} else {
358362
// if control is untouched, pristine, or disabled its state is initial. This is when user did not interact
359363
// with the input or when form/control is reset
360364
this._valid = IgxInputState.INITIAL;
361365
}
366+
} else {
367+
// If validator is dynamically cleared, reset label's required class(asterisk) and IgxInputState #10010
368+
this._valid = IgxInputState.INITIAL;
369+
this.inputGroup.isRequired = false;
362370
}
363371
} else {
364372
this.checkNativeValidity();
@@ -459,8 +467,8 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
459467
private checkNativeValidity() {
460468
if (!this.disabled && this._hasValidators()) {
461469
this._valid = this.nativeElement.checkValidity() ?
462-
this.focused ? IgxInputState.VALID : IgxInputState.INITIAL :
463-
IgxInputState.INVALID;
470+
this.focused ? IgxInputState.VALID : IgxInputState.INITIAL :
471+
IgxInputState.INVALID;
464472
}
465473
}
466474

projects/igniteui-angular/src/lib/directives/radio/radio-group.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ export class IgxRadioGroupDirective implements AfterContentInit, ControlValueAcc
435435
private _selectRadioButton() {
436436
if (this.radioButtons) {
437437
this.radioButtons.forEach((button) => {
438-
if (!this._value) {
438+
if (this._value === null) {
439439
// no value - uncheck all radio buttons
440440
if (button.checked) {
441441
button.checked = false;

projects/igniteui-angular/src/lib/directives/text-highlight/text-highlight.directive.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,19 @@ describe('IgxHighlight', () => {
286286
expect(spans.length).toBe(4);
287287
expect(activeSpans.length).toBe(1);
288288
});
289+
290+
it('Should not throw error when active highlight is not set', () => {
291+
const fix = TestBed.createComponent(HighlightLoremIpsumComponent);
292+
fix.detectChanges();
293+
294+
const component: HighlightLoremIpsumComponent = fix.debugElement.componentInstance;
295+
component.highlight.row = undefined;
296+
component.highlight.column = undefined;
297+
component.highlightText('a');
298+
299+
expect(() => component.highlight.activateIfNecessary()).not.toThrowError();
300+
});
301+
289302
});
290303

291304
@Component({

projects/igniteui-angular/src/lib/directives/text-highlight/text-highlight.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export class IgxTextHighlightDirective implements AfterViewInit, AfterViewChecke
340340
public activateIfNecessary(): void {
341341
const group = IgxTextHighlightDirective.highlightGroupsMap.get(this.groupName);
342342

343-
if (group.column === this.column && group.row === this.row && compareMaps(this.metadata, group.metadata)) {
343+
if (group.index >= 0 && group.column === this.column && group.row === this.row && compareMaps(this.metadata, group.metadata)) {
344344
this.activate(group.index);
345345
}
346346
}

projects/igniteui-angular/src/lib/grids/api.service.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IgxGridBaseDirective } from './grid-base.directive';
88
import { IgxRowDirective } from './row.directive';
99
import { IFilteringExpressionsTree } from '../data-operations/filtering-expressions-tree';
1010
import { Transaction, TransactionType, State } from '../services/transaction/transaction';
11-
import { IgxCell, IgxGridCRUDService, IgxRow } from './common/crud.service';
11+
import { IgxCell, IgxGridCRUDService, IgxEditRow } from './common/crud.service';
1212
import { GridType } from './common/grid.interface';
1313
import { ColumnType } from './common/column.interface';
1414
import { IGridEditEventArgs, IRowToggleEventArgs } from './common/events';
@@ -122,23 +122,6 @@ export class GridBaseAPIService<T extends IgxGridBaseDirective & GridType> {
122122
}
123123
}
124124

125-
public update_add_cell(cell: IgxCell): IGridEditEventArgs {
126-
if (!cell) {
127-
return;
128-
}
129-
130-
const args = cell.createEditEventArgs(true);
131-
132-
const data = cell.rowData;
133-
if (cell.column.hasNestedPath) {
134-
mergeObjects(data, reverseMapper(cell.column.field, args.newValue));
135-
} else {
136-
data[cell.column.field] = args.newValue;
137-
}
138-
mergeObjects(this.crudService.row.data, data);
139-
return args;
140-
}
141-
142125
public update_cell(cell: IgxCell): IGridEditEventArgs {
143126
if (!cell) {
144127
return;
@@ -167,7 +150,7 @@ export class GridBaseAPIService<T extends IgxGridBaseDirective & GridType> {
167150
}
168151

169152
// TODO: CRUD refactor to not emit editing evts.
170-
public update_row(row: IgxRow, value: any, event?: Event) {
153+
public update_row(row: IgxEditRow, value: any, event?: Event) {
171154
const grid = this.grid;
172155
const selected = grid.selectionService.isRowSelected(row.id);
173156
const rowInEditMode = this.crudService.row;
@@ -270,10 +253,11 @@ export class GridBaseAPIService<T extends IgxGridBaseDirective & GridType> {
270253
return this.grid.filteredData;
271254
}
272255

273-
public addRowToData(rowData: any, _parentRowID?) {
256+
public addRowToData(rowData: any, parentID?) {
274257
// Add row goes to transactions and if rowEditable is properly implemented, added rows will go to pending transactions
275258
// If there is a row in edit - > commit and close
276259
const grid = this.grid;
260+
277261
if (grid.transactions.enabled) {
278262
const transactionId = grid.primaryKey ? rowData[grid.primaryKey] : rowData;
279263
const transaction: Transaction = { id: transactionId, type: TransactionType.ADD, newValue: rowData };

0 commit comments

Comments
 (0)