Skip to content

Commit d860375

Browse files
ddinchevazdrawku
andauthored
Add time and date time column types (#9334)
* feat(IgxCell): initial implementation of time and dateTime columns #7678 * feat(*): add sorting for time colimn #7678 * chore(*): update moving sample to have time and dateTime columns * feat(filtering): add resources for time and dateTime columns in filtering #7678 * feat(advancedFiltering): add filter template for time and dateTime columns #7678 * feat(Filtering): add filter template for time and dateTime columns #7678 * feat(Summary): add summary operand fot time column #7678 * feat(Filtering): add filtering conditions for time and dateTime columns #7678 * fix(filtering): filter data correctly #7678 * chore(*): fix build problem * chore(*): fix tree grid fitering strategy * chore(*): address testing comments * chore(IgxCell): fix editing for time columns * fix(IgxGrid): fix overlay warnings thrown on scroll #7678 * chore(*): Add test for filtering condition * chore(*): Fix expect's for +2 new columns * chore(*): add +2 new columns * chore(*): Fix flickering test with kb navigation * chore(*): Remove redundant getDateParts * chore(*): Add dateTime filtering conditions Co-authored-by: Zdravko Kolev <[email protected]>
1 parent 854c4f3 commit d860375

35 files changed

+782
-115
lines changed

projects/igniteui-angular/src/lib/core/i18n/grid-resources.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface IGridResourceStrings {
1010
igx_grid_filter_row_placeholder?: string;
1111
igx_grid_filter_row_boolean_placeholder?: string;
1212
igx_grid_filter_row_date_placeholder?: string;
13+
igx_grid_filter_row_time_placeholder?: string;
1314
igx_grid_filter_operator_and?: string;
1415
igx_grid_complex_filter?: string;
1516
igx_grid_filter_operator_or?: string;
@@ -25,6 +26,10 @@ export interface IGridResourceStrings {
2526
igx_grid_filter_notNull?: string;
2627
igx_grid_filter_before?: string;
2728
igx_grid_filter_after?: string;
29+
igx_grid_filter_at?: string;
30+
igx_grid_filter_not_at?: string;
31+
igx_grid_filter_at_before?: string;
32+
igx_grid_filter_at_after?: string;
2833
igx_grid_filter_today?: string;
2934
igx_grid_filter_yesterday?: string;
3035
igx_grid_filter_thisMonth?: string;
@@ -136,6 +141,7 @@ export const GridResourceStringsEN: IGridResourceStrings = {
136141
igx_grid_filter_row_placeholder: 'Add filter value',
137142
igx_grid_filter_row_boolean_placeholder: 'All',
138143
igx_grid_filter_row_date_placeholder: 'Pick up date',
144+
igx_grid_filter_row_time_placeholder: 'Pick up time',
139145
igx_grid_filter_operator_and: 'And',
140146
igx_grid_filter_operator_or: 'Or',
141147
igx_grid_complex_filter: 'Complex Filter',
@@ -151,6 +157,10 @@ export const GridResourceStringsEN: IGridResourceStrings = {
151157
igx_grid_filter_notNull: 'Not Null',
152158
igx_grid_filter_before: 'Before',
153159
igx_grid_filter_after: 'After',
160+
igx_grid_filter_at: 'At',
161+
igx_grid_filter_not_at: 'Not At',
162+
igx_grid_filter_at_before: 'At or Before',
163+
igx_grid_filter_at_after: 'At or After',
154164
igx_grid_filter_today: 'Today',
155165
igx_grid_filter_yesterday: 'Yesterday',
156166
igx_grid_filter_thisMonth: 'This Month',

projects/igniteui-angular/src/lib/data-operations/data-util.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const DataType = mkenum({
2525
Number: 'number',
2626
Boolean: 'boolean',
2727
Date: 'date',
28+
DateTime: 'dateTime',
29+
Time: 'time',
2830
Currency: 'currency',
2931
Percent: 'percent'
3032
});

projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,179 @@ export class IgxDateFilteringOperand extends IgxFilteringOperand {
372372
return searchVal.has(target.toISOString());
373373
}
374374

375-
private validateInputData(target: Date) {
375+
protected validateInputData(target: Date) {
376+
if (!(target instanceof Date)) {
377+
throw new Error('Could not perform filtering on \'date\' column because the datasource object type is not \'Date\'.');
378+
}
379+
}
380+
}
381+
382+
export class IgxDateTimeFilteringOperand extends IgxDateFilteringOperand {
383+
protected constructor() {
384+
super();
385+
let index = this.operations.indexOf(this.condition('equals'));
386+
this.operations.splice(index, 1);
387+
index = this.operations.indexOf(this.condition('doesNotEqual'));
388+
this.operations.splice(index, 1);
389+
this.operations = [{
390+
name: 'equals',
391+
isUnary: false,
392+
iconName: 'equals',
393+
logic: (target: Date, searchVal: Date) => {
394+
if (!target) {
395+
return false;
396+
}
397+
this.validateInputData(target);
398+
const targetp = IgxDateTimeFilteringOperand.getDateParts(target, 'yMdhms');
399+
const searchp = IgxDateTimeFilteringOperand.getDateParts(searchVal, 'yMdhms');
400+
return targetp.year === searchp.year &&
401+
targetp.month === searchp.month &&
402+
targetp.day === searchp.day &&
403+
targetp.hours === searchp.hours &&
404+
targetp.minutes === searchp.minutes &&
405+
targetp.seconds === searchp.seconds;
406+
}
407+
}, {
408+
name: 'doesNotEqual',
409+
isUnary: false,
410+
iconName: 'not-equal',
411+
logic: (target: Date, searchVal: Date) => {
412+
if (!target) {
413+
return true;
414+
}
415+
this.validateInputData(target);
416+
const targetp = IgxDateTimeFilteringOperand.getDateParts(target, 'yMdhms');
417+
const searchp = IgxDateTimeFilteringOperand.getDateParts(searchVal, 'yMdhms');
418+
return targetp.year !== searchp.year ||
419+
targetp.month !== searchp.month ||
420+
targetp.day !== searchp.day ||
421+
targetp.hours !== searchp.hours ||
422+
targetp.minutes !== searchp.minutes ||
423+
targetp.seconds !== searchp.seconds;
424+
}
425+
}].concat(this.operations);
426+
}
427+
}
428+
429+
export class IgxTimeFilteringOperand extends IgxFilteringOperand {
430+
protected constructor() {
431+
super();
432+
this.operations = [{
433+
name: 'at',
434+
isUnary: false,
435+
iconName: 'equals',
436+
logic: (target: Date, searchVal: Date) => {
437+
if (!target) {
438+
return false;
439+
}
440+
this.validateInputData(target);
441+
const targetp = IgxDateFilteringOperand.getDateParts(target, 'hms');
442+
const searchp = IgxDateFilteringOperand.getDateParts(searchVal, 'hms');
443+
return targetp.hours === searchp.hours &&
444+
targetp.minutes === searchp.minutes &&
445+
targetp.seconds === searchp.seconds;
446+
}
447+
}, {
448+
name: 'not_at',
449+
isUnary: false,
450+
iconName: 'not-equal',
451+
logic: (target: Date, searchVal: Date) => {
452+
if (!target) {
453+
return true;
454+
}
455+
this.validateInputData(target);
456+
const targetp = IgxDateFilteringOperand.getDateParts(target, 'hms');
457+
const searchp = IgxDateFilteringOperand.getDateParts(searchVal, 'hms');
458+
return targetp.hours !== searchp.hours ||
459+
targetp.minutes !== searchp.minutes ||
460+
targetp.seconds !== searchp.seconds;
461+
}
462+
}, {
463+
name: 'before',
464+
isUnary: false,
465+
iconName: 'is-before',
466+
logic: (target: Date, searchVal: Date) => {
467+
if (!target) {
468+
return false;
469+
}
470+
471+
this.validateInputData(target);
472+
const targetn = IgxDateFilteringOperand.getDateParts(target, 'hms');
473+
const search = IgxDateFilteringOperand.getDateParts(searchVal, 'hms');
474+
475+
return targetn.hours < search.hours ? true : targetn.hours === search.hours && targetn.minutes < search.minutes ?
476+
true : targetn.hours === search.hours && targetn.minutes === search.minutes && targetn.seconds < search.seconds;
477+
}
478+
}, {
479+
name: 'after',
480+
isUnary: false,
481+
iconName: 'is-after',
482+
logic: (target: Date, searchVal: Date) => {
483+
if (!target) {
484+
return false;
485+
}
486+
487+
this.validateInputData(target);
488+
const targetn = IgxDateFilteringOperand.getDateParts(target, 'hms');
489+
const search = IgxDateFilteringOperand.getDateParts(searchVal, 'hms');
490+
491+
return targetn.hours > search.hours ? true : targetn.hours === search.hours && targetn.minutes > search.minutes ?
492+
true : targetn.hours === search.hours && targetn.minutes === search.minutes && targetn.seconds > search.seconds;
493+
}
494+
}, {
495+
name: 'at_before',
496+
isUnary: false,
497+
iconName: 'is-before',
498+
logic: (target: Date, searchVal: Date) => {
499+
if (!target) {
500+
return false;
501+
}
502+
503+
this.validateInputData(target);
504+
const targetn = IgxDateFilteringOperand.getDateParts(target, 'hms');
505+
const search = IgxDateFilteringOperand.getDateParts(searchVal, 'hms');
506+
return (targetn.hours === search.hours && targetn.minutes === search.minutes && targetn.seconds === search.seconds) ||
507+
targetn.hours < search.hours ? true : targetn.hours === search.hours && targetn.minutes < search.minutes ?
508+
true : targetn.hours === search.hours && targetn.minutes === search.minutes && targetn.seconds < search.seconds;
509+
}
510+
}, {
511+
name: 'at_after',
512+
isUnary: false,
513+
iconName: 'is-after',
514+
logic: (target: Date, searchVal: Date) => {
515+
if (!target) {
516+
return false;
517+
}
518+
519+
this.validateInputData(target);
520+
const targetn = IgxDateFilteringOperand.getDateParts(target, 'hms');
521+
const search = IgxDateFilteringOperand.getDateParts(searchVal, 'hms');
522+
return (targetn.hours === search.hours && targetn.minutes === search.minutes && targetn.seconds === search.seconds) ||
523+
targetn.hours > search.hours ? true : targetn.hours === search.hours && targetn.minutes > search.minutes ?
524+
true : targetn.hours === search.hours && targetn.minutes === search.minutes && targetn.seconds > search.seconds;
525+
}
526+
}, {
527+
name: 'empty',
528+
isUnary: true,
529+
iconName: 'is-empty',
530+
logic: (target: Date) => target === null || target === undefined
531+
}, {
532+
name: 'notEmpty',
533+
isUnary: true,
534+
iconName: 'not-empty',
535+
logic: (target: Date) => target !== null && target !== undefined
536+
}].concat(this.operations);
537+
}
538+
539+
540+
protected findValueInSet(target: any, searchVal: Set<any>) {
541+
if (!target) {
542+
return false;
543+
}
544+
return searchVal.has(target);
545+
}
546+
547+
protected validateInputData(target: Date) {
376548
if (!(target instanceof Date)) {
377549
throw new Error('Could not perform filtering on \'date\' column because the datasource object type is not \'Date\'.');
378550
}

projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { resolveNestedPath, parseDate } from '../core/utils';
44
import { GridType } from '../grids/common/grid.interface';
55

66
const DateType = 'date';
7+
const TimeType = 'time';
78

89
export interface IFilteringStrategy {
910
filter(data: any[], expressionsTree: IFilteringExpressionsTree, advancedExpressionsTree?: IFilteringExpressionsTree,
@@ -25,9 +26,9 @@ export class NoopFilteringStrategy implements IFilteringStrategy {
2526
}
2627

2728
export abstract class BaseFilteringStrategy implements IFilteringStrategy {
28-
public findMatchByExpression(rec: any, expr: IFilteringExpression, isDate?: boolean, grid?: GridType): boolean {
29+
public findMatchByExpression(rec: any, expr: IFilteringExpression, isDate?: boolean, isTime?: boolean, grid?: GridType): boolean {
2930
const cond = expr.condition;
30-
const val = this.getFieldValue(rec, expr.fieldName, isDate, grid);
31+
const val = this.getFieldValue(rec, expr.fieldName, isDate, isTime, grid);
3132
return cond.logic(val, expr.searchVal, expr.ignoreCase);
3233
}
3334

@@ -59,9 +60,10 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
5960
return true;
6061
} else {
6162
const expression = expressions as IFilteringExpression;
62-
const isDate = grid && grid.getColumnByName(expression.fieldName) ?
63-
grid.getColumnByName(expression.fieldName).dataType === DateType : false;
64-
return this.findMatchByExpression(rec, expression, isDate, grid);
63+
const column = grid && grid.getColumnByName(expression.fieldName);
64+
const isDate = column ? column.dataType === DateType : false;
65+
const isTime = column ? column.dataType === TimeType : false;
66+
return this.findMatchByExpression(rec, expression, isDate, isTime, grid);
6567
}
6668
}
6769

@@ -71,7 +73,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
7173
public abstract filter(data: any[], expressionsTree: IFilteringExpressionsTree,
7274
advancedExpressionsTree?: IFilteringExpressionsTree, grid?: GridType): any[];
7375

74-
protected abstract getFieldValue(rec: any, fieldName: string, isDate?: boolean, grid?: GridType): any;
76+
protected abstract getFieldValue(rec: any, fieldName: string, isDate?: boolean, isTime?: boolean, grid?: GridType): any;
7577
}
7678

7779
export class FilteringStrategy extends BaseFilteringStrategy {
@@ -104,9 +106,9 @@ export class FilteringStrategy extends BaseFilteringStrategy {
104106
return res;
105107
}
106108

107-
protected getFieldValue(rec: any, fieldName: string, isDate: boolean = false): any {
109+
protected getFieldValue(rec: any, fieldName: string, isDate: boolean = false, isTime: boolean = false): any {
108110
let value = resolveNestedPath(rec, fieldName);
109-
value = value && isDate ? parseDate(value) : value;
111+
value = value && (isDate || isTime) ? parseDate(value) : value;
110112
return value;
111113
}
112114
}
@@ -126,13 +128,12 @@ export class FormattedValuesFilteringStrategy extends FilteringStrategy {
126128
return !this.fields || this.fields.length === 0 || this.fields.some(f => f === fieldName);
127129
}
128130

129-
protected getFieldValue(rec: any, fieldName: string, isDate: boolean = false, grid?: GridType): any {
131+
protected getFieldValue(rec: any, fieldName: string, isDate: boolean = false, isTime: boolean = false, grid?: GridType): any {
130132
const column = grid.getColumnByName(fieldName);
131133
let value = resolveNestedPath(rec, fieldName);
132134

133135
value = column.formatter && this.shouldApplyFormatter(fieldName) ?
134-
column.formatter(value) :
135-
value && isDate ? parseDate(value) : value;
136+
column.formatter(value) : value && (isDate || isTime) ? parseDate(value) : value;
136137

137138
return value;
138139
}

projects/igniteui-angular/src/lib/data-operations/sorting-strategy.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import { getHierarchy, isHierarchyMatch } from './operations';
99
import { GridType } from '../grids/common/grid.interface';
1010

1111
const DATE_TYPE = 'date';
12-
12+
const TIME_TYPE = 'time';
13+
const DATE_TIME_TYPE = 'dateTime';
1314
export interface ISortingStrategy {
1415
sort: (data: any[],
1516
fieldName: string,
1617
dir: SortingDirection,
1718
ignoreCase: boolean,
1819
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
19-
isDate?: boolean) => any[];
20+
isDate?: boolean,
21+
isTime?: boolean) => any[];
2022
}
2123

2224
export class DefaultSortingStrategy implements ISortingStrategy {
@@ -33,10 +35,11 @@ export class DefaultSortingStrategy implements ISortingStrategy {
3335
dir: SortingDirection,
3436
ignoreCase: boolean,
3537
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
36-
isDate?: boolean) {
38+
isDate?: boolean,
39+
isTime?: boolean) {
3740
const key = fieldName;
3841
const reverse = (dir === SortingDirection.Desc ? -1 : 1);
39-
const cmpFunc = (obj1, obj2) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate);
42+
const cmpFunc = (obj1, obj2) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate, isTime);
4043
return this.arraySort(data, cmpFunc);
4144
}
4245

@@ -59,10 +62,11 @@ export class DefaultSortingStrategy implements ISortingStrategy {
5962
key: string,
6063
reverse: number,
6164
ignoreCase: boolean,
62-
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
63-
isDate: boolean) {
64-
let a = valueResolver(obj1, key, isDate);
65-
let b = valueResolver(obj2, key, isDate);
65+
valueResolver: (obj: any, key: string, isDate?: boolean, isTime?: boolean) => any,
66+
isDate: boolean,
67+
isTime: boolean) {
68+
let a = valueResolver(obj1, key, isDate, isTime);
69+
let b = valueResolver(obj2, key, isDate, isTime);
6670
if (ignoreCase) {
6771
a = a && a.toLowerCase ? a.toLowerCase() : a;
6872
b = b && b.toLowerCase ? b.toLowerCase() : b;
@@ -107,13 +111,14 @@ export class IgxSorting implements IGridSortingStrategy {
107111
let result = [];
108112
while (i < data.length) {
109113
const column = grid ? grid.getColumnByName(expressions[level].fieldName) : null;
110-
const isDate = column?.dataType === DATE_TYPE;
114+
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
115+
const isTime = column?.dataType === TIME_TYPE;
111116
const group = this.groupedRecordsByExpression(data, i, expressions[level], isDate);
112117
const groupRow: IGroupByRecord = {
113118
expression: expressions[level],
114119
level,
115120
records: cloneArray(group),
116-
value: this.getFieldValue(group[0], expressions[level].fieldName, isDate),
121+
value: this.getFieldValue(group[0], expressions[level].fieldName, isDate, isTime),
117122
groupParent: parent,
118123
groups: [],
119124
height: grid ? grid.renderedRowHeight : null,
@@ -154,8 +159,15 @@ export class IgxSorting implements IGridSortingStrategy {
154159
return result;
155160
}
156161

157-
protected getFieldValue(obj: any, key: string, isDate: boolean = false): any {
158-
return isDate ? parseDate(resolveNestedPath(obj, key)) : resolveNestedPath(obj, key);
162+
protected getFieldValue(obj: any, key: string, isDate: boolean = false, isTime: boolean = false): any {
163+
let resolvedValue = resolveNestedPath(obj, key);
164+
if (isDate || isTime) {
165+
const date = parseDate(resolvedValue);
166+
resolvedValue = isTime ?
167+
new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()) : date;
168+
169+
}
170+
return resolvedValue;
159171
}
160172

161173
private groupedRecordsByExpression(data: any[],
@@ -197,9 +209,10 @@ export class IgxSorting implements IGridSortingStrategy {
197209
if (!expr.strategy) {
198210
expr.strategy = DefaultSortingStrategy.instance();
199211
}
200-
const isDate = grid && grid.getColumnByName(expr.fieldName) ?
201-
grid.getColumnByName(expr.fieldName).dataType === DATE_TYPE : false;
202-
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate);
212+
const column = grid?.getColumnByName(expr.fieldName);
213+
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
214+
const isTime = column?.dataType === TIME_TYPE;
215+
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate, isTime);
203216
if (expressionIndex === exprsLen - 1) {
204217
return data;
205218
}

0 commit comments

Comments
 (0)