Skip to content

Commit 90a5c78

Browse files
committed
Merge branch 'simeonoff/fix-12634' of github.com:IgniteUI/igniteui-angular into simeonoff/fix-12634
2 parents 8bc236f + 3fc0e83 commit 90a5c78

File tree

9 files changed

+272
-18
lines changed

9 files changed

+272
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ All notable changes for each version of this project will be documented in this
1414
- `igxPivotGrid`
1515
- Adding `aggregatorName` for pivot value configuration as an alternative to setting `aggregator` function. If both are set `aggregatorName` takes precedent. If none are set an error is thrown.
1616
- `IgxSimpleCombo`
17-
- **Behavioral Change** - Keyboard navigation `ArrowUp` - when the combo is opened `ArrowUp` will close the dropdown if the search input is focused. If the active item is the first one in the list, the focus will be moved back to the search input while also selecting all of the text in the input. Otherwise `ArrowUp` will move to the previous list item.
17+
- **Behavioral Change**
18+
- When the user clicks on the combo's input, the dropdown opens up.
19+
- Keyboard navigation `ArrowUp` - when the combo is opened `ArrowUp` will close the dropdown if the search input is focused. If the active item is the first one in the list, the focus will be moved back to the search input while also selecting all of the text in the input. Otherwise `ArrowUp` will move to the previous list item.
1820

1921
## 15.0.0
2022

