Skip to content

Commit

Permalink
feat(date-time-editor): display & input format #6271
Browse files Browse the repository at this point in the history
  • Loading branch information
jackofdiamond5 committed Mar 17, 2020
1 parent 17466ae commit 85ab5c9
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 83 deletions.
29 changes: 18 additions & 11 deletions projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,20 @@ export abstract class DatePickerUtil {
}

public static parseDateTimeFormat(mask: string, locale: string = DatePickerUtil.DEFAULT_LOCALE): DatePartInfo[] {
let format = DatePickerUtil.setInputFormat(mask);
let dateTimeData: DatePartInfo[] = [];
if ((mask === undefined || mask === '') && !isIE()) {
if ((format === '') && !isIE()) {
dateTimeData = DatePickerUtil.getDefaultLocaleMask(locale);
} else {
const format = (mask) ? mask : DatePickerUtil.SHORT_DATE_MASK;
format = (format) ? format : DatePickerUtil.SHORT_DATE_MASK;
const formatArray = Array.from(format);
for (let i = 0; i < formatArray.length; i++) {
const datePartRange = this.getDatePartInfoRange(formatArray[i], format, i);
const dateTimeInfo = {
type: DatePickerUtil.determineDatePart(formatArray[i]),
start: datePartRange.start,
end: datePartRange.end,
format: formatArray[i],
format: mask.match(new RegExp(`${format[i]}+`, 'g'))[0],
};
while (DatePickerUtil.isDateOrTimeChar(formatArray[i])) {
if (dateTimeData.indexOf(dateTimeInfo) === -1) {
Expand All @@ -138,16 +139,17 @@ export abstract class DatePickerUtil {
return dateTimeData;
}

public static setInputFormat(format: string) {
public static setInputFormat(format: string): string {
if (!format) { return ''; }
let chars = '';
let newFormat = '';
for (let i = 0; ; i++) {
while (DatePickerUtil.isDateOrTimeChar(format[i])) {
chars += format[i];
i++;
}

if (chars.length === 1 || chars.length === 3) {
const datePartType = DatePickerUtil.determineDatePart(chars[0]);
if (datePartType !== DatePart.Year) {
newFormat += chars[0].repeat(2);
} else {
newFormat += chars;
Expand All @@ -165,7 +167,7 @@ export abstract class DatePickerUtil {
}

public static isDateOrTimeChar(char: string): boolean {
return TimeCharsArr.includes(char) || DateCharsArr.includes(char);
return TimeCharsArr.indexOf(char) !== -1 || DateCharsArr.indexOf(char) !== -1;
}

public static calculateDateOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
Expand Down Expand Up @@ -253,10 +255,15 @@ export abstract class DatePickerUtil {
return currentDate;
}

public static calculateAmPmOnSpin(delta: number, newDate: Date, currentDate: Date) {
newDate = delta > 0
? new Date(newDate.setHours(newDate.getHours() + 12))
: new Date(newDate.setHours(newDate.getHours() - 12));
public static calculateAmPmOnSpin(newDate: Date, currentDate: Date, amPmFromMask: string) {
switch (amPmFromMask) {
case 'AM':
newDate = new Date(newDate.setHours(newDate.getHours() + 12 * 1));
break;
case 'PM':
newDate = new Date(newDate.setHours(newDate.getHours() + 12 * -1));
break;
}
if (newDate.getDate() !== currentDate.getDate()) {
return currentDate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fdescribe('IgxDateTimeEditor', () => {
fdescribe('Should be able to spin the date portions.', () => {
it('Should correctly increment / decrement date portions with passed in DatePart', () => {
dateTimeEditor = new IgxDateTimeEditorDirective(elementRef, maskParsingService, renderer2, DOCUMENT);
dateTimeEditor.format = 'dd/M/yy';
dateTimeEditor.inputFormat = 'dd/M/yy';
dateTimeEditor.ngOnInit();
dateTimeEditor.value = new Date();
const date = dateTimeEditor.value.getDate();
Expand Down Expand Up @@ -145,7 +145,7 @@ fdescribe('IgxDateTimeEditor', () => {

it('Should prioritize Date for spinning, if it is set in format', () => {
dateTimeEditor = new IgxDateTimeEditorDirective(elementRef, maskParsingService, renderer2, DOCUMENT);
dateTimeEditor.format = 'dd/M/yy HH:mm:ss tt';
dateTimeEditor.inputFormat = 'dd/M/yy HH:mm:ss tt';
dateTimeEditor.ngOnInit();
dateTimeEditor.value = new Date(2020, 2, 11);

Expand Down Expand Up @@ -205,7 +205,7 @@ fdescribe('IgxDateTimeEditor', () => {
* format must be set because the editor will prioritize Date if Hours is not set
* and no DatePart is provided to increment / decrement
*/
dateTimeEditor.format = 'HH:mm:ss tt';
dateTimeEditor.inputFormat = 'HH:mm:ss tt';
dateTimeEditor.ngOnInit();
dateTimeEditor.value = new Date();
const hours = dateTimeEditor.value.getHours();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
@Input()
public value: Date;

// TODO
// locale date formats should be the same sa igxdatepicker's - shortDate, longDate, etc
@Input()
public locale: string;
public locale = 'en';

@Input()
public minValue: string | Date;
Expand All @@ -44,14 +42,17 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
@Output()
public validationFailed = new EventEmitter<IgxDateTimeEditorEventArgs>();

public get format(): string {
@Input()
public displayFormat: string;

public get inputFormat(): string {
return this._format;
}

@Input(`igxDateTimeEditor`)
public set format(value: string) {
public set inputFormat(value: string) {
this._format = value;
const mask = this.buildMask(this.format);
const mask = this.buildMask(this.inputFormat);
this.mask = value.indexOf('tt') !== -1 ? mask.substring(0, mask.length - 2) + 'LL' : mask;
}

Expand All @@ -71,6 +72,10 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
return literals;
}

private get emptyInput() {
return this.maskParser.applyMask('', this.maskOptions);
}

private get targetDatePart(): DatePart {
if (this._document.activeElement === this.nativeElement) {
return this._dateTimeFormatParts.find(p => p.start <= this.selectionStart && this.selectionStart <= p.end).type;
Expand All @@ -94,8 +99,8 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn

/** @hidden */
public ngOnInit(): void {
this._dateTimeFormatParts = DatePickerUtil.parseDateTimeFormat(DatePickerUtil.setInputFormat(this.format));
this.renderer.setAttribute(this.nativeElement, 'placeholder', this.format);
this._dateTimeFormatParts = DatePickerUtil.parseDateTimeFormat(this.inputFormat);
this.renderer.setAttribute(this.nativeElement, 'placeholder', this.inputFormat);
}

public clear(): void {
Expand All @@ -105,15 +110,15 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn

public increment(datePart?: DatePart): void {
const newValue = datePart ? this.calculateValueOnSpin(datePart, 1) : this.calculateValueOnSpin(this.targetDatePart, 1);
if (newValue && this.value && newValue !== this.value) {
if (newValue) {
this.updateValue(newValue);
this.updateMask();
}
}

public decrement(datePart?: DatePart): void {
const newValue = datePart ? this.calculateValueOnSpin(datePart, -1) : this.calculateValueOnSpin(this.targetDatePart, -1);
if (newValue && this.value && newValue !== this.value) {
if (newValue) {
this.updateValue(newValue);
this.updateMask();
}
Expand Down Expand Up @@ -152,6 +157,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn

/** @hidden */
public onFocus(): void {
// TODO: apply mask on focus & focused flag
this.onTouchCallback();
super.onFocus();
}
Expand All @@ -161,19 +167,21 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
// if inputted string does not fit in the editor, show as many chars as possible followed by "..." ?
if (!this.valueInRange(this.value)) {
this.validationFailed.emit({ oldValue: this._oldValue, newValue: this.value });
// this.updateMask(); TODO: set empty mask
this.inputValue = this.emptyInput;
this.updateValue(null);
return;
}

// TODO: display value pipe
// this.updateMask();
const format = this.displayFormat ? this.displayFormat : this.inputFormat;
this.inputValue = formatDate(this.value, format, this.locale);
this.onTouchCallback();
super.onBlur(event);
}

/** @hidden */
protected handleInputChanged(): void {
protected onInputChanged(): void {
// the mask must be updated before any date operations
super.handleInputChanged();
super.onInputChanged();
if (this.inputValue === this.maskParser.applyMask('', this.maskOptions)) {
this.updateValue(null);
return;
Expand All @@ -187,8 +195,6 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
this.validationFailed.emit({ oldValue: this.value, newValue: parsedDate.value });
}
}

super.afterInput();
}

private buildMask(format: string): string {
Expand All @@ -213,7 +219,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
}

private calculateValueOnSpin(datePart: DatePart, delta: number): Date {
if (!this.value) { return; }
if (!this.value) { return null; }
const currentDate = this.value as Date;
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(),
currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds());
Expand All @@ -232,7 +238,9 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
case DatePart.Seconds:
return DatePickerUtil.calculateSecondsOnSpin(delta, newDate, currentDate, this.isSpinLoop);
case DatePart.AmPm:
return DatePickerUtil.calculateAmPmOnSpin(delta, newDate, currentDate);
const formatPart = this._dateTimeFormatParts.find(dp => dp.type === DatePart.AmPm);
const amPmFromMask = this.inputValue.substring(formatPart.start, formatPart.end);
return DatePickerUtil.calculateAmPmOnSpin(newDate, currentDate, amPmFromMask);
}
}

Expand All @@ -253,15 +261,34 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
let targetValue: string = this.getMaskedValue(p.type, partLength);

if (p.type === DatePart.Month) {
targetValue = this.prependPromptChars(
parseInt(targetValue.replace(new RegExp(this.promptChar, 'g'), '0'), 10) + 1, partLength);
targetValue = this.prependValue(
parseInt(targetValue.replace(new RegExp(this.promptChar, 'g'), '0'), 10) + 1, partLength, '0');
}

if (p.type === DatePart.Hours && p.format.indexOf('h') !== -1) {
targetValue = this.prependValue(this.toTwelveHourFormat(targetValue), partLength, '0');
}

if (p.type === DatePart.Year && p.format.length === 2) {
targetValue = this.prependValue(parseInt(targetValue.slice(-2), 10), partLength, '0');
}

this.inputValue = this.maskParser.replaceInMask(this.inputValue, targetValue, this.maskOptions, p.start, p.end).value;
});
this.setSelectionRange(cursor);
}

private toTwelveHourFormat(value: string): number {
let hour = parseInt(value.replace(new RegExp(this.promptChar, 'g'), '0'), 10);
if (hour > 12) {
hour -= 12;
} else if (hour === 0) {
hour = 12;
}

return hour;
}

private getMaskedValue(datePart: DatePart, partLength: number): string {
let maskedValue;
switch (datePart) {
Expand Down Expand Up @@ -289,14 +316,14 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
}

if (datePart !== DatePart.AmPm) {
return this.prependPromptChars(maskedValue, partLength);
return this.prependValue(maskedValue, partLength, '0');
}

return maskedValue;
}

private prependPromptChars(value: number, partLength: number): string {
return (this.promptChar + value.toString()).slice(-partLength);
private prependValue(value: number, partLength: number, prependChar: string): string {
return (prependChar + value.toString()).slice(-partLength);
}

private spin(event: KeyboardEvent): void {
Expand Down
83 changes: 38 additions & 45 deletions projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,44 @@ export class IgxMaskDirective implements OnInit, AfterViewChecked, ControlValueA
/** @hidden */
@HostListener('input')
public onInputChanged(): void {
this.handleInputChanged(true);
if (isIE() && this._stopPropagation) {
this._stopPropagation = false;
return;
}

if (this._hasDropAction) {
this._start = this.selectionStart;
}
if (this.inputValue.length < this._oldText.length && this._key === KEYCODES.INPUT_METHOD) {
// software keyboard input delete
this._key = KEYCODES.BACKSPACE;
}

let valueToParse = '';
switch (this._key) {
case KEYCODES.DELETE:
this._end = this._start === this._end ? ++this._end : this._end;
break;
case KEYCODES.BACKSPACE:
this._start = this.selectionStart;
break;
default:
valueToParse = this.inputValue.substring(this._start, this.selectionEnd);
break;
}

const replacedData = this.maskParser.replaceInMask(this._oldText, valueToParse, this.maskOptions, this._start, this._end);
this.inputValue = replacedData.value;
if (this._key === KEYCODES.BACKSPACE) { replacedData.end = this._start; }
this.setSelectionRange(replacedData.end);

const rawVal = this.maskParser.parseValueFromMask(this.inputValue, this.maskOptions);
this._dataValue = this.includeLiterals ? this.inputValue : rawVal;
this._onChangeCallback(this._dataValue);

this.onValueChange.emit({ rawValue: rawVal, formattedValue: this.inputValue });

this.afterInput();
}

/** @hidden */
Expand Down Expand Up @@ -235,50 +272,6 @@ export class IgxMaskDirective implements OnInit, AfterViewChecked, ControlValueA
this._droppedData = event.dataTransfer.getData('text');
}

/** @hidden */
protected handleInputChanged(reset?: boolean) {
if (isIE() && this._stopPropagation) {
this._stopPropagation = false;
return;
}

if (this._hasDropAction) {
this._start = this.selectionStart;
}
if (this.inputValue.length < this._oldText.length && this._key === KEYCODES.INPUT_METHOD) {
// software keyboard input delete
this._key = KEYCODES.BACKSPACE;
}

let valueToParse = '';
switch (this._key) {
case KEYCODES.DELETE:
this._end = this._start === this._end ? ++this._end : this._end;
break;
case KEYCODES.BACKSPACE:
this._start = this.selectionStart;
break;
default:
valueToParse = this.inputValue.substring(this._start, this.selectionEnd);
break;
}

const replacedData = this.maskParser.replaceInMask(this._oldText, valueToParse, this.maskOptions, this._start, this._end);
this.inputValue = replacedData.value;
if (this._key === KEYCODES.BACKSPACE) { replacedData.end = this._start; }
this.setSelectionRange(replacedData.end);

const rawVal = this.maskParser.parseValueFromMask(this.inputValue, this.maskOptions);
this._dataValue = this.includeLiterals ? this.inputValue : rawVal;
this._onChangeCallback(this._dataValue);

this.onValueChange.emit({ rawValue: rawVal, formattedValue: this.inputValue });

if (reset) {
this.afterInput();
}
}

/** @hidden */
protected showMask(value: string) {
if (this.focusedValuePipe) {
Expand Down

0 comments on commit 85ab5c9

Please sign in to comment.