projects/igniteui-angular/src/lib/grids/common/strategy.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ export class IgxSorting implements IGridSortingStrategy {
4343
while (i < data.length) {
4444
const column = grid ? grid.getColumnByName(expressions[level].fieldName) : null;
4545
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
46-
const isTime = column?.dataType === TIME_TYPE;
46+
const isTime = column?.dataType === TIME_TYPE || column?.dataType === DATE_TIME_TYPE;
4747
const isString = column?.dataType === STRING_TYPE;
48-
const group = this.groupedRecordsByExpression(data, i, expressions[level], isDate, isString);
48+
const group = this.groupedRecordsByExpression(data, i, expressions[level], isDate, isTime, isString);
4949
const groupRow: IGroupByRecord = {
5050
expression: expressions[level],
5151
level,
@@ -93,11 +93,13 @@ export class IgxSorting implements IGridSortingStrategy {
9393

9494
protected getFieldValue<T>(obj: T, key: string, isDate: boolean = false, isTime: boolean = false) {
9595
let resolvedValue = resolveNestedPath(obj, key);
96-
if (isDate || isTime) {
97-
const date = parseDate(resolvedValue);
98-
resolvedValue = isTime && date ?
99-
new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()) : date;
100-
96+
const date = parseDate(resolvedValue);
97+
if (date && isDate && isTime) {
98+
resolvedValue = date;
99+
} else if (date && isDate && !isTime) {
100+
resolvedValue = new Date(date.setHours(0, 0, 0, 0));
101+
} else if (date && isTime && !isDate) {
102+
resolvedValue = new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
101103
}
102104
return resolvedValue;
103105
}
@@ -107,17 +109,18 @@ export class IgxSorting implements IGridSortingStrategy {
107109
index: number,
108110
expression: IGroupingExpression,
109111
isDate: boolean = false,
112+
isTime: boolean = false,
110113
isString: boolean
111114
): T[] {
112115
const res = [];
113116
const key = expression.fieldName;
114117
const len = data.length;
115-
let groupval = this.getFieldValue(data[index], key, isDate);
118+
let groupval = this.getFieldValue(data[index], key, isDate, isTime);
116119
res.push(data[index]);
117120
index++;
118121
const comparer = expression.groupingComparer || DefaultSortingStrategy.instance().compareValues;
119122
for (let i = index; i < len; i++) {
120-
let fieldValue = this.getFieldValue(data[i], key, isDate);
123+
let fieldValue = this.getFieldValue(data[i], key, isDate, isTime);
121124
if (expression.ignoreCase && isString) {
122125
// when column's dataType is string but the value is number
123126
fieldValue = fieldValue?.toString().toLowerCase();
@@ -155,15 +158,15 @@ export class IgxSorting implements IGridSortingStrategy {
155158
}
156159
const column = grid?.getColumnByName(expr.fieldName);
157160
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
158-
const isTime = column?.dataType === TIME_TYPE;
161+
const isTime = column?.dataType === TIME_TYPE || column?.dataType === DATE_TIME_TYPE;
159162
const isString = column?.dataType === STRING_TYPE;
160163
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate, isTime, grid);
161164
if (expressionIndex === exprsLen - 1) {
162165
return data;
163166
}
164167
// in case of multiple sorting
165168
for (i = 0; i < dataLen; i++) {
166-
gbData = this.groupedRecordsByExpression(data, i, expr, isDate, isString);
169+
gbData = this.groupedRecordsByExpression(data, i, expr, isDate, isTime, isString);
167170
gbDataLen = gbData.length;
168171
if (gbDataLen > 1) {
169172
gbData = this.sortDataRecursive(gbData, expressions, expressionIndex + 1, grid);

projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IgxChipComponent } from '../../chips/chip.component';
1313
import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec';
1414
import { DefaultSortingStrategy, ISortingExpression, SortingDirection } from '../../data-operations/sorting-strategy';
1515
import { configureTestSuite } from '../../test-utils/configure-suite';
16-
import { DataParent } from '../../test-utils/sample-test-data.spec';
16+
import { DataParent, SampleTestData } from '../../test-utils/sample-test-data.spec';
1717
import { MultiColumnHeadersWithGroupingComponent } from '../../test-utils/grid-samples.spec';
1818
import { GridSelectionFunctions, GridFunctions, GRID_SCROLL_CLASS } from '../../test-utils/grid-functions.spec';
1919
import { GridSelectionMode } from '../common/enums';
@@ -39,7 +39,8 @@ describe('IgxGrid - GroupBy #grid', () => {
3939
GroupByEmptyColumnFieldComponent,
4040
MultiColumnHeadersWithGroupingComponent,
4141
GridGroupByRowCustomSelectorsComponent,
42-
GridGroupByCaseSensitiveComponent
42+
GridGroupByCaseSensitiveComponent,
43+
GridGroupByTestDateTimeDataComponent
4344
],
4445
imports: [NoopAnimationsModule, IgxGridModule]
4546
});
@@ -180,6 +181,145 @@ describe('IgxGrid - GroupBy #grid', () => {
180181
[null, fix.componentInstance.prevDay, fix.componentInstance.today, fix.componentInstance.nextDay]);
181182
}));
182183

184+
it('should only account for year, month and day when grouping by \'date\' dataType column', fakeAsync(() => {
185+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
186+
fix.detectChanges();
187+
188+
const grid = fix.componentInstance.grid;
189+
fix.detectChanges();
190+
191+
grid.groupBy({
192+
fieldName: 'DateField', dir: SortingDirection.Asc, ignoreCase: false
193+
});
194+
fix.detectChanges();
195+
196+
const groupRows = grid.groupsRowList.toArray();
197+
expect(groupRows.length).toEqual(3);
198+
199+
const targetTestVal = new Date(2012, 1, 12);
200+
const index = groupRows.findIndex(gr => new Date(gr.groupRow.value).getTime() === targetTestVal.getTime());
201+
expect(groupRows[index].groupRow.records.length).toEqual(2);
202+
203+
// compare the date values in the target group which are two identical dates with different time values
204+
const field = groupRows[index].groupRow.expression.fieldName;
205+
const record1 = groupRows[index].groupRow.records[0];
206+
const record2 = groupRows[index].groupRow.records[1];
207+
const rec1Date = new Date(record1[field]);
208+
const rec2Date = new Date(record2[field]);
209+
210+
// the time portions of the two records differ, so the Dates are not equal even though they are in the same group
211+
expect(rec1Date.getTime()).not.toEqual(rec2Date.getTime());
212+
// the date portions are the same, so they are in the same group, as the column type is `date`
213+
expect(rec1Date.getDate()).toEqual(rec2Date.getDate());
214+
expect(rec1Date.getMonth()).toEqual(rec2Date.getMonth());
215+
expect(rec1Date.getFullYear()).toEqual(rec2Date.getFullYear());
216+
}));
217+
218+
it('should only account for hours, minutes, seconds and ms when grouping by \'time\' dataType column', fakeAsync(() => {
219+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
220+
fix.detectChanges();
221+
222+
const grid = fix.componentInstance.grid;
223+
224+
grid.groupBy({
225+
fieldName: 'TimeField', dir: SortingDirection.Asc, ignoreCase: false
226+
});
227+
fix.detectChanges();
228+
229+
const groupRows = grid.groupsRowList.toArray();
230+
expect(groupRows.length).toEqual(3);
231+
232+
const targetTestVal = new Date(new Date().setHours(3, 20, 0, 1));
233+
const index = groupRows.findIndex(gr => new Date(gr.groupRow.value).getHours() === targetTestVal.getHours()
234+
&& new Date(gr.groupRow.value).getMinutes() === targetTestVal.getMinutes()
235+
&& new Date(gr.groupRow.value).getSeconds() === targetTestVal.getSeconds()
236+
&& new Date(gr.groupRow.value).getMilliseconds() === targetTestVal.getMilliseconds());
237+
238+
expect(groupRows[index].groupRow.records.length).toEqual(3);
239+
240+
// compare the date values in the target group which are three different dates with same time values
241+
const field = groupRows[index].groupRow.expression.fieldName;
242+
const record1 = groupRows[index].groupRow.records[0];
243+
const record2 = groupRows[index].groupRow.records[1];
244+
const record3 = groupRows[index].groupRow.records[2];
245+
const rec1Date = new Date(record1[field]);
246+
const rec2Date = new Date(record2[field]);
247+
const rec3Date = new Date(record3[field]);
248+
249+
// the date portions of the following records differ, so the Dates are not equal even though they are in the same group
250+
expect(rec1Date.getTime()).not.toEqual(rec2Date.getTime());
251+
// the below are equal by date as well
252+
expect(rec2Date.getTime()).toEqual(rec3Date.getTime());
253+
// the time portions of the not equal dates are the same, so they are in the same group, as the column type is `time`
254+
expect(rec1Date.getHours()).toEqual(rec2Date.getHours());
255+
expect(rec1Date.getMinutes()).toEqual(rec2Date.getMinutes());
256+
expect(rec1Date.getSeconds()).toEqual(rec2Date.getSeconds());
257+
expect(rec1Date.getMilliseconds()).toEqual(rec2Date.getMilliseconds());
258+
}));
259+
260+
it('should account for all date values when grouping by \'dateTime\' dataType column', fakeAsync(() => {
261+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
262+
fix.detectChanges();
263+
264+
const grid = fix.componentInstance.grid;
265+
266+
grid.groupBy({
267+
fieldName: 'DateTimeField', dir: SortingDirection.Asc, ignoreCase: false
268+
});
269+
fix.detectChanges();
270+
271+
// there are two identical DateTime values, so 4 groups out of 5 data records
272+
const groupRows = grid.groupsRowList.toArray();
273+
expect(groupRows.length).toEqual(4);
274+
275+
const targetTestVal = new Date(new Date('2003-03-17').setHours(3, 20, 0, 1));
276+
const index = groupRows.findIndex(gr => new Date(gr.groupRow.value).getTime() === targetTestVal.getTime());
277+
expect(groupRows[index].groupRow.records.length).toEqual(2);
278+
279+
// compare the date values in the target group which are two identical dates - date and time portions
280+
const field = groupRows[index].groupRow.expression.fieldName;
281+
const record1 = groupRows[index].groupRow.records[0];
282+
const record2 = groupRows[index].groupRow.records[1];
283+
const rec1Date = new Date(record1[field]);
284+
const rec2Date = new Date(record2[field]);
285+
286+
expect(rec1Date.getTime()).toEqual(rec2Date.getTime());
287+
}));
288+
289+
it('should display time value in the group by row when grouped by a \'time\' column', fakeAsync(() => {
290+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
291+
fix.detectChanges();
292+
293+
const grid = fix.componentInstance.grid;
294+
295+
grid.groupBy({
296+
fieldName: 'TimeField', dir: SortingDirection.Asc, ignoreCase: false
297+
});
298+
fix.detectChanges();
299+
300+
const groupRows = grid.groupsRowList.toArray();
301+
const expectedValue1 = groupRows[0].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent;
302+
const actualValue1 = groupRows[0].element.nativeElement.querySelector('.igx-group-label__text').textContent;
303+
expect(expectedValue1).toEqual(actualValue1);
304+
}));
305+
306+
it('should display time value in the group by row when grouped by a \'dateTime\' column', fakeAsync(() => {
307+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
308+
fix.detectChanges();
309+
310+
const grid = fix.componentInstance.grid;
311+
312+
grid.groupBy({
313+
fieldName: 'DateTimeField', dir: SortingDirection.Asc, ignoreCase: false
314+
});
315+
fix.detectChanges();
316+
317+
const groupRows = grid.groupsRowList.toArray();
318+
const expectedValue1 = groupRows[0].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[4].textContent;
319+
const actualValue1 = groupRows[0].element.nativeElement.querySelector('.igx-group-label__text').textContent;
320+
expect(expectedValue1).toEqual(actualValue1);
321+
}));
322+
183323
it('should allow grouping by multiple columns.', fakeAsync(() => {
184324
const fix = TestBed.createComponent(DefaultGridComponent);
185325
fix.detectChanges();
@@ -3816,3 +3956,27 @@ export class GridGroupByCaseSensitiveComponent {
38163956
}
38173957
];
38183958
}
3959+
3960+
@Component({
3961+
template: `
3962+
<igx-grid
3963+
#grid
3964+
[width]='width'
3965+
[height]='height'
3966+
[data]="datesData">
3967+
<igx-column [field]="'ProductID'" [width]="200" [groupable]="true" dataType="number"></igx-column>
3968+
<igx-column [field]="'ProductName'" [width]="200" [groupable]="true" dataType="string"></igx-column>
3969+
<igx-column [field]="'DateField'" [width]="200" [groupable]="true" dataType="date"></igx-column>
3970+
<igx-column [field]="'TimeField'" [width]="200" [groupable]="true" dataType="time"></igx-column>
3971+
<igx-column [field]="'DateTimeField'" [width]="200" [groupable]="true" dataType="dateTime"></igx-column>
3972+
</igx-grid>
3973+
`
3974+
})
3975+
export class GridGroupByTestDateTimeDataComponent {
3976+
@ViewChild("grid", { read: IgxGridComponent, static: true })
3977+
public grid: IgxGridComponent;
3978+
3979+
public width = '800px';
3980+
public height = null;
3981+
public datesData = SampleTestData.generateTestDateTimeData();
3982+
}

projects/igniteui-angular/src/lib/grids/grid/grid.sorting.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ describe('IgxGrid - Grid Sorting #grid', () => {
163163
});
164164
fixture.detectChanges();
165165
grid = fixture.componentInstance.grid;
166+
const hireDateCol = grid.columns.findIndex(col => col.field === "HireDate");
167+
grid.columns[hireDateCol].dataType = 'dateTime';
168+
fixture.detectChanges();
166169

167170
const currentColumn = 'HireDate';
168171
grid.sort({ fieldName: currentColumn, dir: SortingDirection.Asc, ignoreCase: false });

projects/igniteui-angular/src/lib/grids/grid/groupby-row.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@
5555
<ng-container *ngIf="dataType === 'number'">
5656
<span class="igx-group-label__text">{{ groupRow.value | number }}</span>
5757
</ng-container>
58-
<ng-container *ngIf="dataType === 'date'">
59-
<span class="igx-group-label__text">{{ groupRow.value | date }}</span>
58+
<ng-container *ngIf="dataType === 'date' || dataType === 'dateTime' || dataType === 'time'">
59+
<span class="igx-group-label__text">{{ groupRow.value |
60+
date:groupRow.column.pipeArgs.format:groupRow.column.pipeArgs.timezone:grid.locale }}</span>
6061
</ng-container>
6162
</ng-template>
6263

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[attr.aria-expanded]="!this.dropdown.collapsed" [attr.aria-controls]="this.dropdown.listId"
1515
[attr.aria-labelledby]="this.ariaLabelledBy || this.label?.id || this.placeholder"
1616
[attr.placeholder]="placeholder" [disabled]="disabled" [igxTextSelection]="!composing"
17-
(focus)="onFocus()" (input)="handleInputChange($event)"
17+
(focus)="onFocus()" (input)="handleInputChange($event)" (click)="handleInputClick()"
1818
(keyup)="handleKeyUp($event)" (keydown)="handleKeyDown($event)" (blur)="onBlur()"/>
1919

2020
<ng-container ngProjectAs="igx-suffix">

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,37 @@ describe('IgxSimpleCombo', () => {
11391139
expect(combo.selection.length).toEqual(0);
11401140
});
11411141

1142+
it('should open the combo when input is focused', () => {
1143+
spyOn(combo, 'open').and.callThrough();
1144+
spyOn(combo, 'close').and.callThrough();
1145+
1146+
input.triggerEventHandler('focus', {});
1147+
fixture.detectChanges();
1148+
1149+
input.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
1150+
fixture.detectChanges();
1151+
1152+
expect(combo.open).toHaveBeenCalledTimes(1);
1153+
1154+
input.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
1155+
fixture.detectChanges();
1156+
1157+
expect(combo.open).toHaveBeenCalledTimes(1);
1158+
1159+
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
1160+
fixture.detectChanges();
1161+
1162+
expect(combo.close).toHaveBeenCalledTimes(1);
1163+
1164+
input.triggerEventHandler('focus', {});
1165+
fixture.detectChanges();
1166+
1167+
input.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
1168+
fixture.detectChanges();
1169+
1170+
expect(combo.open).toHaveBeenCalledTimes(1);
1171+
});
1172+
11421173
it('should empty any invalid item values', () => {
11431174
combo.valueKey = 'key';
11441175
combo.displayKey = 'value';

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,14 @@ export class IgxSimpleComboComponent extends IgxComboBaseDirective implements Co
272272
this.composing = true;
273273
}
274274

275+
/** @hidden @internal */
276+
public handleInputClick(): void {
277+
if (this.collapsed) {
278+
this.open();
279+
this.comboInput.focus();
280+
}
281+
}
282+
275283
/** @hidden @internal */
276284
public handleKeyDown(event: KeyboardEvent): void {
277285
if (event.key === this.platformUtil.KEYMAP.ENTER) {
@@ -373,7 +381,9 @@ export class IgxSimpleComboComponent extends IgxComboBaseDirective implements Co
373381
/** @hidden @internal */
374382
public handleOpened(): void {
375383
this.triggerCheck();
376-
this.dropdownContainer.nativeElement.focus();
384+
if (!this.comboInput.focused) {
385+
this.dropdownContainer.nativeElement.focus();
386+
}
377387
this.opened.emit({ owner: this });
378388
}
379389

0 commit comments

Comments
 (0)